Android MVVM框架
这是我参与「第四届青训营 」笔记创作活动的第5天
介绍
MVVM 模式将代码划分为三个部分:
- View: Activity 和 Layout XML 文件,与 MVP 中 View 的概念相同
- Model: 负责管理业务数据逻辑,如网络请求、数据库处理,与 MVP 中 Model 的概念相同
- ViewModel: 存储视图状态,负责处理表现逻辑,并将数据设置给可观察数据容器
在实现细节上,View 和 Presenter 从双向依赖变成 View 可以向 ViewModel 发指令,但 ViewModel 不会直接向 View 回调,而是让 View 通过观察者的模式去监听数据的变化,有效规避了 MVP 双向依赖的缺点。
实现
IViewModel
定义ViewModel实现的共同方法,可以在这设置加载,错误处理函数
interface IViewModel {
fun showLoading()
fun closeLoading()
}
LifeViewModel
生命周期管理类,可以自己选择加或者不加
open class LifeViewModel : ViewModel(), DefaultLifecycleObserver {
companion object {
private const val TAG = "MyLifeViewModel"
}
override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
Log.d(TAG, "onCreate: ")
}
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
Log.d(TAG, "onStart: ")
}
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
Log.d(TAG, "onResume: ")
}
override fun onPause(owner: LifecycleOwner) {
super.onPause(owner)
Log.d(TAG, "onPause: ")
}
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
Log.d(TAG, "onStop: ")
}
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
Log.d(TAG, "onDestroy: ")
}
}
BaseViewModel
open class BaseViewModel : LifeViewModel(),IViewModel {
var loadingEvent = MutableLiveData<Boolean>()
override fun showLoading() {
loadingEvent.postValue(true)
}
override fun closeLoading() {
loadingEvent.postValue(false)
}
}
LoadingDialog
加载LoadingDialog
class LoadingDialog(context: Context, themeId: Int) : Dialog(context, themeId) {
init {
initView()
}
private fun initView(){
setContentView(R.layout.dialog_loading)
setCanceledOnTouchOutside(true)
val attributes: WindowManager.LayoutParams = window!!.attributes
attributes.alpha = 0.5f
window!!.attributes = attributes
setCancelable(false)
}
}
BaseActivity
abstract class BaseActivity<VDB : ViewDataBinding, VM : BaseViewModel> : AppCompatActivity() {
private var sViewModel: VM? = null
private var sViewDateBinding: VDB? = null
private lateinit var loadingDialog: LoadingDialog
override fun onCreate(savedInstanceState: Bundle?) {
setStatusBarColor(Color.BLACK)
super.onCreate(savedInstanceState)
handlerVDB()
handlerVM()
loadingDialog = getLoadingDialog()
receiveLiveData()
initData(savedInstanceState)
}
private fun handlerVDB() {
sViewDateBinding = DataBindingUtil.setContentView(this, getLayoutId())
sViewDateBinding?.lifecycleOwner = this
}
private fun handlerVM() {
val viewModelClass: Class<BaseViewModel>
val type = javaClass.genericSuperclass
viewModelClass = if (type is ParameterizedType) {
type.actualTypeArguments[1] as Class<BaseViewModel> //获取第1个注解即VM的注解类型
} else {
//使用父类的类型
BaseViewModel::class.java
}
sViewModel = ViewModelProvider(this)[viewModelClass] as VM
if (getVariableId() > 0) {
if (sViewModel != null) {
lifecycle.addObserver(sViewModel!!)
}
sViewDateBinding?.setVariable(getVariableId(), sViewModel)
}
}
abstract fun getLayoutId(): Int
private fun receiveLiveData() {
sViewModel?.loadingEvent?.observe(this) { aBoolean ->
if (aBoolean) {
showLoading()
} else {
dismissLoading()
}
}
}
open fun showLoading() {
if (!loadingDialog.isShowing) {
loadingDialog.show()
}
}
private fun getLoadingDialog(): LoadingDialog =
LoadingDialog(this, R.style.trans_Dialog)
open fun dismissLoading() {
if (loadingDialog.isShowing) {
loadingDialog.dismiss()
}
}
/**
* 初始化ViewModel的id
*
* @return BR的id
*/
abstract fun getVariableId(): Int
/**
* 初始化数据,相当于OnCreate方法
*/
abstract fun initData(savedInstanceState: Bundle?)
/**
* 获取当前Activity的ViewModel
*/
open fun getViewModel(): VM = sViewModel!!
/**
* 获取当前Activity的DataBinding
*/
open fun getDataBinding() : VDB = sViewDateBinding!!
}
BaseFragment
abstract class BaseFragment<VDB : ViewDataBinding, VM : BaseViewModel> : Fragment() {
private var viewModel: VM? = null
private var viewDataBinding: VDB? = null
private lateinit var loadingDialog : LoadingDialog
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewDataBinding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false)
return if (viewDataBinding != null) {
viewDataBinding!!.lifecycleOwner = this
viewDataBinding!!.root
} else inflater.inflate(getLayoutId(), container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
handlerVM()
loadingDialog = LoadingDialog(requireActivity(), R.style.trans_Dialog)
receiveLiveData()
initData(savedInstanceState)
}
private fun handlerVM() {
val viewModelClass: Class<BaseViewModel>
val type = javaClass.genericSuperclass
viewModelClass = if (type is ParameterizedType) {
type.actualTypeArguments[1] as Class<BaseViewModel> //获取第1个注解即VM的注解类型
} else {
//使用父类的类型
BaseViewModel::class.java
}
viewModel = ViewModelProvider(requireActivity())[viewModelClass] as VM //找到Activity对于的VM
if (viewModel == null) {
viewModel = ViewModelProvider(this)[viewModelClass] as VM //fragment自己的VM 不是Activity
}
if (getVariableId() > 0) {
if (viewModel != null) lifecycle.addObserver(viewModel!!)
viewDataBinding?.setVariable(getVariableId(), viewModel)
}
}
private fun receiveLiveData() {
if (viewModel != null){
viewModel!!.loadingEvent.observe(viewLifecycleOwner){ aBoolean ->
if (aBoolean) {
showLoading()
} else {
dismissLoading()
}
}
}
}
abstract fun getLayoutId(): Int
open fun getViewModel(): VM {
return viewModel!!
}
open fun getViewDataBinding(): VDB {
return viewDataBinding!!
}
abstract fun initData(savedInstanceState: Bundle?)
/**
* 初始化ViewModel的id
*
* @return BR的id
*/
abstract fun getVariableId(): Int
open fun showLoading() {
if (!loadingDialog.isShowing) {
loadingDialog.show()
}
}
open fun dismissLoading() {
loadingDialog.dismiss()
}
override fun onDestroy() {
super.onDestroy()
viewDataBinding?.unbind()
viewDataBinding = null
}
}
StateBarUtil
/**
* 设置状态栏颜色
*/
fun Activity.setStatusBarColor(color: Int) {
window.statusBarColor = color
}
/**
* 状态栏反色
*/
fun Activity.setAndroidNativeLightStatusBar() {
val controller = ViewCompat.getWindowInsetsController(window.decorView)
controller?.isAppearanceLightStatusBars = !isDarkMode()
}
/**
* 获取当前是否为深色模式
* 深色模式的值为:0x21
* 浅色模式的值为:0x11
* @return true 为是深色模式 false为不是深色模式
*/
fun Context.isDarkMode(): Boolean {
return resources.configuration.uiMode == 0x21
}
使用
//Fragment同理
class TestActivity : BaseActivity<ActivityTestBinding,TestViewModel>() {
//设置Activity的xml布局
override fun getLayoutId() = R.layout.activity_test
//在xml中设置ViewModel的data,然后在该方法中添加
override fun getVariableId() = BR.friendViewModel
//相当于onCreate
override fun initData(savedInstanceState: Bundle?) {
}
}
class TestViewModel : BaseViewModel(){
//写正常的ViewModel逻辑即可
}
缺点
- 这个框架每个Activity只绑定了一个ViewModel,如果需要用到多个ViewModel,则需要自己改代码或者直接创建
- 没有实现错误处理的捕获,出现错误时依旧会出现闪退现象,需要改进
- 还是具有MVVM的一些缺陷,页面逻辑复杂时,会出现大量的MutableLiveData,并且暴露为不可变的LiveData,所以可以尝试结合Flow来使用