前言
本篇文章是針對(duì)上一篇文章:帶你封裝自己的MVP+Retrofit+RxJava2框架(一)的進(jìn)一步封裝改進(jìn)缨历,建議在看完上一篇文章后粥血,再食用本文效果更佳题山!
本文已經(jīng)收錄到我的Github個(gè)人博客懈糯,歡迎大佬們光臨寒舍:我的GIthub博客
本篇文章需要已經(jīng)具備的知識(shí):
-
MVP
的概念和基本使用 -
Retrofit
框架的基本使用 -
RxJava2
框架的基本使用 -
ButterKnife
框架的基本使用 -
Base
基類的概念 - YUtils的簡單使用
- BaseRecyclerViewAdapterHelper的簡單使用
學(xué)習(xí)清單:
-
Base
實(shí)體類的封裝 -
Base
異常類的封裝 -
Base
觀察者的封裝 -
RxJava
線程自動(dòng)調(diào)度的小技巧 - 進(jìn)行網(wǎng)絡(luò)請(qǐng)求自動(dòng)顯示加載中
- 完成網(wǎng)絡(luò)請(qǐng)求自動(dòng)關(guān)閉加載中
- 自動(dòng)處理異常信息
-
Cookie
自動(dòng)持久化與Retrofit
的協(xié)同使用 - 接口管理
Retrofit
請(qǐng)求接口的優(yōu)美方式
一.為什么要封裝這套框架
? 如上一篇文章所說粤蝎,在MVP
模式日漸流行的時(shí)候志笼,封裝一套MVP
框架乏盐,不僅對(duì)日常的開發(fā)大大便利什乙,還能提前積累一下未來在實(shí)際工作中的技巧被辑,并且燎悍,良好的封裝和規(guī)范使用還能減少開發(fā)中的各種令人頭疼的BUG。
? 有人可能會(huì)問:“你上一篇不是也寫了MVP
框架嗎盼理?你這篇難道還是一樣的嗎谈山?難道你是換湯不換藥嗎?”
? 其實(shí)宏怔,一開始筆者自以為我上一篇文章封裝的MVP
框架已經(jīng)夠不錯(cuò)了奏路,但是,在筆者某天看了yechaoa大神玩安卓java的源碼后臊诊,被其封裝的MVP
框架的所折服鸽粉,因此第一時(shí)間寫這篇文章,想向大家分享下抓艳,筆者從中汲取的經(jīng)驗(yàn)触机,希望能夠幫助到各位!
? 本文相對(duì)帶你封裝自己的MVP+Retrofit+RxJava2框架(一)的改進(jìn)地方有下面幾點(diǎn):
-
精簡了Activity基類玷或,將原來的兩個(gè)
BaseActivity
和BaseMvpActivity
精簡為一個(gè)BaseActivity
-
修復(fù)了當(dāng)繼承了
Activity
基類儡首,不添加Presenter
會(huì)導(dǎo)致空指針的Bug
- 添加了網(wǎng)絡(luò)請(qǐng)求可選擇自動(dòng)顯示加載中和自動(dòng)關(guān)閉加載中的功能
- 添加了自動(dòng)處理異常信息的功能
- 封裝了一個(gè)
Bean
對(duì)象的基類 -
精簡了RxJava的用法,因此可以省去
Model
類的編寫 -
封裝了一個(gè)
Observer
的基類 - 增添了cookie自動(dòng)持久化的功能
-
改進(jìn)了
RetrofitService
的封裝偏友,將Retrofit
接口的實(shí)例化引入基類
二.核心用法與樣例分析
本項(xiàng)目基于
Android X
進(jìn)行構(gòu)建蔬胯,完整代碼已經(jīng)上傳到我的Github倉庫
首先,先給大家介紹下筆者項(xiàng)目的基本結(jié)構(gòu)
為了給大家模擬帶自動(dòng)獲取Cookie的功能,所以筆者設(shè)計(jì)了一個(gè)具有登陸约谈,注冊(cè)笔宿,收藏功能的Demo
筆者在Demo
中用到的框架如下
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'com.google.android.material:material:1.1.0'
//cardView
implementation 'androidx.cardview:cardview:1.0.0'
/*retrofit犁钟、rxjava*/
implementation 'com.squareup.retrofit2:retrofit:2.6.2'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'
implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'
/*glide*/
implementation 'com.github.bumptech.glide:glide:4.10.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0'
/*butterknife*/
implementation 'com.jakewharton:butterknife:10.2.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.0'
/*YUtils*/
implementation 'com.github.yechaoa:YUtils:2.1.0'
/*BRVAH*/
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.50'
/*banner*/
implementation 'com.youth.banner:banner:1.4.10'
下面筆者將為大家詳細(xì)介紹每個(gè)類的相關(guān)信息
2.1 Base
基類
2.1.1 BaseActivity
BaseActivity
相對(duì)于筆者上一個(gè)版本的MVP
框架的改進(jìn)之處:
- 將兩個(gè)基類
Activity
合并為一個(gè)BaseActivity
- 在其中封裝了進(jìn)度條的顯示和隱藏的方法
/**
* Description : BaseActivity
*
* @author XuCanyou666
* @date 2020/2/7
*/
public abstract class BaseActivity<P extends BasePresenter> extends AppCompatActivity implements BaseView {
protected P presenter;
protected abstract P createPresenter();
protected abstract int getLayoutId();
protected abstract void initView();
protected abstract void initData();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//設(shè)置豎屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
setContentView(LayoutInflater.from(this).inflate(getLayoutId(), null));
ButterKnife.bind(this);
presenter = createPresenter();
initView();
initData();
}
@Override
protected void onResume() {
super.onResume();
initListener();
}
@Override
protected void onDestroy() {
super.onDestroy();
//銷毀時(shí),解除綁定
if (presenter != null) {
presenter.detachView();
}
}
protected void initListener() {
}
@Override
public void showLoading() {
YUtils.showLoading(this, "加載中");
}
@Override
public void hideLoading() {
YUtils.dismissLoading();
}
/**
* 可以處理異常
*/
@Override
public void onErrorCode(BaseBean bean) {
}
/**
* 啟動(dòng)activity
*
* @param activity 當(dāng)前活動(dòng)
* @param isFinish 是否結(jié)束當(dāng)前活動(dòng)
*/
public void startActivity(Class<?> activity, boolean isFinish) {
Intent intent = new Intent(this, activity);
startActivity(intent);
if (isFinish) {
finish();
}
}
}
2.1.2 BaseFragment
/**
* Description : BaseFragment
*
* @author XuCanyou666
* @date 2020/2/7
*/
public abstract class BaseFragment<P extends BasePresenter> extends Fragment implements BaseView {
private Unbinder unbinder;
protected Context mContext;
protected P presenter;
protected abstract P createPresenter();
protected abstract int getLayoutId();
protected abstract void initView();
protected abstract void initData();
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(getLayoutId(), container, false);
unbinder = ButterKnife.bind(this, view);
//得到context,在后面的子類Fragment中都可以直接調(diào)用
mContext = ActivityUtil.getCurrentActivity();
presenter = createPresenter();
initView();
initData();
return view;
}
@Override
public void onResume() {
super.onResume();
initListener();
}
@Override
public void onDestroyView() {
super.onDestroyView();
//do something
unbinder.unbind();
//銷毀時(shí)泼橘,解除綁定
if (presenter != null) {
presenter.detachView();
}
}
private void initListener() {
}
@Override
public void onErrorCode(BaseBean bean) {
}
/**
* 顯示加載中
*/
@Override
public void showLoading() {
YUtils.showLoading(ActivityUtil.getCurrentActivity(), "加載中");
}
/**
* 隱藏加載中
*/
@Override
public void hideLoading() {
YUtils.dismissLoading();
}
}
2.1.3 BasePresenter
BasePresenter
相對(duì)于筆者上一個(gè)版本的MVP
框架的改進(jìn)之處:
- 將線程的調(diào)度寫入了
addDisposable
中- 改寫了
addDisposable
方法涝动,使得調(diào)用方式更加簡單優(yōu)美
/**
* Description : BasePresenter
*
* @author XuCanyou666
* @date 2020/2/7
*/
public class BasePresenter<V extends BaseView> {
private CompositeDisposable compositeDisposable;
public V baseView;
/**
* 這個(gè)后面可以直接用 Example:apiServer.login(username, password);
*/
protected API.WAZApi apiServer = RetrofitService.getInstance().getApiService();
public BasePresenter(V baseView) {
this.baseView = baseView;
}
/**
* 解除綁定
*/
public void detachView() {
baseView = null;
removeDisposable();
}
/**
* 返回 view
*/
public V getBaseView() {
return baseView;
}
public void addDisposable(Observable<?> observable, BaseObserver observer) {
if (compositeDisposable == null) {
compositeDisposable = new CompositeDisposable();
}
compositeDisposable
.add(observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(observer));
}
private void removeDisposable() {
if (compositeDisposable != null) {
compositeDisposable.dispose();
}
}
}
2.1.4 BaseObserver
Observer
的基類炬灭,提供了自動(dòng)顯示和自動(dòng)隱藏進(jìn)度條的方法對(duì)內(nèi)處理了
onStart
醋粟,onError
,onComplete
方法對(duì)外只提供了
onSuccess
和onError
方法重归,符合用戶一般使用習(xí)慣
/**
* Description : BaseObserver
*
* @author XuCanyou666
* @date 2020/2/7
*/
public abstract class BaseObserver<T> extends DisposableObserver<T> {
protected BaseView view;
private boolean isShowDialog;
protected BaseObserver(BaseView view) {
this.view = view;
}
/**
* 帶進(jìn)度條的初始化方法
*
* @param view view
* @param isShowDialog 是否顯示進(jìn)度條
*/
protected BaseObserver(BaseView view, boolean isShowDialog) {
this.view = view;
this.isShowDialog = isShowDialog;
}
@Override
protected void onStart() {
if (view != null && isShowDialog) {
view.showLoading();
}
}
@Override
public void onNext(T o) {
onSuccess(o);
}
@Override
public void onError(Throwable e) {
if (view != null && isShowDialog) {
view.hideLoading();
}
BaseException be;
if (e != null) {
//自定義異常
if (e instanceof BaseException) {
be = (BaseException) e;
//回調(diào)到view層 處理 或者根據(jù)項(xiàng)目情況處理
if (view != null) {
// 處理登錄失效 更新
view.onErrorCode(new BaseBean(be.getErrorCode(), be.getErrorMsg()));
} else {
onError(be.getErrorMsg());
}
//系統(tǒng)異常
} else {
if (e instanceof HttpException) {
//HTTP錯(cuò)誤
be = new BaseException(BaseException.BAD_NETWORK_MSG, e);
} else if (e instanceof ConnectException || e instanceof UnknownHostException) {
//連接錯(cuò)誤
be = new BaseException(BaseException.CONNECT_ERROR_MSG, e);
} else if (e instanceof InterruptedIOException) {
//連接超時(shí)
be = new BaseException(BaseException.CONNECT_TIMEOUT_MSG, e);
} else if (e instanceof JsonParseException || e instanceof JSONException || e instanceof ParseException) {
//解析錯(cuò)誤
be = new BaseException(BaseException.PARSE_ERROR_MSG, e);
} else {
be = new BaseException(BaseException.OTHER_MSG, e);
}
}
} else {
be = new BaseException(BaseException.OTHER_MSG);
}
onError(be.getErrorMsg());
}
@Override
public void onComplete() {
if (view != null && isShowDialog) {
view.hideLoading();
}
}
public abstract void onSuccess(T o);
public abstract void onError(String msg);
}
2.1.5 BaseException
異常的基類
/**
* Description : BaseException
*
* @author XuCanyou666
* @date 2020/2/7
*/
public class BaseException extends IOException {
/**
* 解析數(shù)據(jù)失敗
*/
public static final String PARSE_ERROR_MSG = "解析數(shù)據(jù)失敗";
/**
* 網(wǎng)絡(luò)問題
*/
public static final String BAD_NETWORK_MSG = "網(wǎng)絡(luò)問題";
/**
* 連接錯(cuò)誤
*/
public static final String CONNECT_ERROR_MSG = "連接錯(cuò)誤";
/**
* 連接超時(shí)
*/
public static final String CONNECT_TIMEOUT_MSG = "連接超時(shí)";
/**
* 未知錯(cuò)誤
*/
public static final String OTHER_MSG = "未知錯(cuò)誤";
private String errorMsg;
private int errorCode;
public String getErrorMsg() {
return errorMsg;
}
public int getErrorCode() {
return errorCode;
}
public BaseException(String message) {
this.errorMsg = message;
}
public BaseException(String errorMsg, Throwable cause) {
super(errorMsg, cause);
this.errorMsg = errorMsg;
}
public BaseException(int errorCode, String message) {
this.errorMsg = message;
this.errorCode = errorCode;
}
}
2.1.6 BaseBean
實(shí)體類的基類米愿,方便處理返回的
Json
數(shù)據(jù),具體的寫法需根據(jù)每個(gè)API
而定
/**
* Description : BaseBean 實(shí)體類的基類
*
* @author XuCanyou666
* @date 2020/2/7
*/
public class BaseBean<T> implements Serializable {
/**
* data :
* errorCode : 0
* errorMsg :
*/
public int errorCode;
public String errorMsg;
public T data;
public BaseBean(int code, String data) {
this.errorCode = code;
this.data = (T) data;
}
}
2.1.7 BaseView
/**
* Description : BaseView
*
* @author XuCanyou666
* @date 2020/2/7
*/
public interface BaseView {
void showLoading();
void hideLoading();
void onErrorCode(BaseBean bean);
}
2.2 http
2.2.1 cookie
持久化
cookie
,因?yàn)榇a太多鼻吮,這里只展示一個(gè)類的代碼育苟,詳細(xì)代碼請(qǐng)前往我的Github查看
package com.users.xucanyou666.rxjava2_retrofit_mvp2.http.cookie;
import android.content.Context;
import java.util.List;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
/**
* Created by yechao on 2019/11/19/019.
* Describe :
*/
public class CookiesManager implements CookieJar {
private final PersistentCookieStore cookieStore;
public CookiesManager(Context context) {
cookieStore = new PersistentCookieStore(context);
}
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
if (cookies.size() > 0) {
for (Cookie item : cookies) {
cookieStore.add(url, item);
}
}
}
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
return cookieStore.get(url);
}
}
2.2.2 gson
重寫
ResponseBodyConverter
對(duì)Json
預(yù)處理,這里只展示一個(gè)類的代碼椎木,詳細(xì)代碼請(qǐng)前往我的Github
/**
* Created by yechao on 2019/11/18/018.
* Describe : 重寫ResponseBodyConverter對(duì)json預(yù)處理
*/
public class BaseResponseBodyConverter<T> implements Converter<ResponseBody, T> {
private final TypeAdapter<T> adapter;
/**
* 登陸失效
*/
private static final int LOG_OUT_TIME = -1001;
BaseResponseBodyConverter( TypeAdapter<T> adapter) {
this.adapter = adapter;
}
@Override
public T convert(ResponseBody value) throws IOException {
String jsonString = value.string();
try {
JSONObject object = new JSONObject(jsonString);
int code = object.getInt("errorCode");
if (0 != code) {
String data;
//錯(cuò)誤信息
if (code == LOG_OUT_TIME) {
data = "登錄失效违柏,請(qǐng)重新登錄";
} else {
data = object.getString("errorMsg");
}
//異常處理
throw new BaseException(code, data);
}
//正確返回整個(gè)json
return adapter.fromJson(jsonString);
} catch (JSONException e) {
e.printStackTrace();
//數(shù)據(jù)解析異常即json格式有變動(dòng)
throw new BaseException(BaseException.PARSE_ERROR_MSG);
} finally {
value.close();
}
}
}
2.2.3 API
- 原因:隨著項(xiàng)目日漸龐大,請(qǐng)求也越來越多香椎,不可能每個(gè)請(qǐng)求都使用一個(gè)接口漱竖,否則不但造成浪費(fèi),而且不方便管理
- 作用:新建一個(gè)
API
作為Retrofit
的管理類畜伐,用一個(gè)接口管理所有網(wǎng)絡(luò)請(qǐng)求馍惹,可以有效改善代碼質(zhì)量
/**
* Description : API
* 接口的管理類
*
* @author XuCanyou666
* @date 2020/2/7
*/
public class API {
static final String BASE_URL = "https://www.wanandroid.com/";
public interface WAZApi {
//-----------------------【首頁相關(guān)】----------------------
//首頁文章列表 這里的{}是填入頁數(shù)
@GET("article/list/{page}/json")
Observable<BaseBean<Article>> getArticleList(@Path("page") Integer page);
//-----------------------【登錄注冊(cè)】----------------------
//登錄
@FormUrlEncoded
@POST("user/login")
Observable<BaseBean<User>> login(@Field("username") String username, @Field("password") String password);
//注冊(cè)
@FormUrlEncoded
@POST("user/register")
Observable<BaseBean<User>> register(@Field("username") String username, @Field("password") String password, @Field("repassword") String repassword);
//-----------------------【 收藏 】----------------------
//收藏站內(nèi)文章
@POST("lg/collect/{id}/json")
Observable<BaseBean> collectIn(@Path("id") Integer id);
//取消收藏---文章列表
@POST("lg/uncollect_originId/{id}/json")
Observable<BaseBean> uncollect(@Path("id") Integer id);
}
}
2.2.4 RetrofitService
Retrofit
的配置類,在里面初始化了apiServer
對(duì)象玛界,并配置了日志信息万矾,超時(shí)時(shí)間,Cookie
持久化脚仔,用了靜態(tài)內(nèi)部類的單例模式
/**
* Description : RetrofitService
*
* @author XuCanyou666
* @date 2020/2/8
*/
public class RetrofitService {
private volatile static RetrofitService apiRetrofit;
private API.WAZApi apiServer;
/**
* 單例調(diào)用
*
* @return RetrofitService
*/
public static RetrofitService getInstance() {
if (apiRetrofit == null) {
synchronized (Object.class) {
if (apiRetrofit == null) {
apiRetrofit = new RetrofitService();
}
}
}
return apiRetrofit;
}
/**
* 獲取api對(duì)象
*
* @return api對(duì)象
*/
public API.WAZApi getApiService() {
return apiServer;
}
/**
* 初始化retrofit
*/
private RetrofitService() {
//配置okHttp并設(shè)置時(shí)間勤众、日志信息和cookies
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
//設(shè)置超時(shí)時(shí)間
.connectTimeout(15, TimeUnit.SECONDS)
//設(shè)置Cookie持久化
.cookieJar(new CookiesManager(XUtil.getApplication()))
.build();
//關(guān)聯(lián)okHttp并加上rxJava和Gson的配置和baseUrl
Retrofit retrofit = new Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(BaseConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(API.BASE_URL)
.build();
apiServer = retrofit.create(API.WAZApi.class);
}
}
2.3 bean
- 這里的帶有嵌套的實(shí)體類看似很復(fù)雜,其實(shí)可以通過
AS
的GsonFormat
插件一鍵生成- 注意:不要將
data
,errorCode
,errorMsg
的導(dǎo)入到實(shí)體類中
2.3.1 Article
文章內(nèi)容的實(shí)體類
/**
* Description : Article
*
* @author XuCanyou666
* @date 2020/2/8
*/
public class Article {
/**
* curPage : 2
* datas : [{"apkLink":"","author":"葉應(yīng)是葉","chapterId":67,"chapterName":"網(wǎng)絡(luò)基......."}]
* offset : 20
* over : false
* pageCount : 62
* size : 20
* total : 1224
*/
public int curPage;
public int offset;
public boolean over;
public int pageCount;
public int size;
public int total;
public List<DataDetailBean> datas;
public static class DataDetailBean {
/**
* apkLink :
* author : 葉應(yīng)是葉
* chapterId : 67
* chapterName : 網(wǎng)絡(luò)基礎(chǔ)
* collect : false
* courseId : 13
* desc :
* envelopePic :
* fresh : false
* id : 2809
* link : http://www.reibang.com/p/6d2f324c8f42
* niceDate : 2018-04-12
* origin :
* projectLink :
* publishTime : 1523532264000
* superChapterId : 98
* superChapterName : 網(wǎng)絡(luò)訪問
* tags : []
* title : 在 Android 設(shè)備上搭建 Web 服務(wù)器
* type : 0
* visible : 1
* zan : 0
*/
public String apkLink;
public String author;
public int chapterId;
public String chapterName;
public boolean collect;
public int courseId;
public String desc;
public String envelopePic;
public boolean fresh;
public int id;
public int originId;
public String link;
public String niceDate;
public String origin;
public String projectLink;
public long publishTime;
public int superChapterId;
public String superChapterName;
public String title;
public int type;
public int visible;
public int zan;
public List<?> tags;
}
}
2.3.2 User
/**
* GitHub : https://github.com/yechaoa
* CSDN : http://blog.csdn.net/yechaoa
* <p>
* Created by yechao on 2018/5/2.
* Describe :
*/
public class User {
/**
* collectIds : []
* email :
* icon :
* id : 3
* password : 111111
* type : 0
* username : 111111
*/
public String email;
public String icon;
public int id;
public String password;
public int type;
public String username;
public List<?> collectIds;
public String repassword;
}
2.4 module
- 這里分模塊化進(jìn)行管理鲤脏,本
Demo
有Login
,Register
,Home
總共三個(gè)模塊- 限于篇幅们颜,在這里僅說明一個(gè)模塊,其他模塊的寫法類似猎醇,具體寫法窥突,可以上Github查看
2.4.1 login
2.4.1.1 ILoginView
LoginView
層的接口
/**
* Description : ILoginView
*
* @author XuCanyou666
* @date 2020/2/8
*/
public interface ILoginView extends BaseView {
/**
* 顯示登陸成功
*
* @param successMessage 成功信息
*/
void showLoginSuccess(String successMessage);
/**
* 顯示登陸失敗
*
* @param errorMessage 失敗信息
*/
void showLoginFailed(String errorMessage);
void doSuccess(BaseBean<User> user);
}
2.4.1.2 LoginPresenter
這里因?yàn)?code>RxJava經(jīng)過封裝后,
Model
層的代碼比較精簡硫嘶,所以將Model
直接寫入Presenter
中阻问,以節(jié)省工作量
/**
* Description : LoginPresenter
*
* @author XuCanyou666
* @date 2020/2/8
*/
class LoginPresenter extends BasePresenter<ILoginView> {
LoginPresenter(ILoginView baseView) {
super(baseView);
}
/**
* 登陸
*
* @param username username
* @param password password
* @param usernameCountMax 賬號(hào)規(guī)定輸入字符最大值
* @param passwordCountMax 密碼規(guī)定輸入字符最大值
*/
void login(String username, String password, int usernameCountMax, int passwordCountMax) {
YUtils.closeSoftKeyboard();
//判斷輸入的賬號(hào)密碼是否符合規(guī)范
if (isValid(username, password, usernameCountMax, passwordCountMax)) {
addDisposable(apiServer.login(username, password), new BaseObserver<BaseBean<User>>(baseView, true) {
@Override
public void onSuccess(BaseBean<User> bean) {
baseView.showLoginSuccess("登錄成功( ̄▽ ̄)");
//將登陸的賬號(hào)存進(jìn)sp里面
SpUtil.setBoolean(GlobalConstant.IS_LOGIN, true);
SpUtil.setString(GlobalConstant.USERNAME, bean.data.username);
SpUtil.setString(GlobalConstant.PASSWORD, bean.data.password);
baseView.doSuccess();
}
@Override
public void onError(String msg) {
baseView.showLoginFailed(msg + "(°?°)?");
}
});
} else {
baseView.showLoginFailed("填寫錯(cuò)誤 (°?°)?");
}
}
/**
* 判斷輸入的賬號(hào)密碼是否符合規(guī)范
*
* @param userName username
* @param password password
* @param usernameCountMax 賬號(hào)規(guī)定輸入字符最大值
* @param passwordCountMax 密碼規(guī)定輸入字符最大值
* @return 是否合規(guī)
*/
private boolean isValid(String userName, String password, int usernameCountMax, int passwordCountMax) {
return check(userName, usernameCountMax) && check(password, passwordCountMax);
}
/**
* 判斷輸入是否規(guī)范
*
* @param string 輸入的內(nèi)容
* @param tilCounterMaxLength textInputLayout控件的輸入字符的最大長度
* @return 是否合規(guī)
*/
private boolean check(String string, int tilCounterMaxLength) {
return !TextUtils.isEmpty(string) && string.length() <= tilCounterMaxLength && tilCounterMaxLength / 2 <= string.length();
}
}
2.4.1.3 LoginTextWatcher
登陸界面輸入框的監(jiān)聽器
/**
* TextInputLayout監(jiān)聽器
* created by xucanyou666
* on 2020/2/7 18:09
* email:913710642@qq.com
*/
public class LoginTextWatcher implements android.text.TextWatcher {
private TextInputLayout mTilUsername;
private TextInputLayout mTilPassword;
LoginTextWatcher(TextInputLayout username, TextInputLayout password) {
mTilUsername = username;
mTilPassword = password;
}
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable s) {
checkInput(mTilUsername);
checkInput(mTilPassword);
}
/**
* 判斷輸入內(nèi)容是否合法
*
* @param textInputLayout textInputLayout
*/
public static void checkInput(TextInputLayout textInputLayout) {
if (textInputLayout != null) {
if (textInputLayout.getEditText().getText().length() > textInputLayout.getCounterMaxLength()) {
textInputLayout.setError("輸入內(nèi)容超過上限");
} else if (textInputLayout.getEditText().getText().length() < textInputLayout.getCounterMaxLength() / 2) {
textInputLayout.setError("最少6位");
} else {
textInputLayout.setError(null);
}
}
}
}
2.4.1.4 LoginActivity
/**
* Description : LoginActivity
*
* @author XuCanyou666
* @date 2020/2/8
*/
public class LoginActivity extends BaseActivity<LoginPresenter> implements ILoginView {
@BindView(R.id.et_username)
EditText mEtUsername;
@BindView(R.id.til_username)
TextInputLayout mTilUsername;
@BindView(R.id.et_password)
EditText mEtPassword;
@BindView(R.id.til_password)
TextInputLayout mTilPassword;
@BindView(R.id.btn_login)
Button mBtnLogin;
@BindView(R.id.btn_register)
Button mBtnRegister;
@Override
protected LoginPresenter createPresenter() {
return new LoginPresenter(this);
}
@Override
protected int getLayoutId() {
return R.layout.activity_login;
}
@Override
protected void initData() {
}
@Override
protected void initView() {
LoginTextWatcher textWatcher = new LoginTextWatcher(mTilUsername, mTilPassword);
mEtUsername.addTextChangedListener(textWatcher);
mEtPassword.addTextChangedListener(textWatcher);
}
@Override
public void showLoginSuccess(String successMessage) {
ToastUtil.showToast(successMessage);
}
@Override
public void showLoginFailed(String errorMessage) {
ToastUtil.showToast(errorMessage);
}
@Override
public void doSuccess() {
startActivity(MainActivity.class, true);
}
@OnClick({R.id.btn_login, R.id.btn_register})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.btn_login:
String username = mEtUsername.getText().toString().trim();
String password = mEtPassword.getText().toString().trim();
int tilUsernameCounterMaxLength = mTilUsername.getCounterMaxLength();
int tilPasswordCounterMaxLength = mTilPassword.getCounterMaxLength();
presenter.login(username, password, tilUsernameCounterMaxLength, tilPasswordCounterMaxLength);
break;
case R.id.btn_register:
YUtils.closeSoftKeyboard();
startActivity(RegisterActivity.class, false);
break;
default:
break;
}
}
}
三.我在使用中遇到的問題
3.1 顏色的資源文件出錯(cuò)
有一天, 當(dāng)我點(diǎn)開我的colors.xml
資源文件的時(shí)候沦疾,發(fā)現(xiàn)是下圖這個(gè)樣子
然后當(dāng)鼠標(biāo)的光標(biāo)移動(dòng)到紅色標(biāo)記處称近,發(fā)現(xiàn)
The color “colorPrimary” in values has no declaration in the base values folder; this can lead to crashes when the resource is queried in a configuration that does not match this qualifier less…
接著我翻譯了一下:
值中的顏色“
colorPrimary
”在基本值folde
中沒有聲明
懵逼了第队,我不是聲明了嗎....最后還是百度到了結(jié)果
解決方式是:先把colors
文件剪切下來,再粘回去刨秆。
感覺是AS
的BUG
....我用的AS
版本是3.5.1
3.2 導(dǎo)入依賴的時(shí)候提示Failed to resolve
當(dāng)然百度了一下凳谦,解決方式是:在根目錄的build.gradle
中添加maven
就是下面這樣
3.3 在進(jìn)入文章列表界面的時(shí)候,進(jìn)度條不會(huì)自動(dòng)隱藏
發(fā)生問題的場景:筆者在
Presenter
中請(qǐng)求文章列表的數(shù)據(jù)的時(shí)候衡未,會(huì)自動(dòng)顯示和隱藏進(jìn)度條尸执,但請(qǐng)求完文章列表后,不能自動(dòng)隱藏
經(jīng)過瀏覽代碼缓醋,發(fā)現(xiàn)如失,我的請(qǐng)求文章列表的方法寫多了一次,解決方法:只保存onResume
里面的一次
四.快捷體驗(yàn)
如果你想更加簡單地使用這套框架送粱,筆者特地為您準(zhǔn)備了我已經(jīng)封裝好的 MVP
框架褪贵,你只需要導(dǎo)入依賴即可享受如上的快捷的開發(fā)體驗(yàn),詳情請(qǐng)見 github
github
地址:https://github.com/LoveLifeEveryday/XMvp
五.在項(xiàng)目中運(yùn)用
看完了抗俄,有些讀者可能會(huì)有疑惑竭鞍,說得那么牛逼,在項(xiàng)目中運(yùn)用又是怎樣吖橄镜,會(huì)不會(huì)有bug吖!冯乘!別急洽胶,筆者馬上根據(jù)這個(gè)框架,開發(fā)了一個(gè)簡單易用美觀的『玩安卓』裆馒!希望能夠解決您的困惑hhh
[圖片上傳失敗...(image-74b509-1603720322753)]
如果文章對(duì)您有一點(diǎn)幫助的話姊氓,希望您能點(diǎn)一下贊,您的點(diǎn)贊喷好,是我前進(jìn)的動(dòng)力
本文參考鏈接: