什么是事务场景

这个词在咱们开发作业傍边应该经常会被提起,那到底什么是事务场景呢?我的理解是事务场景便是一种状况对应着页面的一种展现办法,比方某些app上你假如是未登录状况,页面上就会展现一个提示你去登录的提示语,然后再加上一个登录按钮,又比方安全验证方面,有的app会依据用户的风险等级去决议是去密码验证仍是人脸验证,又或许是同时验证,在这些大大小小的事务场景里边,有的完成起来简略,或许只要两三句代码就完成了,有的或许完成起来会比较杂乱,需求三方sdk装备再加上与自己服务端交互才能完成,而当咱们一个独立的页面也牵扯进来了多个事务场景,咱们通常会怎么处理呢?

保守派

这种做法是给每一种事务场景都独立创立个页面,然后底子一切页面元素都差不多,视图逻辑都差不多,唯一在特定场景下完成办法不同,然后在进口的当地依据某种判别条件来决议跳哪一个页面

页面里面业务场景太多看花眼了?要不先分个层试试

长处

这种做法的长处很明显,在每一个Activity里边只需求管理保护各自的对象就好,MilkActivity里边只会有牛奶相关的内容,不会有咖啡的内容出现,假如出来个酒类的需求,我只需求改WineActivity里边的代码就好了

缺陷

假如此时来了一个新需求,需求在一切饮品页面中参加一些用户购买数据,那么我就要在三个页面里边都要加上购买数据的恳求接口,这无疑是做了重复作业,又或许到了后期视觉检验环节,因为三个页面的页面元素底子类似,所以一个ui问题或许要在三个页面里边都要修正,开发效率大大下降

懒汉派

这种做法不会去树立各种页面,而是会在一个页面里边经过传进来的类型,将不同的代码逻辑套在一个个if-else里边,底子每到一处有差异的当地,你就会看到如下代码

页面里面业务场景太多看花眼了?要不先分个层试试

每一个分支里边的代码量有多有少,乃至我见过TabLayout底下只要一个页面,然后运用index来区别不同的页面逻辑,那代码看起来简直是太一言难尽了

长处

这种做法的长处便是一些共有逻辑只需求完成一遍就好,事务不杂乱的情况下,代码仍是能够保留一些可读性的

缺陷

缺乏的当地便是当事务量日趋巨大之后,每个if-else里边的事务代码将变得越来越难保护,耦合度也越来越高,当查看某一处事务逻辑的时分,有必要将代码滑到最上面看看这部分代码归于哪一种事务类型的,然后滑下来持续看代码,十分不方便,别的假如又进来个新的需求,比方多了一个果汁类型的事务,那么每一个事务逻辑部分都需求另加一个if-else分支,只要有一处漏加就会发生问题,风险就比较大了。所以关于这种多事务场景的页面,咱们终究应该怎么来做才能既下降开发与保护成本,也能让功用能够安稳运转呢?

思考怎么规划

透过现象看实质,无论是上面说的保守派仍是懒汉派,发生那样做法的始作俑者无非便是遇到了两个问题,一个是页面元素发生了差异,一个是事务逻辑发生了差异,咱们又想坚持老逻辑能够正常运转,又想让代码能够兼容新的逻辑,已然知道了问题的底子,那么咱们能不能这样做,将一个页面的视图元素与逻辑代码拆分开来,逻辑代码里边也将视图逻辑与数据逻辑也拆分开来,每一部分都供给一个公共层,咱们在公共层代码的基础上,按照事务上的差异,增量的在公共层上增加差异代码

页面里面业务场景太多看花眼了?要不先分个层试试

如上图所示,上半部分的分支便是公共层,黄色板块为基础的视图绑定类与逻辑类,往左边橙色部分为详细完成,再往左灰色板块作为一个事务分层的管理类,负责供给该事务分层的视图绑定类与逻辑类,而下半部分的分支则是包含了一些除了公共层以外其他差异部分的完成,这些都体现在了浅赤色板块里边,最左边绿色板块里边就作为一个署理类,依据传入的不同类型来创立不同的事务分层管理类,这样就能确保在一个页面里边依据执行场景的不同来完成不同的逻辑,以上便是全体规划思路,下面是详细完成进程

创立视图绑定类的基类

abstract class BaseBindView {
    abstract fun bindView(activity: Activity?)
}

这个基类首要供给的服务便是去绑定视图,所以只需求一个bindView函数即可

创立数据逻辑类的基类

abstract class BaseViewModel(application: Application) : AndroidViewModel(application)

创立视图逻辑类的基类

abstract class BaseProxyView {
    abstract fun setActivity(activity:Activity)
    abstract fun setViewModel(viewModel:AndroidViewModel)
}

这个基类供给了逻辑类proxyview所要支撑的服务,现在有一个设置页面上下文的函数,还有一个设置该页面对应的viewmodel的函数,其他有需求的能够往里边加,比方一些Activity的回调办法,能够经过在BaseProxyView增加函数的办法将详细完成放在逻辑类里边

创立公共视图绑定类

open class DrinkBindView(baseProxyView: BaseProxyView) : BaseBindView() {
    var drinkProxyView: DrinkProxyView
    init {
        drinkProxyView = baseProxyView as DrinkProxyView
    }
    override fun bindView(activity: Activity?) {
        drinkProxyView.initProxyView()
    }
}

公共的视图绑定类继承了BaseBindView,重写了bindView函数,在bindView里边针对页面的元素进行初始化,比方findViewbyId,或许Viewbinding,不过要运用ViewBinding的话还要支撑装备不同的binding类,咱们这儿就运用findViewbyId,这边或许有人还注意到了,在绑定类的构造函数中,还传入了BaseProxyView,这样做的意图是一个为了从视图逻辑类那儿供给视图来初始化,究竟视图自身的声明仍是归于视图逻辑那一部分,另一个则是一切的逻辑的开端有必要是在视图元素初始化完今后才进行,所以咱们也看到了在bindView函数的最终一步,增加了真实开端运转逻辑代码的进口initProxyView,这个时分,咱们能够先写个简略的layout文件,当作咱们公共层的默许布局,代码如下

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <TextView
        android:id="@+id/drink_text"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="20dp"
        android:layout_marginStart="20dp"
        android:textColor="@color/black"
        android:textSize="18sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/drink_button"
        app:layout_constraintStart_toStartOf="@+id/drink_text"
        app:layout_constraintTop_toBottomOf="@+id/drink_text"
        android:layout_marginTop="10dp"
        android:text="开端喝"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</androidx.constraintlayout.widget.ConstraintLayout>

这个公共的页面有一个按钮,还有一个用来显现案牍的TextView,这个时分咱们就能够把这个案牍与按钮控件的初始化作业增加到DrinkBindView里边

open class DrinkBindView(baseProxyView: BaseProxyView) : BaseBindView() {
    var drinkProxyView: DrinkProxyView
    init {
        drinkProxyView = baseProxyView as DrinkProxyView
    }
    override fun bindView(activity: Activity?) {
        drinkProxyView.apply {
            drinkText = activity?.findViewById(R.id.drink_text)
            drinkBtn = activity?.findViewById(R.id.drink_button)
            initProxyView()
        }
    }
}

创立公共视图逻辑类

open class DrinkProxyView : BaseProxyView() {
    var context:Activity?=null
    var drinkText: TextView? = null
    var drinkBtn:Button?=null
    var mViewModel:DrinkViewModel?=null
    open fun initProxyView() {
        initDrinkText()
        initDrinkButton()
    }
    override fun setActivity(activity: Activity) {
        context = activity
    }
    override fun setViewModel(viewModel: AndroidViewModel) {
        this.mViewModel = viewModel as DrinkViewModel
    }
    /**
     * 初始化案牍
     */
    open fun initDrinkText() {
    }
    /**
     * 初始化按钮
     */
    open fun initDrinkButton(){
    }
}

这个视图逻辑类是一些公共的完成,可供其他逻辑类来继承运用或许定制自己有差异的逻辑,在这个视图逻辑类里边,做的作业首要是声明视图,初始化上下文,初始化viewmodel,以及针对每个视图开发详细事务逻辑,这儿建议每一个视图的事务逻辑都能够保护在一个独立的函数中,而且函数是open的,这样做的意图是当有差异性的事务需求来的时分,只需求重写有差异的视图对应的函数即可,不必将其他无关代码逻辑仿制后再去完成一遍。

界说署理服务

咱们现在现已完成了绑定类与逻辑类的代码,那么接下来便是咱们的署理类,去署理现已被拆分好的绑定类,视图逻辑类以及数据逻辑类供页面运用,代码如下

interface IDrink {
    fun getBindView(proxyView: BaseProxyView?): BaseBindView?
    fun getProxyView(): BaseProxyView?
    fun getViewModel(): BaseViewModel?
}

创立署理类

class Drink(mDrink:IDrink):IDrink {
    private var drink:IDrink? = null
    init {
        drink = mDrink
    }
    override fun getBindView(proxyView: BaseProxyView?): BaseBindView? {
        return drink?.getBindView(proxyView)
    }
    override fun getProxyView(): BaseProxyView? {
        return drink?.getProxyView()
    }
    override fun getViewModel(): BaseViewModel? {
        return drink?.getViewModel()
    }
}

这个署理类的作用便是依据不同的事务传参来生成对应的署理Drink类,现在先创立一个公共署理完成类

class BaseDrinkImpl : IDrink {
    override fun getBindView(proxyView: BaseProxyView?): BaseBindView? {
        return DrinkBindView(proxyView!!)
    }
    override fun getProxyView(): BaseProxyView? {
        return DrinkProxyView()
    }
    override fun getViewModel(): BaseViewModel? {
        return ViewModelProvider.AndroidViewModelFactory(MainApplication.application!!)
            .create(DrinkViewModel::class.java)
    }
}

当没有新的事务到来之前,咱们的页面都是默许走的这个完成类,最终在Activity里边的代码装备如下

class DrinkActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_drink)
        val drink = Drink(BaseDrinkImpl())
        val proxyView: BaseProxyView = drink.getProxyView()!!
        proxyView.setActivity(this)
        proxyView.setViewModel(drink.getViewModel()!!)
        with( DrinkBindView(proxyView)){
            bindView(this@DrinkActivity)
        }
    }
}

这儿先以BaseDrinkImpl作为参数来获取咱们的署理类Drink,然后经过Drink获取页面所需求的绑定类与逻辑类,最终经过bindView函数完成视图初始化以及敞开事务逻辑代码,这样咱们的一个全体架构就现已出来了,现在就能测验下用这个架构来开发需求了

实践一下

开发公共事务逻辑

咱们公共层有必要要有一套完好的事务完成,比方这儿的事务逻辑便是点击按钮后,开端计时,榜首秒案牍是喝了1杯饮料,第二秒案牍是喝了2杯饮料以此类推,逻辑很简略,首先便是在viewmodel里边写上这个守时器的代码

open class DrinkViewModel(application: Application) : BaseViewModel(application) {
    val _stateFlow by lazy { MutableStateFlow("") }
    val stateFlow = _stateFlow.asStateFlow()
    open fun beginTodrink(){
        var cup = 0
       viewModelScope.launch {
           flow {
               while (true){
                   delay(1000)
                   emit(cup++)
               }
           }.collect{
               _stateFlow.value = "喝了${it}杯饮料"
           }
       }
    }
}

然后在咱们的视图逻辑类DrinkProxyView里边写上视图相关的代码,这儿案牍与按钮别离都做了工作,案牍是显现从数据逻辑类那儿传来的数据,而按钮是点击敞开这个守时操作,所以咱们需求将这两件工作别离写在对应的initDrinkTextinitDrinkButton函数里边,代码如下

/**
 * 初始化案牍
 */
