一個基於Android MVVM的架構🐖
以下文章,我會把本架構稱爲 Capybara
放下Github鏈接先https://github.com/Ubitar/MVVMCapybara
Capybara 使用databinding + fragmentation
搭建,僅包含Activity
及Fragment
等基礎組件等功能,
可能有些人喜歡用 navigation
,架構有分層,大家可以自己fork下來刪減定製。
前言
在閱讀 Capybara 前,我會默認大家都看過databinding、fragmentation、LiveData
的使用方法,及kotlin的使用,kt真香。
不過我希望先閱讀一下下面鏈接裏的大佬文章,加深對MVVM和數據驅動的瞭解。
https://www.zhihu.com/question/30976423/answer/106134677
https://www.jianshu.com/p/1fcda521fcda 禁止在layout中寫複雜邏輯
當然,如果你做過Vue或者微信小程序那更好理解了,即時那時你會嫌棄安卓的MVVM,或者充滿黑人問號
結構簡述
Capybara 主要通過讓組件繼承IView 、IViewModel 、IModel
這3個接口來實現的。
View 層可以爲你的Activity、Fragment或者DialogFragment
Model 層爲爲你的業務提供網絡請求服務或者數據庫讀存服務
ViewModel 中間層 則是負責處理你的業務邏輯,從Model中獲取數據進行處理,並對View進行更新的
圖中BaseMvvMActivity
(我寫錯成了BseMvvMActivtivty了)、BaseViewModel
和BaseModel
爲MVVM的實現抽象類,在不同的生命週期中實現並調用了MVVM接口。
而BaseMvvMFragment
也類似有同樣的實現,只不過因其生命週期,調用方法的位置有些不同,具體內容需要大家去瀏覽源碼。
如果你想支持例如Popup
之類的組件,也可以瞭解架構的大體走向後通過實現上方所提及的3個接口進行實現,當然,前提是你的這類popup
組件得有一個說得過去的生命週期。
食用方法
1、新建一個佈局文件,裏面就只有一個按鈕
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModelName"
type="com.example.example.demo1.Demo1ViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:orientation="vertical">
<Button
android:id="@+id/btn1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text='@{"點擊這個按鈕 "+viewModelName.count}'
android:textAllCaps="false"
app:onClick="@{viewModelName.onClickBtn1}"
tools:text="點擊這個按鈕" />
</LinearLayout>
</layout>
2、新建一個ViewModel
,繼承自BaseActivityViewModel
**注意:**這個ViewModel是繼承自ActivityViewModel的,因爲我們接下來要創建的是其相應的Activity
,如果你要創建的是Fragment
,那麼你需要繼承的則是BaseFragmentViewModel
,同理,Dialog
是BaseDialogViewModel
class Demo1ViewModel(application: Application) : BaseActivityViewModel<BaseModel>(application) {
val count = MutableLiveData<Int>(0)
/** 這個是 MVVM 中的 Model層,如沒有網絡或數據庫需求,傳NUll即可 */
override fun getModel(): Class<BaseModel>? = null
//用於初始化RecyclerView adapter的事件監聽
override fun initEvent() {
super.initEvent()
}
//初始化數據或者獲取數據
override fun initData() {
super.initData()
}
fun onClickBtn1(view: View) {
count.value = count.value!! + 1
}
}
3、新建你的Activity
,繼承 BaseActivity
並實現getLayoutId()
和getViewModelId()
提供佈局Id和ViewModel在佈局中的變量名稱
class Demo1Activity: BaseActivity<ActivityDemo1Binding, Demo1ViewModel>() {
/** 你的佈局Id **/
override fun getLayoutId(inflater: LayoutInflater, savedInstanceState: Bundle?): Int = R.layout.activity_demo1
/** 佈局中ViewModel的Name */
override fun getViewModelId(): Int =BR.viewModelName
/**
* 下方函數運行順序(從上往下順序運行)
*
* initParams()
* initViewModelParams()
* initView()
* onBindObservable()
* ViewModel.initEvent()
* ViewModel.initData()
*
*/
//用於接收並處理從上一個界面傳遞過來的數據
override fun initParams() {
super.initParams()
}
//如果有需要,可以通過這個函數把初始數據傳到ViewModel
override fun initViewModelParams() {
super.initViewModelParams()
viewModel.count.value = 100
}
// 初始化RecyclerView或者其他View
override fun initView() {
super.initView()
}
// 註冊ViewModel中變量值的改變,可用於ViewModel向View傳遞信息或操作
override fun onBindObservable() {
super.onBindObservable()
}
}
4、或許你的項目裏會有讀取數據庫或者網絡的時候,那麼你就需要實現相應的Model層,編寫一個Model層文件
class Demo3Model(
private val viewModel: Demo3ViewModel
) : BaseModel(viewModel) {
fun toLogin(account: String, password: String) {
//模擬登錄
Flowable.timer(1000, TimeUnit.MILLISECONDS)
.doOnSubscribe { ToastUtils.showShort("加載中") }
//這個是AutoDispose 如果要Rxlifecycle你要去想辦法自己更換
// .`as`(AutoDisposeUtil.fromOnDestroy(viewModel.lifecycle.get()!!))
.subscribe({
viewModel.afterLoginSuccess()
}, {
println(it)
}).isDisposed
}
override fun onCleared() {
super.onCleared()
}
}
在ViewModel處編寫getModel()
函數的返回值,同時別忘了修改類上方的泛型
class Demo3ViewModel(application: Application) : BaseActivityViewModel<Demo3Model>(application) {
//改這裏,還有上方的泛型
override fun getModel(): Class<Demo3Model>? = Demo3Model::class.java
var account = MutableLiveData<String>("")
var password = MutableLiveData<String>("")
fun onClickLogin(view: View) {
// model是從父類繼承的Model層的引用
model.toLogin(account.value ?: "", password.value ?: "")
}
fun afterLoginSuccess() {
ToastUtils.showShort("登陸成功")
}
}
關於fragmentation
在這個架構上的應用可以查看源碼demo4
常見問題
1、這架構膠水代碼好多
額。。。。這個我也想解決,但是奈何我還不會寫插件一鍵生成
2、MVVM好用不
這個問題就很刁鑽了,MVVM和MVP一個自底向上,一個自頂向下,寫法完全相反,而且MVVM並不是android原生就有的,稍微複雜點的邏輯不能寫在xml(或者說太多if條件),xml報錯的時候也一臉懵逼,只能一個一個註釋排查,你想從不是MVVM架構的項目中複製代碼?對不起,大部分你都要重寫。
但是好處還是有的,感觸最深的還是改了只改變一個變量就顯示隱藏了多個View,其次是@BindingAdapter
對UI操作的封裝,其他的還要自己去體會。