乐趣区

关于android:Android极简MVVM从一个基类库谈起

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 无参 初始化数据
setDarkTitle dark: Boolean, color: Int, title: String,1、dark: Boolean,状态栏色彩,true 就是彩色,false 就是红色。2、color: Int,状态栏背景色彩,3、title: String,标题栏内容 设置题目,状态栏背景及色彩
setBarTitle title: String,标题栏内容 设置题目
hintLeftMenu 无参 暗藏左侧按钮
getActionBarView 无参 获取标题栏 View,能够操作标题栏里的任何控件
hintActionBar 无参 暗藏标题栏
translucentWindow dark: Boolean,状态栏色彩,true 就是彩色,false 就是红色 通明状态栏
setEmptyOrError view: 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 也罢,无论应用哪种,适宜的才是最好的。

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

退出移动版