open fun initDrinkText() {
    MainScope().launch {
        mViewModel?.stateFlow?.collect{
            drinkText?.text = it
        }
    }
}
/**
 * 初始化按钮
 */
open fun initDrinkButton(){
    drinkBtn?.apply {
        setOnClickListener {
            mViewModel?.beginTodrink()
        }
    }
}

这样咱们这个需求就现已完成了,运转后的作用便是下面这样

页面里面业务场景太多看花眼了?要不先分个层试试

扩展事务–视图逻辑与数据逻辑存在差异

现在比方咱们又来了一个需求,有一个页面上有两个按钮,别离是悉数,牛奶,需求要求点击悉数按钮,跳转到咱们原来的DrinkActivity完成老逻辑,点击牛奶按钮,跳转到另一个页面,与DrinkActivity比较类似,可是按钮案牍变成了开端喝牛奶,案牍也是一秒中显现一次,可是变成了喝了xx杯牛奶,而且案牍颜色变成了赤色,该怎么做呢,是新建一个MilkActivity仍是在DrinkActivity里边加上if-else的逻辑判别,都不是,还记得之前那个联系图吗,当有新的差异需求来的时分,咱们只需求针对逻辑上的差异创立对应的署理类与逻辑类就好了,因为这儿咱们视图上的元素没有变化,仍然仍是按钮与案牍,所以绑定类暂时不必别的创立,仍是用的公共层的视图绑定类,至于逻辑类,首先咱们要考虑一点,点击牛奶按钮显现的页面中,显现的案牍数据是跟公共层有差异的,公共层里边,DrinkViewModel里边发送的案牍是固定的喝了${it}杯饮料,可是在牛奶事务的页面里边需求要求显现的案牍是喝了${it}杯牛奶,那么咱们能够在新建的MilkViewModel里边,重写beginTodrink函数,然后将DrinkViewModel里边的代码仿制过去,更改下发送的数据就好了,像这样

class MilkViewModel(application: Application) : DrinkViewModel(application) {
    override fun beginTodrink() {
        var cup = 0
        viewModelScope.launch {
            flow {
                while (true){
                    delay(1000)
                    emit(cup++)
                }
            }.collect{
                _stateFlow.value = "喝了${it}杯牛奶"
            }
        }
    }
}

尽管逻辑上没什么问题,可是还能够更简化一点,因为咱们这边的差异只是是体现在发送的数据上,关于整个beginTodrink函数的功用来说,并没有什么不同,所以咱们应该把定制的范围缩小一点,将发送的数据独自拿出来对外供给一个函数,先更改下公共层的数据逻辑类的代码

open class DrinkViewModel(application: Application) : BaseViewModel(application) {
    val _stateFlow by lazy { MutableStateFlow("") }
    val stateFlow = _stateFlow.asStateFlow()
    open fun beginTodrink(){
        var cup = 0
       viewModelScope.launch {
           flow {
               while (true){
                   delay(1000)
                   emit(cup++)
               }
           }.collect{
               _stateFlow.value = drinkData(it)
           }
       }
    }
    open fun drinkData(num:Int):String{
        return "喝了${num}杯饮料"
    }
}

新增了一个函数drinkData,这个函数专门用来界说发送的数据内容,这样的话咱们在MilkViewModel里边只需求重写drinkData函数,更改一下它的返回值就好了,其他逻辑仍然仍是由公共的DrinkViewModel来完成

class MilkViewModel(application: Application) : DrinkViewModel(application) {
    override fun drinkData(num: Int): String {
        return "喝了${num}杯牛奶"
    }
}

