Hello啊各位老铁,明天带来一个陈词滥调的技术,MVVM,这篇文章,次要具体介绍如何封装一个MVVM的基类库,以及MVVM架构模式在理论业务中的用法,最初会把理论的封装代码开源,并提供近程依赖,不便给到大家应用以及二次批改,尽量做到细致入微,浅显易懂,OK,废话不多赘述,咱们进入注释。

这篇文章大略会依照以下几个模块进行论述,此次封装,做到绝无第三方依赖,都是Android原生的代码封装,请放心使用,如果您想间接进行应用,请间接跳到第4步,集成应用即可,此次的封装,和目前支流的MVVM架构模式,会完满符合,让架构模式简单化,让业务代码清晰化,必须值得举荐应用。

一、MVVM简略概括

二、基于MVVM模式如何封装基类库

三、实战封装

四、封装后在业务中如何应用

五、开源以及Demo查看

舒适提醒:内容稍多,请合理安排好工夫,如果不想查阅具体封装过程,底部有开源地址,能够间接查看。

一、MVVM简略概括

MVVM的开发模式,相对来说低耦合,业务之间逻辑显得也非常明显,Model层负责将申请的数据交给ViewModel层;ViewModel层负责将申请到的数据做业务逻辑解决,最初交给View层去展现,与View一一对应;View层只负责界面绘制刷新,不解决业务逻辑,非常适合进行独立模块开发。

三层简略概括

1、Model:数据层,蕴含数据实体和对数据实体的操作。

2、View:视图层,对应于Activity,XML,View,负责数据显示以及用户交互。

3、ViewModel:关联层,将Model和View进行绑定,Model或者View更改时,实时刷新对方。

须要留神:

1、View只做和UI相干的工作,不波及任何业务逻辑,不波及操作数据,不解决数据,也就是UI和数据是严格离开的。

2、ViewModel只做和业务逻辑相干的工作,不波及任何和UI相干的操作,不持有控件援用,不更新UI。

二、基于MVVM模式如何封装基类库

MVVM咱们曾经清晰,然而针对现有的三层,咱们如何进行拆解封装呢?面对这样的一个问题,咱们也是须要从三层以及和理论的业务进行相结合,从理论业务中来,也要从理论业务中去,这是咱们封装的一个潜在因素,一旦脱离了理论,封装的再优良,也只是一个花瓶,中看不中用。

针对MVVM中的三层,其实,咱们在封装中,也是基于这三层,View,ViewModel和Model。View中,在理论的开发中,个别针对Activity和Fragment进行零碎的抽取封装,ViewModel个别会抽取一个父类,做一些公共的办法或属性配置,Model层个别封装的较少,依据理论业务,须要具体问题具体分析。

Activity和Fragment的封装思路,其实是统一的,须要以简略和简单两种方向进行抽取,一种是简略的页面继承应用,一种是简单的页面继承应用,这样辨别的一个目标,就是,专职专用,防止大材小用,而具体的封装,除了使得代码简洁化,更重要的拓展化,不便子类的调用。

在具体封装的时候,与理论业务相结合,这个无比重要,比方理论的大部分页面,都带有一个标题栏,那么标题栏就能够间接封装父类外面,像子类拓展出,更改题目,右侧按钮,左侧按钮等功能属性;除了对立的标题栏,另外就是子类的视图了,对于子类的视图传递,这个是必须的,能够间接形象出一个必须要实现的办法,其余的,比方状态栏的扭转,缺省页的设置等等,也须要在父类中对立的给出。

简单的页面是基于简略的页面而来的,这里的简单,个别是蕴含很多逻辑的解决,那么,咱们就能够减少ViewModel层和Model层了,目前基于DataBinding的实现形式,无论简略和简单,都是必须须要思考的,也就是说在父类中,咱们就须要向子类提供出能够拿到的databinding和viewmodel,个别以泛型的形式引入,这样子类再继承的时候,就能够很不便的进行调用。

在简单的页面,也就是蕴含ViewModel层和Model层的时候,须要思考绑定视图variable的传递,也就是以后的ViewModel和那个xml进行绑定,当然这是在须要的时候,必须要操作的,除了视图绑定,常见的,数据申请状态,比方申请胜利,申请失败,缺省页显示和暗藏,Dialog的显示和暗藏,LiveData的数据回传等等,在简单的页面中也是须要咱们思考的,除此之外,ViewModel中如何和View层的生命周期绑定,在理论的业务中也是不得不须要思考的。

除了以上的惯例思考,在理论的业务中,比方事件消息传递,PagerAdapter应用,状态栏通明等很多和基类的相干的性能,咱们其实也能够进行封装进去,便于子类的调用。

三、实战封装

通过第2条中的拆解和具体的封装思路,无妨咱们进行实战一下,因为Activity和Fragment的封装思路以及相干属性和办法,大部分都是雷同的,所以目前只介绍Activity,更具体的封装,还请大家参考源码。

1、Activity的简略封装

简略封装,不携带ViewModel,只传递ViewDataBinding,子类必须重写的办法只有一个initData,其余均为选择性重写,如果绝对逻辑比较简单的页面,能够继承此类。

一个很简略的一般封装,就是把共有的常见的,封装到父类里,便于子类的调用,具体什么办法,什么逻辑进行采取封装,须要咱们依据具体业务或者公司的相干状况而定,以下是源码。

abstract class BaseActivity<VB : ViewDataBinding>(@LayoutRes layoutId: Int = 0) :    AppCompatActivity(layoutId) {      private var mActionBarView: ActionBarView? = null    private var mLayoutError: LinearLayout? = null    private var mLayoutId = layoutId    lateinit var mBinding: VB      override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        try {            //默认状态栏为白底黑字            darkMode(BaseConfig.statusBarDarkMode)            statusBarColor(ContextCompat.getColor(this, BaseConfig.statusBarColor))            setContentView(R.layout.activity_base)            val baseChild = findViewById<LinearLayout>(R.id.layout_base_child)            mLayoutError = findViewById(R.id.layout_empty_or_error)            mActionBarView = findViewById(R.id.action_bar)            if (mLayoutId == 0) {                mLayoutId = getLayoutId()            }              if (savedInstanceState != null && getIntercept()) {                noEmptyBundle()                return            }              val childView = layoutInflater.inflate(mLayoutId, null)            baseChild.addView(childView)            mBinding = DataBindingUtil.bind(childView)!!              initView()            initData()        } catch (e: Exception) {            e.printStackTrace()            noEmptyBundle()        }    }      /**     * AUTHOR:AbnerMing     * INTRODUCE:获取视图id     */    open fun getLayoutId(): Int {        return 0    }      open fun initView() {}      /**     * AUTHOR:AbnerMing     * INTRODUCE:初始化数据     */    abstract fun initData()        /**     * AUTHOR:AbnerMing     * INTRODUCE:动静扭转状态栏色彩和题目     */    fun setDarkTitle(dark: Boolean, color: Int, title: String) {        try {            darkMode(dark)            statusBarColor(ContextCompat.getColor(this, color))            setBarTitle(title)          } catch (e: Exception) {            e.printStackTrace()        }      }      /**     * AUTHOR:AbnerMing     * INTRODUCE:设置题目     */    fun setBarTitle(title: String) {        mActionBarView!!.visibility = View.VISIBLE        mActionBarView!!.setBarTitle(title)    }        /**     * AUTHOR:AbnerMing     * INTRODUCE:暗藏左侧按钮     */      fun hintLeftMenu() {        mActionBarView!!.hintLeftBack()    }        /**     * AUTHOR:AbnerMing     * INTRODUCE:获取ActionBarView     */    fun getActionBarView(): ActionBarView {        return mActionBarView!!    }      /**     * AUTHOR:AbnerMing     * INTRODUCE:暗藏标题栏     */    fun hintActionBar() {        mActionBarView?.visibility = View.GONE    }      /**     * AUTHOR:AbnerMing     * INTRODUCE:Bundle为空进行拦挡,解决扭转权限后重回App解体问题     */    open fun getIntercept(): Boolean {        return false    }      /**     * AUTHOR:AbnerMing     * INTRODUCE:Bundle为空时的逻辑解决,解决扭转权限后重回App解体问题     */    open fun noEmptyBundle() {}        override fun onDestroy() {        super.onDestroy()        try {            LiveDataBus.removeObserve(this)            LiveDataBus.removeStickyObserver(this)        } catch (e: Exception) {            e.printStackTrace()        }    }        /**     * AUTHOR:AbnerMing     * INTRODUCE:通明状态栏     */    fun translucentWindow(dark: Boolean) {        try {            immersive(0, dark)        } catch (e: Exception) {            e.printStackTrace()        }      }      /**     * AUTHOR:AbnerMing     * INTRODUCE:设置缺省页     */    fun setEmptyOrError(view: View) {        mLayoutError?.visibility = View.VISIBLE        mLayoutError?.removeAllViews()        mLayoutError?.addView(view)    }      /**     * AUTHOR:AbnerMing     * INTRODUCE:暗藏     */    fun hintEmptyOrErrorView() {        mLayoutError?.visibility = View.GONE    }      /**     * AUTHOR:AbnerMing     * INTRODUCE:获取谬误或为空的view     */    fun getEmptyOrErrorView(): LinearLayout {        return mLayoutError!!    }}

波及的办法概述

办法名参数概述
getLayoutId无参子类传递的layout,用于加载视图,能够通过构造方法传递,也能够通过此办法传递。
initView无参初始化View,非必须重写
initData无参初始化数据
setDarkTitledark: Boolean, color: Int, title: String,1、dark: Boolean,状态栏色彩,true就是彩色,false就是红色。2、color: Int,状态栏背景色彩,3、title: String,标题栏内容设置题目,状态栏背景及色彩
setBarTitletitle: String,标题栏内容设置题目
hintLeftMenu无参暗藏左侧按钮
getActionBarView无参获取标题栏View,能够操作标题栏里的任何控件
hintActionBar无参暗藏标题栏
translucentWindowdark: Boolean,状态栏色彩,true就是彩色,false就是红色通明状态栏
setEmptyOrErrorview: View,传递的缺省View视图设置缺省视图
hintEmptyOrErrorView无参暗藏缺省视图
getEmptyOrErrorView无参获取缺省视图

简略的Activity没有什么好说的,都是中规中矩,具体的应用请大家看第四条,具体应用即可。

2、Activity的简单封装

也谈不上简单,只是在继承简略页面的根底之上多加了一个ViewModel,绝对于比较复杂的页面,就能够继承此类,此类,拓展了ViewModel,能够在ViewModel里进行逻辑的书写,此类也是MVVM的规范执行,V继承于BaseVMActivity,VM继承于BaseViewModel,至于M,能够在VM中通过getRepository办法进行获取。

具体代码逻辑如下:

BaseVMActivity继承于BaseActivity。

abstract class BaseVMActivity<VB : ViewDataBinding, BM : BaseViewModel>(@LayoutRes layoutId: Int = 0) :    BaseActivity<VB>(layoutId) {      lateinit var mViewModel: BM      override fun initData() {        mViewModel = getViewModel()!!        val variableId = getVariableId()        if (variableId != -1) {            mBinding.setVariable(getVariableId(), mViewModel)            mBinding.executePendingBindings()        }        initVMData()        observeLiveData()        initState()        lifecycle.addObserver(mViewModel)    }      /**     * AUTHOR:AbnerMing     * INTRODUCE:获取绑定的xml id     */    open fun getVariableId(): Int {        return -1    }      /**     * AUTHOR:AbnerMing     * INTRODUCE:初始化状态     */    private fun initState() {        mViewModel.mStateViewLiveData.observe(this, {            when (it) {                StateLayoutEnum.DIALOG_LOADING -> {                    dialogLoading()                }                StateLayoutEnum.DIALOGD_DISMISS -> {                    dialogDismiss()                }                StateLayoutEnum.DATA_ERROR -> {                    dataError()                }                StateLayoutEnum.DATA_NULL -> {                    dataEmpty()                }                StateLayoutEnum.NET_ERROR -> {                    netError()                }                StateLayoutEnum.HIDE -> {                    hide()                }            }        })    }      /**     * AUTHOR:AbnerMing     * INTRODUCE:初始化数据     */    abstract fun initVMData()      /**     * AUTHOR:AbnerMing     * INTRODUCE:LiveData的Observer     */    open fun observeLiveData() {      }      /**     * AUTHOR:AbnerMing     * INTRODUCE:dialog加载     */    open fun dialogLoading() {}      /**     * AUTHOR:AbnerMing     * INTRODUCE:dialog暗藏     */    open fun dialogDismiss() {}      /**     * AUTHOR:AbnerMing     * INTRODUCE:数据谬误     */    open fun dataError() {}      /**     * AUTHOR:AbnerMing     * INTRODUCE:数据为空     */    open fun dataEmpty() {}      /**     * AUTHOR:AbnerMing     * INTRODUCE:网络谬误或申请谬误     */    open fun netError() {}      /**     * AUTHOR:AbnerMing     * INTRODUCE:暗藏某些布局或者缺省页等     */    open fun hide() {}      private fun getViewModel(): BM? {        //这里取得到的是类的泛型的类型        val type = javaClass.genericSuperclass        if (type != null && type is ParameterizedType) {            val actualTypeArguments = type.actualTypeArguments            val tClass = actualTypeArguments[1]            return ViewModelProvider(                this,                ViewModelProvider.AndroidViewModelFactory.getInstance(application)            )                .get(tClass as Class<BM>)        }        return null    } override fun onDestroy() {    super.onDestroy()    try {        lifecycle.removeObserver(mViewModel)    } catch (e: Exception) {        e.printStackTrace()    }} }

封装波及的办法概述

办法名参数概述
getVariableId无参获取绑定的xml variable,也就是以后的xml和哪个对象进行绑定,用于xml里间接数据绑定
initVMData无参初始化数据,必须要实现的办法
observeLiveData无参LiveData的Observer,UI层监听ViewModel层的数据扭转
dialogLoading无参dialog加载
dialogDismiss无参dialog暗藏
dataError无参数据谬误
dataEmpty无参数据为空
netError无参数据谬误
hide无参暗藏缺省页等其余页面

BaseViewModel

BaseViewModel绝对比较简单,只提供了一个能够获取Repository的办法,还有一个是刷新UI视图的一个LiveData,就是数据申请,Dialog加载,缺省页加载的状态。更改状态,只须要调用changeStateView办法即可,子类能够重写生命周期办法,便于生命周期的思考。

 open class BaseViewModel : ViewModel() , BaseObserver{    /**     * 管制状态视图的LiveData     */    val mStateViewLiveData = MutableLiveData<StateLayoutEnum>()      /**     * 更改状态视图的状态     */    public fun changeStateView(        state: StateLayoutEnum    ) {        // 对参数进行校验        when (state) {            StateLayoutEnum.DIALOG_LOADING -> {                mStateViewLiveData.postValue(StateLayoutEnum.DIALOG_LOADING)            }            StateLayoutEnum.DIALOGD_DISMISS -> {                mStateViewLiveData.postValue(StateLayoutEnum.DIALOGD_DISMISS)            }            StateLayoutEnum.DATA_ERROR -> {                mStateViewLiveData.postValue(StateLayoutEnum.DATA_ERROR)            }            StateLayoutEnum.DATA_NULL -> {                mStateViewLiveData.postValue(StateLayoutEnum.DATA_NULL)            }            StateLayoutEnum.NET_ERROR -> {                mStateViewLiveData.postValue(StateLayoutEnum.NET_ERROR)            }            StateLayoutEnum.HIDE -> {                mStateViewLiveData.postValue(StateLayoutEnum.HIDE)            }        }      }        /**     * AUTHOR:AbnerMing     * INTRODUCE:获取Repository     */    inline fun <reified R> getRepository(): R? {        try {            val clazz = R::class.java            return clazz.newInstance()        } catch (e: Exception) {            e.printStackTrace()        }        return null    } /** * AUTHOR:AbnerMing * INTRODUCE:生命周期初始化 */override fun onCreate() {} /** * AUTHOR:AbnerMing * INTRODUCE:生命周期页面可见 */override fun onStart() {} /** * AUTHOR:AbnerMing * INTRODUCE:生命周期页面获取焦点 */override fun onResume() {} /** * AUTHOR:AbnerMing * INTRODUCE:生命周期页面失去焦点 */override fun onPause() { } /** * AUTHOR:AbnerMing * INTRODUCE:生命周期页面不可见 */override fun onStop() {    } /** * AUTHOR:AbnerMing * INTRODUCE:生命周期页面销毁 */override fun onDestroy() {} }

简单的Activity,大家能够发现,其实就是规范的MVVM模式封装,Fragment的封装也是基于此,搞清楚上述,基本上咱们这个基类库就实现了大半,的确也没什么好说的,大家间接看应用吧。

四、封装后在业务中如何应用

通过以上的封装,咱们在业务层所有的页面就能够继承父类进行应用,以达到代码的高度对立,使得架构模式简单化,让业务代码清晰化,目前的封装,大家能够间接封装成库或者打成aar给到其余开发者应用,目前我曾经上传到近程,不想麻烦的老铁,能够间接依照上面的步骤进行应用。

1、在你的根我的项目下的build.gradle文件下,引入maven。

