
前言
- 前面幾章,和大家說了 DataBinding、Dagger2 在的配置,今天就說說在項目中如何使用吧,配合 MVP 模式對 BaseActivtiy 進行封裝。(之前已經(jīng)說過一遍了,是在 Java 平臺下,傳送門在此)
- KotlinTest Github
MVP
那么,我們就從 MVP 開始。
-
首先,項目中使用 Retrofit + RxJava2 進行網(wǎng)絡(luò)請求,那么,我們在使用的時候就要考慮到 RxJava 的生命周期問題,如果不對它進行處理,那么在應用中進入一個 Activity、請求網(wǎng)絡(luò)、在請求未完成返回這個流程中就會拋出異常,導致應用崩潰,于是,就有了這樣的 BaseMVPPresenter。
/** * Presenter基類 * * @param V MVP View類型 繼承[BaseMVPView] * @param M MVP Module 繼承[BaseMVPModule] */ open class BaseMVPPresenter<V : BaseMVPView, M : BaseMVPModule> { /** MVP View 對象 */ protected var mView: V? = null /** MVP Module 對象 */ @Inject protected lateinit var mModule: M /** RxJava2 生命周期管理 */ private val disposables: CompositeDisposable = CompositeDisposable() /** * 界面綁定,關(guān)聯(lián) MVP View * * @param view MVP View */ fun attach(view: V) { mView = view } /** * 解除綁定,去除 MVP View 引用 */ fun detach() { mView = null } /** * 檢查請求返回數(shù)據(jù),并在登錄狀態(tài)異常時彈出提示 * * @param data 返回數(shù)據(jù) * @param T 返回數(shù)據(jù)類型 * * @return 是否成功 */ protected fun <T : BaseEntity> checkResponse(data: T): Boolean { return data.code == Constants.ResponseCode.SUCCESS } /** * 將網(wǎng)絡(luò)請求添加到 RxJava2 生命周期 */ protected fun addDisposable(dis: Disposable) { disposables.add(dis) } /** * 消費所有事件 */ fun dispose() { if (!disposables.isDisposed && disposables.size() > 0) { disposables.dispose() } } } 為了能夠方便的進行復用,所以 BaseMVPPresenter 中使用泛型來確定 View 以及 Module 的類型。
定義的 attach() 方法在 presenter 使用前調(diào)用,綁定 View,并在 Activity 結(jié)束時使用 detach() 方法接觸綁定,移除引用,調(diào)用 dispose() 方法消費所有事件。
-
在 Module 中進行網(wǎng)絡(luò)請求以及其他耗時操作,所以有了 BaseMVPModule
/** * MVP Module基類 */ open class BaseMVPModule @Inject constructor() { @Inject lateinit var netClient: NetApi } 因為在所有 Module 中都會用到網(wǎng)絡(luò)請求,所以將網(wǎng)絡(luò)請求 API 在 BaseMVPModule 中聲明。
-
其中 NetApi 的依賴注入可以新建一個 NetModule
/** * 網(wǎng)絡(luò)模塊依賴注入 */ @Module class NetModule { @Provides @Singleton fun netClient(): NetApi { val okHttpClient = OkHttpClient.Builder() .addInterceptor(ParametersInterceptor()) .addInterceptor(LogInterceptor()) .build() val retrofit = Retrofit.Builder() .baseUrl(UrlDefinition.BASE_URL) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create()) .client(okHttpClient) .build() return retrofit.create(NetApi::class.java) } } 這里的 @Singleton 注解表明使用單例模式,即 NetApi 對象是單例的。
-
然后還需要將 NetModule 添加到 ApplicationSub 中
/** * Application Dagger2 組件 */ @Singleton @Component(modules = arrayOf( ActivityModule::class, SupportFragmentModule::class, NetModule::class, AndroidSupportInjectionModule::class)) interface ApplicationSub : AndroidInjector<MyApplication> { @Component.Builder abstract class Builder : AndroidInjector.Builder<MyApplication>() } 注意:Module 中使用了 @Singleton 注解,ApplicationSub 中也要添加 @Singleton 注解。
-
然后,就是 BaseMVPView
/** * MVP View基類 */ interface BaseMVPView { /** * 網(wǎng)絡(luò)請求結(jié)束 */ fun onNetFinished() /** * 網(wǎng)絡(luò)故障 */ fun onNetError() /** * 無數(shù)據(jù) */ fun onNoData() /** * 加載中 */ fun onLoading() } 在 BaseMVPView 中,定義了一系列通用方法,即每個界面都會用到的方法。
BaseActivity
上面說完了 MVP 模式相關(guān)的模塊基類封裝,現(xiàn)在說說 Activity 的基類封裝。
-
我們?yōu)槭裁匆庋b基類?當然是為了開發(fā)方便,防止重復的模版代碼,那么首先就要把每個界面都需要的抽取出來,標題欄那是肯定的,還有加載數(shù)據(jù)的不同狀態(tài),即上面 BaseMVPView 中所定義的幾個狀態(tài)。把這些抽取成 layout_base.xml
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <RelativeLayout android:layout_width="match_parent" android:layout_height="@dimen/dp_48"> 標題欄 </RelativeLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:id="@+id/fl_content" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"/> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> 網(wǎng)絡(luò)異常 </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> 無數(shù)據(jù) </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> 加載中 </LinearLayout> </RelativeLayout> </LinearLayout> -
新建 BaseActivity 繼承 DaggerAppCOmpatActivity (支持 Dagger2),使用泛型確定 DataBinding、MVPPresenter 類型。
/** * Activity 基類 */ abstract class BaseActivity<P : BaseMVPPresenter<*, *>, DB : ViewDataBinding> : DaggerAppCompatActivity(), BaseMVPView, RootHandler.OnTitleClickListener { /** 當前界面 Context 對象*/ protected lateinit var mContext: AppCompatActivity /** 當前界面 Presenter 對象 */ @Inject protected lateinit var presenter: P /** 根布局 DataBinding 對象 */ protected lateinit var rootBinding: LayoutBaseBinding /** 當前界面布局 DataBinding 對象 */ protected lateinit var mBinding: DB /** * 重寫 onCreate() 方法,添加了 Dagger2 注入、Activity 管理以及根布局等初始化操作 */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 保存當前 Context 對象 mContext = this // 添加到 AppManager 應用管理 AppManager.addActivity(this) // 加載根布局,初始化 DataBinding rootBinding = DataBindingUtil.inflate( LayoutInflater.from(mContext), R.layout.layout_base, null, false ) // 綁定事件處理 rootBinding.handler = RootHandler(this) } /** * 重寫 onDestroy() 方法,移除 Activity 管理以及 MVP 生命周期管理 */ override fun onDestroy() { // 從應用管理移除當前 Activity 對象 AppManager.removeActivity(this) // 界面銷毀時,消費所有事件,清空引用 presenter.dispose() presenter.detach() super.onDestroy() } /** * 重寫 setContentView(layoutResID) 方法,使其支持 DataBinding 以及標題欄、狀態(tài)欄初始化操作 */ override fun setContentView(layoutResID: Int) { // 初始化標題欄 initTitleBar() // 加載布局,初始化 DataBinding mBinding = DataBindingUtil.inflate( LayoutInflater.from(mContext), layoutResID, null, false ) // 將當前布局添加到根布局 rootBinding.flContent.removeAllViews() rootBinding.flContent.addView(mBinding.root) // 設(shè)置布局 super.setContentView(rootBinding.root) // 初始化狀態(tài)欄 initStatusBar() } /** * 初始化標題欄,抽象方法,子類實現(xiàn)標題欄自定義 */ protected abstract fun initTitleBar() /** * 初始化狀態(tài)欄,默認主題色、不透明,修改需重寫 */ protected fun initStatusBar() { setStatusBar() } /** * 設(shè)置狀態(tài)欄,默認主題色、不透明 * * @param colorResId 狀態(tài)欄顏色,默認主題色 * @param alpha 狀態(tài)欄透明度,默認不透明,取值范圍 0~255 */ protected fun setStatusBar(@ColorRes colorResId: Int = R.color.colorTheme, alpha: Int = 0) { if (alpha !in 0..255) { RuntimeException("The value of the alpha must between 0 and 255") } else { StatusBarUtil.setResColor(this, colorResId, alpha) } } /** * 顯示標題欄 */ protected fun showTitle() { rootBinding.handler?.showTitle = true } /** * 設(shè)置標題文本 * * @param strResID 標題文本資源id */ protected fun setTitleStr(@StringRes strResID: Int) { rootBinding.handler?.showTvTitle = true rootBinding.handler?.tvTitle = getString(strResID) } /** * 設(shè)置標題文本 * * @param str 標題文本 */ protected fun setTitleStr(str: String) { rootBinding.handler?.showTvTitle = true rootBinding.handler?.tvTitle = str } /** * 設(shè)置標題欄左側(cè)圖標,默認返回按鈕 * * @param resID 標題欄左側(cè)圖標資源id,默認返回按鈕 */ protected fun setIvLeft(@DrawableRes resID: Int = R.mipmap.arrow_left_white) { rootBinding.handler?.showIvLeft = true rootBinding.handler?.ivLeftResID = resID } /** * 設(shè)置右側(cè)圖標 * * @param resID 圖片資源id */ protected fun setIvRight(@DrawableRes resID: Int) { rootBinding.handler?.showIvRight = true rootBinding.handler?.ivRightResID = resID } /** * 設(shè)置右側(cè)文本 * * @param strResID 文本資源id */ protected fun setTvRight(@StringRes strResID: Int) { rootBinding.handler?.showTvRight = true rootBinding.handler?.tvRight = getString(strResID) } /** * 重寫B(tài)aseMvpView中方法,網(wǎng)絡(luò)異常時調(diào)用 */ override fun onNetError() { val handler = rootBinding.handler handler?.let { if (handler.showNoData) { handler.showNoData = false } if (handler.showLoading) { val drawable = rootBinding.ivLoading.drawable (drawable as? AnimationDrawable)?.stop() handler.showLoading = false } if (!handler.showNetError) { handler.showNetError = true } onListComplete() } } /** * 重寫B(tài)aseMvpView中方法,無數(shù)據(jù)時調(diào)用 */ override fun onNoData() { val handler = rootBinding.handler handler?.let { if (handler.showNetError) { handler.showNetError = false } if (handler.showLoading) { val drawable = rootBinding.ivLoading.drawable (drawable as? AnimationDrawable)?.stop() handler.showLoading = false } if (!handler.showNoData) { handler.showNoData = true } onListComplete() } } /** * 重寫B(tài)aseMvpView中方法,加載數(shù)據(jù)時調(diào)用 */ override fun onLoading() { val handler = rootBinding.handler handler?.let { if (handler.showNetError) { handler.showNetError = false } if (handler.showNoData) { handler.showNoData = false } if (!handler.showLoading) { val drawable = rootBinding.ivLoading.drawable (drawable as? AnimationDrawable)?.start() handler.showLoading = true } } } /** * 重寫B(tài)aseMvpView中方法,網(wǎng)絡(luò)請求結(jié)束后調(diào)用,隱藏其他界面 */ override fun onNetFinished() { val handler = rootBinding.handler handler?.let { if (handler.showNetError) { handler.showNetError = false } if (handler.showNoData) { handler.showNoData = false } if (handler.showLoading) { val drawable = rootBinding.ivLoading.drawable (drawable as? AnimationDrawable)?.stop() handler.showLoading = false } onListComplete() } } /** * 使用SwipeToLoadView時重寫,完成刷新步驟 */ protected fun onListComplete() {} /** * 標題欄左側(cè)點擊事件,默認結(jié)束當前界面 */ override fun onLeftClick() { finish() } /** * 標題欄右側(cè)點擊事件 */ override fun onRightClick() {} /** * 無數(shù)據(jù)界面點擊事件,默認顯示加載中 */ override fun onNoDataClick() { onLoading() } /** * 網(wǎng)絡(luò)異常界面點擊事件,默認顯示加載中 */ override fun onNetErrorClick() { onLoading() } } -
就這樣完成了 BaseActivity 的封裝,使用起來也很簡單:
/** * 主界面 */ class MainActivity : BaseActivity<BlankPresenter, ActivityMainBinding>() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val mFrags = ArrayList<Fragment>() mFrags.add(MoviesListFragment()) mBinding.vp.adapter = FragVpAdapter.Builder() .manager(supportFragmentManager) .frags(mFrags) .build() } override fun initTitleBar() { showTitle() setTitleStr("高評分電影") } } 詳細代碼大家可以看我的 Github項目:KotlinTest
語法解析
-
在上面的代碼中有幾個簡單的 Kotlin 語法要和大家說明:
// Java 中,如果類聲明有泛型,而在使用時不需要泛型,那么直接不聲明即可 abstract class BaseActivity<P extends BaseMVPPresenter, DB extends ViewDataBinding> {} // 但是在 Kotlin 中必須使用 * 代替 abstract class BaseActivity<P : BaseMVPPresenter<*,*>, DB : ViewDataBinding> {} /* * 在 DataBinding 中,Handler 的類型是可為空的,即 var handler: Handler? * 在 Kotlin 中,調(diào)用可空類型的方法或?qū)傩员仨毷褂冒踩{(diào)用 ?. */ handler?.showTvTitle = true // 等價于 if(null != handler) handler.showTvTitle = true /* * Kotlin 還提供了 let 函數(shù),{}使用了 lambda 表達式, Kotlin 是默認支持的 */ handler?.let { dosomething... } // 等價于 if(null != handler) { dosomething } /* * Kotlin 中的類型轉(zhuǎn)換使用 as 關(guān)鍵字,如果類型錯誤拋出異常 * 同時 Kotlin 也提供了安全轉(zhuǎn)換 as? ,轉(zhuǎn)換成功返回該類型對象,轉(zhuǎn)換失敗返回 null */ (drawable as? AnimationDrawable)?.start() // 等價于 Kotlin if(drawable is AnimationDrawable) { // 判斷是否是該類型 drawable.start() // 使用 is 關(guān)鍵字判斷類型,如果為 true 則自動轉(zhuǎn)換為該類型使用 } // 等價于 Java if(drawable instanceof AnimationDrawable) { ((AnimationDrawable) drawable).start(); } /* * Kotlin 中有智能類型轉(zhuǎn)換,即在變量聲明時就賦值,可以省略聲明時的類型,Kotlin 會根據(jù)賦的值確定變量的類型 */ var str = "" // 自動確定為 String 類型 val handler = mBinding.handler // 自動確定為 Handler 類型
最后
- 到這里,Kotlin 下的項目框架封裝就基本完成了,接下來會給大家?guī)碓敿毜?Kotlin 語法解析,歡迎關(guān)注。
- 有疑問的可以在評論區(qū)提問哦。