像这样完成,不管是从代码量上,仍是逻辑上咱们都能够明晰的看到,牛奶事务里边在数据上的差异体现在了发送的数据上,其他的不变,这样的做法放在咱们平时的开发里边,就能够运用在像接口恳求上,比方有个新的页面,底子与现有的某个页面相同,可是在调用某个接口上,新的页面需求传的参数或许关于返回值的处理不相同,那么咱们就能够经过这种办法,只是将传参的进程或许处理返回值的进程封装起来并暴露出去,以最小的作业量和代码量来完成这个需求,既不必重复造轮子,后期改问题也能够快速定位到详细事务代码里边,全体结构也明晰了不少。咱们言归正传持续开发这个需求,在这个需求里除了发送数据上的差异,在视图上也有两处差异,别离是按钮案牍不同以及展现案牍的色值不同,已然是视图逻辑上的差异,那么咱们就需求再创立一个MilkProxyView,而且重写按钮与案牍的逻辑函数

class MilkProxyView: DrinkProxyView() {
    override fun initDrinkText() {
        super.initDrinkText()
        drinkText?.setTextColor(Color.RED)
    }
    override fun initDrinkButton() {
        super.initDrinkButton()
        drinkBtn?.text = "开端喝牛奶"
    }
}

咱们看到无论是initDrinkText仍是initDrinkButton,都别离先调用了父类函数办法,然后才是各自完成对应的逻辑,这个表明保留默许完成,然后在这个基础上再做开发,假如咱们的牛奶事务里边比方点击按钮现已不是敞开倒计时了,而是去恳求个接口,拿下发的数据展现在案牍上,那么能够不需求调用super.initDrinkButton()这一行代码,彻底在MilkProxyView里边从头对按钮完成新的逻辑,这样也就达到了相同的页面元素,在不同的事务场景完成不同的逻辑的作用,现在数据逻辑以及视图逻辑都开发完成,就差运用署理类将它们放到页面中去了,这儿再创立一个MilkImpl的类,完成IDrink接口,在对应重写函数中创立对应的逻辑类实例

class MilkImpl: IDrink {
    override fun getBindView(proxyView: BaseProxyView?): BaseBindView? {
        return DrinkBindView(proxyView!!)
    }
    override fun getProxyView(): BaseProxyView? {
        return MilkProxyView()
    }
    override fun getViewModel(): BaseViewModel? {
        return ViewModelProvider.AndroidViewModelFactory(MainApplication.application!!)
            .create(MilkViewModel::class.java)
    }
}

最终一步在Activity中经过intent传进来的参数,判别该选择运用哪一个事务署理类,代码完成如下

class DrinkActivity : AppCompatActivity() {
    lateinit var drink: Drink
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_drink)
        val type = intent.getStringExtra("type")
        drink = when(type){
            "milk"->{
                Drink(MilkImpl())
            }
            else ->{
                Drink(BaseDrinkImpl())
            }
        }
        val proxyView: BaseProxyView = drink.getProxyView()!!
        proxyView.setActivity(this)
        proxyView.setViewModel(drink.getViewModel()!!)
        with( DrinkBindView(proxyView)){
            bindView(this@DrinkActivity)
        }
    }
}

进口的代码就不展现了,无非便是不同按钮点击后传不同的参数到Activity的进程,咱们直接看下运转作用

页面里面业务场景太多看花眼了?要不先分个层试试

现在咱们现已完成了在一个页面里边,事务逻辑零耦合的完成了两种不相同的事务需求,但这个时分或许有些小伙伴要来吐槽了,这个是不是太理想化了,咱们哪次需求变化,界面上不得多点元素少点元素啊,那么咱们再新增个需求,试试看在页面里边假如多个视图元素又该怎么做?

扩展事务–视图元素差异与定制差异元素逻辑

咱们再新增一个咖啡事务需求,要求现在有一组图片,需求每秒钟展现一张,图片下方需求展现一段文字,描述当时是第几张图片,然后再加一个按钮用来敞开这个使命,全体剖析下来,按钮与显现案牍的控件都能够运用公共层的视图,可是这儿又多出来一个ImageView,这个是公共层没有的,需求在咖啡事务里边定制,那么咱们的布局文件也不能用公共层的了,得先创立个咖啡事务的布局文件

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <ImageView
        android:id="@+id/coffee_image"
        android:src="@mipmap/coffee1"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="20dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    <TextView
        android:id="@+id/coffee_text"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/coffee_image"
        android:layout_marginTop="10dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <Button
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/coffee_text"
        android:layout_marginTop="20dp"
        android:text="更换图片开端"
        android:id="@+id/coffee_button"
        android:layout_width="match_parent"
        android:layout_height="50dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