 allprojects {    repositories {      maven { url "https://gitee.com/AbnerAndroid/almighty/raw/master" }    }}

2、在你须要应用的Module中build.gradle文件下,引入依赖。

 dependencies {  implementation 'com.vip:base:1.0.2'}

通过以上的Maven仓库依赖,咱们就能够欢快的进行应用了,上面针对各个封装的性能进行一个简略的演示,当然,大家能够间接看源码中的实例,那里绝对比拟全面。

1、一般的Activity的继承

如果,你的Activity页面逻辑比较简单,倡议继承BaseActivity,此父类,没有与ViewModel相结合,只蕴含失常且简略的逻辑解决,目前必须重写的只有一个initData办法,其余办法,大家能够依据业务重写即可。

class MainActivity : BaseActivity<ActivityMainBinding>(R.layout.activity_main) {    /**     * AUTHOR:AbnerMing     * INTRODUCE:初始化数据    */    override fun initData() {        setBarTitle("主页")    }}

2、ViewModel模式Activity的继承

View层,须要继承BaseVMActivity

class TestViewModelActivity : BaseVMActivity<ActivityViewModelBinding,        TestViewModel>(R.layout.activity_view_model) {      override fun initVMData() {        setBarTitle("ViewModel形式应用")    } }

ViewModel层,须要继承BaseViewModel

理论的业务中,遇到网络申请,缺省页展现,Dialog显示暗藏,调用changeStateView办法,UI层只须要重写对应的办法即可。

 class TestViewModel : BaseViewModel() {        /**     * AUTHOR:AbnerMing     * INTRODUCE:获取须要的Repository     */    private val repository by lazy {        getRepository<TestRepository>()    } }

Model层,个别依据理论须要,进行具体的封装应用。

 class TestRepository { }

3、DataBinding模式应用

View层,继承BaseVMActivity,返回以后视图的绑定variable

 class DataBindActivity :    BaseVMActivity<ActivityDataBindBinding,            DataBindViewModel>(R.layout.activity_data_bind) {      override fun initVMData() {        setBarTitle("DataBinding应用")    }      override fun getVariableId(): Int {        return BR.data    }}

ViewModel层继承BaseViewModel