这个布局文件里边与之前的布局文件存在着两个差异,一个是id都不相同了,一个是多了个ImageView,因为咱们这儿的ButtonTextView做的工作同公共层里边的相同,所以能够直接将coffee_textcoffee_button这两个id绑定至公共层页面里边的drinkBtndrinkText,这样咱们就能够运用公共页面里边按钮与文字的功用了,再新建个CoffeeBindView,在里边做绑定视图的工作,代码如下所示

class CoffeeBindView(baseProxyView: BaseProxyView) : DrinkBindView(baseProxyView) {
    var coffeeProxyView:CoffeeProxyView
    init {
        coffeeProxyView = baseProxyView as CoffeeProxyView
    }
    override fun bindView(activity: Activity?) {
        drinkProxyView.drinkText = activity?.findViewById(R.id.coffee_text)
        drinkProxyView.drinkBtn = activity?.findViewById(R.id.coffee_button)
        coffeeProxyView.imageView = activity?.findViewById(R.id.coffee_image)
        coffeeProxyView.initProxyView()
    }
}

咱们看到按钮与案牍直接运用的是DrinkProxyView里边的变量,只要图片用的是CoffeeProxyView里边的imageView,这儿边就体现出了咖啡事务里边新增的视图只要一个imageview,差异逻辑也只是在imageview上做,可是除此之外,咱们刚刚是不是新建了一个layout文件,可是在公共层的DrinkActivity里边,貌似setcontentView里边是固定运用的默许layout文件,所以这部分咱们也需求让它可装备的,怎么配呢?就在BaseProxyView里边新增一个设置布局文件的函数

abstract class BaseProxyView {
    abstract fun setActivity(activity:Activity)
    abstract fun setViewModel(viewModel:AndroidViewModel)
    /**
     * 设置不同事务场景的布局文件
     */
    abstract fun layoutId():Int
}

新增了一个layoutId函数用来设置布局文件的id,然后在公共层的DrinkProxyView里边经过重写这个函数,将默许的布局文件设置进去

open class DrinkProxyView : BaseProxyView() {
    var context:Activity?=null
    var drinkText: TextView? = null
    var drinkBtn:Button?=null
    var mViewModel:DrinkViewModel?=null
    open fun initProxyView() {
        initDrinkText()
        initDrinkButton()
    }
    override fun setActivity(activity: Activity) {
        context = activity
    }
    override fun setViewModel(viewModel: AndroidViewModel) {
        this.mViewModel = viewModel as DrinkViewModel
    }
    override fun layoutId(): Int {
        return R.layout.activity_drink
    }
}

这样设置今后,假如其他事务的布局结构同公共层的布局结构类似,那么不需求在对应的视图逻辑类里边重写layoutId函数,就会运用DrinkProxyView默许设置的布局文件,可是比方像咱们这个咖啡事务里边布局结构存在差异了,就能够把新建的布局文件设置到咖啡事务的视图逻辑类里边

class CoffeeProxyView : DrinkProxyView() {
    var imageView: ImageView? = null
    override fun layoutId(): Int {
        return R.layout.activity_coffee
    }
 }

最终再将layoutId装备到DrinkActivity里边就完成了装备不同事务层的布局文件的功用

val proxyView: BaseProxyView = drink.getProxyView()!!
setContentView(proxyView.layoutId())

现在来看下怎么完成公共层没有的切换图片的功用,首先从网上随便下几张咖啡相关的图片

页面里面业务场景太多看花眼了?要不先分个层试试

然后在CoffeeProxyView加上切换图片的逻辑代码