 class DataBindViewModel : BaseViewModel() {      var oneWayContent = "单向绑定数据测试"      var twoWayContent = "双向绑定数据测试"      /**     * AUTHOR:AbnerMing     * INTRODUCE:获取双向绑定数据     */    var clickListener = View.OnClickListener {          Toast.makeText(it.context, twoWayContent, Toast.LENGTH_SHORT).show()    }}

XML视图,间接绑定

 <layout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto">      <data>          <variable            name="data"            type="com.abner.base.bind.DataBindViewModel" />    </data>      <LinearLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        android:orientation="vertical"        android:paddingLeft="@dimen/gwm_dp_20"        android:paddingRight="@dimen/gwm_dp_20">          <TextView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_gravity="center"            android:layout_marginTop="@dimen/gwm_dp_20"            android:text="@{data.oneWayContent}" />          <EditText            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:layout_marginTop="@dimen/gwm_dp_20"            android:hint="双向绑定"            android:text="@={data.twoWayContent}" />          <Button            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_gravity="center"            android:layout_marginTop="@dimen/gwm_dp_20"            android:onClick="@{data.clickListener}"            android:text="获取双向绑定数据" />      </LinearLayout></layout>

4、Fragment的简略应用

如果,你的Fragment页面逻辑比较简单,倡议继承BaseFragment,此父类,没有与ViewModel相结合,只蕴含失常且简略的逻辑解决,目前必须重写的只有一个initData办法,其余办法,大家能够依据业务重写即可。

class TestPagerFragment : BaseFragment    <FragmentTestPagerBinding>(R.layout.fragment_test_pager) {      override fun initData() {      }}

5、ViewModel模式Fragment的继承

View层,须要继承BaseVMFragment

class TestViewModelPagerFragment :    BaseVMFragment<FragmentTestPagerBinding,            TestFragmentViewModel>(R.layout.fragment_test_pager) {        override fun initVMData() {     }  }

ViewModel层,须要继承BaseViewModel

class TestFragmentViewModel :BaseViewModel(){          /**     * AUTHOR:AbnerMing     * INTRODUCE:获取须要的Repository     */    private val repository by lazy {        getRepository<TestRepository>()    } }

Model层,个别依据理论须要,进行具体的封装应用。

 class TestRepository { }

6、Fragment的DataBinding模式应用和Activity相似,就不赘述了。

7、事件音讯总线应用

一般事件发送

    LiveDataBus.send("send", "我发送了一条一般音讯")

一般发送事件接管

 LiveDataBus.observe(this, "send", Observer<String> {            Toast.makeText(this, it, Toast.LENGTH_SHORT).show()        })

粘性事件发送

 LiveDataBus.sendSticky("sendSticky", "我发送了一条粘性事件音讯")

粘性事件接管

   LiveDataBus.observeSticky(this, "sendSticky", Observer<String> {            Toast.makeText(this, it, Toast.LENGTH_SHORT).show()        })

更多的其余性能应用,大家间接看Github即可,上边有比拟清晰的介绍。

五、开源以及Demo查看

以上的封装,目前曾经开源,大家能够下载查看源码,或者进行二次更改应用,地址是:

https://github.com/AbnerMing888/VipBase

相干Demo,大家能够down下我的项目,运行即可,这里简略贴张效果图:

目前的封装,没有过多的冗余代码,齐全能够满足理论的业务须要,大家能够依照这种模式试验一番,遇到问题,能够多多交换,毕竟,技术是凋谢的,交换中能力一直的提高,当然了,须要联合本人的理论业务进行应用,毕竟我的项目中不应存在多个架构模式,MVC也好,MVP,MVVM,MVI也罢,无论应用哪种,适宜的才是最好的。

好了,各位老铁,这篇文章就到这里,下篇文章《组件化开发,从未如此简略》正在撰写中,大家敬请期待!