class CoffeeProxyView : DrinkProxyView() {
    var imageView: ImageView? = null
    val imageList = listOf(
        R.mipmap.coffee1, R.mipmap.coffee2, R.mipmap.coffee3,
        R.mipmap.coffee4, R.mipmap.coffee5, R.mipmap.coffee6
    )
    var index = 0
    override fun layoutId(): Int {
        return R.layout.activity_coffee
    }
    override fun initProxyView() {
        super.initProxyView()
        initImageView()
    }
    private fun initImageView() {
        MainScope().launch {
            mViewModel?.stateFlow?.collect {
                imageView?.setImageResource(imageList[index])
                index++
                if (index > 5) index = 0
            }
        }
    }
}

这儿咱们看到了重写了公共层的initProxyView函数,这么做的首要原因是initProxyView是一切视图元素逻辑初始化的进口方位,因为咱们咖啡事务里边新增切换图片事务,所以也有必要将图片相关初始化逻辑也放在initProxyView中进行,而super.initProxyView()这行代码也是有必要存在的,因为只要加了这行代码,才能够运用公共层里边按钮的点击功用,不然的话咱们只能在CoffeeProxyView里边新界说个按钮再增加点击事情,这样一来功用就与公共层重复了,不符合规划初衷,别的需求里边还说到,需求有个案牍展现当时是展现的第几张图片,但因为公共层里边案牍是展现的数据逻辑类里边StateFlow发出来的数据,所以这一部分需求重写initDrinkText函数,从头给drinkText设置案牍

override fun initDrinkText() {
    MainScope().launch {
        mViewModel?.stateFlow?.collect {
            drinkText?.text = "当时显现的是第${index+1}张图片"
        }
    }
}

到这儿咱们咖啡事务的视图逻辑类现已完成了,老规矩咱们创立CoffeeImpl用来供给CoffeeBindViewCoffeeProxyView

class CoffeeImpl: IDrink {
    override fun getBindView(proxyView: BaseProxyView?): BaseBindView? {
        return CoffeeBindView(proxyView!!)
    }
    override fun getProxyView(): BaseProxyView? {
        return CoffeeProxyView()
    }
    override fun getViewModel(): BaseViewModel? {
        return ViewModelProvider.AndroidViewModelFactory(MainApplication.application!!)
            .create(DrinkViewModel::class.java)
    }
}

因为在咖啡事务里边数据逻辑没变化,所以这部分仍然运用公共层的DrinkViewModel,然后咱们去Activity里边将CoffeeImpl也装备进去

class DrinkActivity : AppCompatActivity() {
    lateinit var drink: Drink
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val type = intent.getStringExtra("type")
        drink = when (type) {
            "milk" -> Drink(MilkImpl())
            "coffee" -> Drink(CoffeeImpl())
            else -> Drink(BaseDrinkImpl())
        }
        val proxyView: BaseProxyView = drink.getProxyView()!!
        setContentView(proxyView.layoutId())
        proxyView.setActivity(this)
        proxyView.setViewModel(drink.getViewModel()!!)
        with(when(type){
            "coffee" -> CoffeeBindView(proxyView)
            else -> DrinkBindView(proxyView)
        }){
            bindView(this@DrinkActivity)
        }
    }
}

在Activity里边依据传进来的type来生成不同的署理类Drink,别的因为咖啡事务的布局也不同,关于了不同的绑定类,所以当typecoffee的时分,绑定类运用的是CoffeeBindView,其他情况仍然仍是运用默许的DrinkBindView,现在来运转下看看咖啡事务逻辑完成的怎么样

页面里面业务场景太多看花眼了?要不先分个层试试

总结

相信假如文章能看到这儿的小伙伴必定会对这种拆分事务逻辑的思想有了必定的概念了,这种思想的实质也便是将页面傍边繁琐的事务经过剖析整理,将这些杂乱的逻辑代码分成事务公共层与事务定制层,在应对新事务或许保护老事务的时分,只需求判别事务的实质是处理数据仍是视图,是公共逻辑仍是定制逻辑,然后去对应的方位做修正即可。