Android框架的历史演变

记得最开始入门Android的时候,还未流行MVP,都是MVC一把梭,后面工作了就是使用了MVP,当时学习的时候好难理解它的回调。

到目前主流的MVVM,其实就是MVP的升级版,再到最新的MVI使用意图传输,隔离各层级的直接调用。我算是经历了Android框架变迁的全过程。

这里记录一下各框架的简单Demo用例。

一. MVC框架

经典MVC分为:

Model 模型层 : 数据和网络

View 视图层 : 视图的展示

Controller 控制层 : 逻辑控制,调用模型驱动视图

一般我们是把一个xml看作一个View层, Activity看作一个Control层 , Model层则是由相关的数据操作类。

Model层:

class OtherModel : BaseRepository() {
    /**
     * 使用扩展方法,请求网络
     */
    suspend inline fun getIndustry(): OkResult<List<Industry>> {
        return extRequestHttp {
            DemoRetrofit.apiService.getIndustry(
                Constants.NETWORK_CONTENT_TYPE,
                Constants.NETWORK_ACCEPT_V1
            )
        }
    }
}

Controller层:

class MVCActivity : AbsActivity() {
    private val mOtherModel: OtherModel by lazy { OtherModel() }
    override fun setContentView() {
        setContentView(R.layout.activity_demo14_1)
    }
    override fun init() {
        val btnGetData = findViewById<Button>(R.id.btn_get_data)
        btnGetData.click {
            requestIndustry()
        }
    }
    private fun requestIndustry() {
        //MVC中Activity就是Controller,直接调用接口,获取数据之后直接操作xml控件刷新
        lifecycleScope.launch {
            //开始Loading
            LoadingDialogManager.get().showLoading(this@MVCActivity)
            val result = mOtherModel.getIndustry()
            result.checkSuccess {
                //处理成功的信息
                toast("list:$it")
                //doSth...
            }
            LoadingDialogManager.get().dismissLoading()
        }
    }
}

XML就是View层,获取到信息展示到XML中。

这样分工其实也是很明确的,但是一旦逻辑过多,会导致Activity太臃肿。Activcity中又是Model又是View,耦合性太强,记得那时候一个Activity中上千行代码都是平平常常。

为了解决这个问题,大家开始使用MVP架构。

二. MVP框架

MVP框架的出现,各个模块权责分明,各干各的活,降低了耦合,减少Activity的臃肿。

Model层:还是MVC那个Model。
View层:接口定义由Activity实,用于操作相应的UI。
Presenter层:用于Model和View的桥梁,负责Model与View的交互

那更复杂的一点的,就是其中加入Contract契约类,把指定页面的Presenter和View等关联起来,方便维护。

View接口定义

interface IDemoView {
    fun showLoading()
    fun hideLoading()
    fun getIndustrySuccess(list: List<Industry>?)
    fun getIndustryFailed(msg: String?)
}

Presenter的实现:

class DemoPresenter(private val view: IDemoView) {
    private val mOtherModel: OtherModel by lazy { OtherModel() }
    //获取行业数据
    fun requestIndustry(lifecycleScope: LifecycleCoroutineScope) {
        lifecycleScope.launch {
            //开始Loading
            view.showLoading()
            val result = mOtherModel.getIndustry()
            result.checkResult({
                //处理成功的信息
                toast("list:$it")
                view.getIndustrySuccess(it)
            }, {
                //失败
                view.getIndustryFailed(it)
            })
            view.hideLoading()
        }
    }
}

Activity的实现:

class MVPActivity : AbsActivity(), IDemoView {
    private lateinit var mPresenter: DemoPresenter
    override fun setContentView() {
        setContentView(R.layout.activity_demo14_1)
    }
    override fun init() {
        //创建Presenter
        mPresenter = DemoPresenter(this)
        val btnGetData = findViewById<Button>(R.id.btn_get_data)
        btnGetData.click {
            //通过Presenter调用接口
            mPresenter.requestIndustry(lifecycleScope)
        }
    }
    //回调再次触发
    override fun showLoading() {
        LoadingDialogManager.get().showLoading(this)
    }
    override fun hideLoading() {
        LoadingDialogManager.get().dismissLoading()
    }
    override fun getIndustrySuccess(list: List<Industry>?) {
        //popupIndustryData
    }
    override fun getIndustryFailed(msg: String?) {
        //showErrorMessage
    }
}

当时MVP框架是火遍一时,当时面试要不会这个,那都不好意思说是做安卓的。

虽然它有一些缺点,比如太复杂,每次都要写重复的View,修改麻烦,回调地狱,数据交互体验不佳,无法感知生命周期,重建页面无法自动恢复数据,耦合还是有很多,等等。但是在当时没有替代品的选择下,它是当之无愧的王。

但是当谷歌出了Jetpack,当ViewModel LiveData Lifecycles的出现给了我们新的选择 MVVM框架开始出现并迅猛发展。

三. MVVM框架

这里先说一点有争议的点。 有些人认为,只要用上ViewModel LiveData这些就算MVVM框架 Model View ViewModel嘛。 有些人认为,MVVM的意思是数据驱动,最大的亮点是数据绑定,使用DataBinding的才算MVVM。 其实这个也没有官方的定义,世上本无框架,用的人多了才出现框架名字,约定俗成的东西,你想怎么定义就怎么定义,那我姑且称为前者为半MVVM后者为MVVM吧

3.1 半MVVM框架

其实可以理解为MVP的升级版,去掉了View的接口回调,保存了ViewModel的特性

Model层:还是MVC那个Model。
View层:Activity,用于操作相应的UI。
ViewModel:还是MVP那个Presenter,只是用ViewModel实现。

ViewModel实现: 可以看到代码确实相比MVP少了很多

class DemoViewModel @ViewModelInject constructor(
    private val mRepository: Demo5Repository,
    @Assisted val savedState: SavedStateHandle
) : BaseViewModel() {
    val liveData = MutableLiveData<List<Industry>?>()
    //获取行业数据
    fun requestIndustry() {
        viewModelScope.launch {
            //开始Loading
            loadStartLoading()
            val result = mRepository.getIndustry()
            result.checkResult({
                //处理成功的信息
                toast("list:$it")
                liveData.value = it
            }, {
                //失败
                liveData.value = null
            })
            loadHideProgress()
        }
    }
}

Activity的实现:

@AndroidEntryPoint
class MVVMActivity : BaseVMActivity<DemoViewModel>() {
    override fun getLayoutIdRes(): Int = R.layout.activity_demo14_1
    override fun init() {
        //自动注入ViewModel,调用接口通过LiveData回调
        mViewModel.requestIndustry()
    }
    override fun startObserve() {
        //获取到网络数据之后改变xml对应的值
        mViewModel.liveData.observe(this) {
            it?.let {
                // popopIndustryData
            }
        }
    }
}

3.2 带DataBinding的MVVM框架

特别是现在kotlin那种直接拿id使用的插件已经被官方标记为过时,还不赶紧用DataBinding或ViewBinding?

ViewModel实现:

class DemoViewModel @ViewModelInject constructor(
    private val mRepository: Demo5Repository,
    @Assisted val savedState: SavedStateHandle
) : BaseViewModel() {
    val liveData = MutableLiveData<List<Industry>?>()
    //获取行业数据
    fun requestIndustry() {
        viewModelScope.launch {
            //开始Loading
            loadStartLoading()
            val result = mRepository.getIndustry()
            result.checkResult({
                //处理成功的信息
                toast("list:$it")
                liveData.value = it
            }, {
                //失败
                liveData.value = null
            })
            loadHideProgress()
        }
    }
}

Repository的实现:其实和Model差不多的意思,数据仓库而已,下面的一些注解是用到了Hilt依赖注入,不用直接new对象也是可以的,不要在意一些细节。

@Singleton
class Demo5Repository @Inject constructor() : BaseRepository() {
    suspend inline fun getIndustry(): OkResult<List<Industry>> {
        return extRequestHttp {
            DemoRetrofit.apiService.getIndustry(
                Constants.NETWORK_CONTENT_TYPE,
                Constants.NETWORK_ACCEPT_V1
            )
        }
    }
}

Activity的实现: 内部做了一些基类的封装,事件处理封装为对象,viewmodel和事件对象在xml中做了引用

@AndroidEntryPoint
class MVVM2Activity : BaseVDBActivity<DemoViewModel, ActivityDemo142Binding>() {
    private val clickProxy: ClickProxy by lazy { ClickProxy() }
    override fun getDataBindingConfig(): DataBindingConfig {
        return DataBindingConfig(R.layout.activity_demo14_2, BR.viewModel, mViewModel)
            .addBindingParams(BR.click, clickProxy)
    }
    override fun init() {
    }
    override fun startObserve() {
    }
    /**
     * DataBinding事件处理
     */
    inner class ClickProxy {
        fun getData() {
            //MVVM直接调用网络请求,结果在xml中自动显示
            mViewModel.requestIndustry()
        }
    }
}

Xml的实现: 注意引用指向的包名要写对,写对了可以直接跳转过去的。

<?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:binding="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
            name="viewModel"
            type="com.guadou.kt_demo.demo.demo14_mvi.mvvm1.DemoViewModel" />
        <variable
            name="click"
            type="com.guadou.kt_demo.demo.demo14_mvi.mvvm2.MVVM2Activity.ClickProxy" />
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/picture_color_blue"
        android:orientation="vertical">
        <com.guadou.lib_baselib.view.titlebar.StatusbarGrayView
            android:id="@ id/status_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="获取数据"
            binding:clicks="@{click.getData}" />
        <TextView
            android:id="@ id/tv_message"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{viewModel.liveData.toString()}" />
    </LinearLayout>
</layout>

这样就完成了一个基于数据驱动的DataBinding的MVVM。 如果上面一些代码如果看不太明白,后面我可能会出DataBinding的封装并开源。

截止到发稿日期为止,目前市面上最流行的还是MVVM框架,此框架唯一的槽点可能就是Databinding的不好调试吧,一旦出问题,有时候报错信息莫名其妙的,没有指向XML中某个数据或语法的错误,需要对DataBinding有一定的了解。 不过AS现在貌似越来越智能了,报错信息都还指向蛮清晰的。MVVM完全可用的。

四. MVI框架

由于是出来没多久,具体是不是叫MVI框架这个名字还不确定,大家都这么叫,姑且就叫MVI吧。伴随Compose出现的框架,主流用于Compose应用。

MVI框架是由Model View Intent组成的。可以算上MVVM的升级版,在之前我们都是通过在Activity中直接调用ViewModel的方法,现在改为发出操作指令,由ViewModel解析指令,调用对应的方法,回调给Activity。

比如一个DemoActivity需要获取行业数据,学校数据,等。那么就可以把数据和操作都封装成指定的对象。

    //当前页面所需的数据与状态
    data class Demo14ViewState(
        val industrys: List<Industry> = emptyList(),
        val schools: List<SchoolBean> = emptyList(),
        var isChanged: Boolean = false
    ) : BaseViewState()
    //当前页面需要的事件定义
    sealed class DemoAction {
        object RequestIndustry : DemoAction()
        object RequestSchool : DemoAction()
        object RequestAllData : DemoAction()
        data class UpdateChanged(val isChange: Boolean) : DemoAction()
    }

Activity调用相关的接口就不是直接调用ViewModel的方法,而是:

   override fun init() {
        //发送Intent指令,具体的实现由ViewModel实现
        mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestAllData)
    }

那么ViewModel就需要解析指令:

    //Action分发入口
    fun dispatch(action: DemoAction) {
        when (action) {
            is DemoAction.RequestIndustry -> requestIndustry()
            is DemoAction.RequestSchool -> requestSchool()
            is DemoAction.RequestAllData -> getTotalData()
            is DemoAction.UpdateChanged -> changeData(action.isChange)
        }
    }
    //获取行业数据
    private fun requestIndustry() {
      //xxx
    }

完整的代码如下:

ViewModel的实现:

class Damo14ViewModel @ViewModelInject constructor(
    private val mRepository: Demo5Repository,
    @Assisted val savedState: SavedStateHandle
) : BaseViewModel() {
    private val _viewStates: MutableLiveData<Demo14ViewState> = MutableLiveData(Demo14ViewState())
    //只需要暴露一个LiveData,包括页面所有状态
    val viewStates: LiveData<Demo14ViewState> = _viewStates
    //Action分发入口
    fun dispatch(action: DemoAction) {
        when (action) {
            is DemoAction.RequestIndustry -> requestIndustry()
            is DemoAction.RequestSchool -> requestSchool()
            is DemoAction.RequestAllData -> getTotalData()
            is DemoAction.UpdateChanged -> changeData(action.isChange)
        }
    }
    //获取行业数据
    private fun requestIndustry() {
        viewModelScope.launch {
            //开始Loading
            loadStartLoading()
            val result = mRepository.getIndustry()
            result.checkSuccess {
                _viewStates.setState {
                    copy(industrys = it ?: emptyList())
                }
            }
            loadHideProgress()
        }
    }
    //获取学校数据
    private fun requestSchool() {
        viewModelScope.launch {
            //开始Loading
            loadStartLoading()
            val result = mRepository.getSchool()
            result.checkSuccess {
                _viewStates.setState {
                    copy(schools = it ?: emptyList())
                }
            }
            loadHideProgress()
        }
    }
    //获取全部数据
    private fun getTotalData() {
        //默认执行在主线程的协程-必须用(可选择默认执行在IO线程的协程)
        launchOnUI {
            //开始Loading
            loadStartProgress()
            val industryResult = async {
                mRepository.getIndustry()
            }
            val schoolResult = async {
                mRepository.getSchool()
            }
            //一起处理数据
            val industry = industryResult.await()
            val school = schoolResult.await()
            //如果都成功了才一起返回
            if (industry is OkResult.Success && school is OkResult.Success) {
                loadHideProgress()
                //设置多种LiveData
                _viewStates.setState {
                    copy(industrys = industry.data ?: emptyList(), schools = school.data ?: emptyList())
                }
            }
        }
    }
    //改变状态
    private fun changeData(isChanged: Boolean) {
        _viewStates.setState {
            copy(isChanged = isChanged)
        }
    }
    //当前页面所需的数据与状态
    data class Demo14ViewState(
        val industrys: List<Industry> = emptyList(),
        val schools: List<SchoolBean> = emptyList(),
        var isChanged: Boolean = false
    ) : BaseViewState()
    //如果想再度封装,也可以把回调的结果封装成类似Action的对象,由页面判断回调的是哪一种类型,进行相关的操作
    //这样就不需要使用LiveData回调了,LiveData就只是作为保存数据的功能,由DemoEvent回调
//    sealed class DemoEvent {
//        object PopBack : DemoEvent()
//        data class ErrorMessage(val message: String) : DemoEvent()
//    }
    //当前页面需要的事件定义
    sealed class DemoAction {
        object RequestIndustry : DemoAction()
        object RequestSchool : DemoAction()
        object RequestAllData : DemoAction()
        data class UpdateChanged(val isChange: Boolean) : DemoAction()
    }
}

Activity的实现:

@AndroidEntryPoint
class Demo14Activity : BaseVDBActivity<Damo14ViewModel, ActivityDemo14Binding>() {
    private val clickProxy: ClickProxy by lazy { ClickProxy() }
    companion object {
        fun startInstance() {
            commContext().let {
                it.startActivity(Intent(it, Demo14Activity::class.java).apply {
                    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                })
            }
        }
    }
    override fun getDataBindingConfig(): DataBindingConfig {
        return DataBindingConfig(R.layout.activity_demo14)
            .addBindingParams(BR.click, clickProxy)
    }
    @SuppressLint("SetTextI18n")
    override fun startObserve() {
        //监听两者数据变化
        mViewModel.viewStates.observeState(
            this,
            Damo14ViewModel.Demo14ViewState::industrys,
            Damo14ViewModel.Demo14ViewState::schools
        ) { industry, school ->
            YYLogUtils.w("industry: $industry ; school: $school")
        }
        //只监听changed的变换
        mViewModel.viewStates.observeState(this, Damo14ViewModel.Demo14ViewState::isChanged) {
            if (it) {
                val industry = mViewModel.viewStates.value?.industrys
                val school = mViewModel.viewStates.value?.schools
                mBinding.tvMessage.text = "industry: $industry ; school: $school"
            }
        }
    }
    override fun init() {
        //发送Intent指令,具体的实现由ViewModel实现
        mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestAllData)
    }
    /**
     * DataBinding事件处理
     */
    inner class ClickProxy {
        fun getData() {
            //发送Intent指令,具体的实现由ViewModel实现
//            mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestIndustry)
//            mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestSchool)
            mViewModel.dispatch(Damo14ViewModel.DemoAction.UpdateChanged(true))
        }
    }
}

注意,有些MVI的写法是回调给Activity的方式也是用对象封装如我注释的代码:

    //如果想再度封装,也可以把回调的结果封装成类似Action的对象,由页面判断回调的是哪一种类型,进行相关的操作
    //这样就不需要使用LiveData回调了,LiveData就只是作为保存数据的功能,由DemoEvent回调
//    sealed class DemoEvent {
//        object PopBack : DemoEvent()
//        data class ErrorMessage(val message: String) : DemoEvent()
//    }

也可以使用LiveData返回,我这里使用扩展方法observeState方法来监听,这样可以保证只有你监听的对象发生了变化才会收到回调。这个扩展方法在MVVM框架也能使用。

扩展方法如下:

import androidx.lifecycle.*
import kotlin.reflect.KProperty1
/**
 * @auther Newki
 * @date 2022/2/10
 * @description LiveData的扩展 支持MVI模式 订阅单个LiveData实现监听不同的操作与数据
 */
//监听一个属性
fun <T, A> LiveData<T>.observeState(
    lifecycleOwner: LifecycleOwner,
    prop1: KProperty1<T, A>,
    action: (A) -> Unit
) {
    this.map {
        StateTuple1(prop1.get(it))
    }.distinctUntilChanged().observe(lifecycleOwner) { (a) ->
        action.invoke(a)
    }
}
//监听两个属性
fun <T, A, B> LiveData<T>.observeState(
    lifecycleOwner: LifecycleOwner,
    prop1: KProperty1<T, A>,
    prop2: KProperty1<T, B>,
    action: (A, B) -> Unit
) {
    this.map {
        StateTuple2(prop1.get(it), prop2.get(it))
    }.distinctUntilChanged().observe(lifecycleOwner) { (a, b) ->
        action.invoke(a, b)
    }
}
//监听三个属性
fun <T, A, B, C> LiveData<T>.observeState(
    lifecycleOwner: LifecycleOwner,
    prop1: KProperty1<T, A>,
    prop2: KProperty1<T, B>,
    prop3: KProperty1<T, C>,
    action: (A, B, C) -> Unit
) {
    this.map {
        StateTuple3(prop1.get(it), prop2.get(it), prop3.get(it))
    }.distinctUntilChanged().observe(lifecycleOwner) { (a, b, c) ->
        action.invoke(a, b, c)
    }
}
internal data class StateTuple1<A>(val a: A)
internal data class StateTuple2<A, B>(val a: A, val b: B)
internal data class StateTuple3<A, B, C>(val a: A, val b: B, val c: C)
//更新State
fun <T> MutableLiveData<T>.setState(reducer: T.() -> T) {
    this.value = this.value?.reducer()
}

太干了,一张图都没上,最后总结一下:

世界上本无框架,用的人多了就成了框架,适合自己的才是好的。不是一定说出了最新框架我就要用最新的框架,理解之后再使用才能得心应手。

个人目前平时开发中用的也是MVVM框架。后期会出一些MVVM的封装和用法开源。

以上就是Android开发框架MVC-MVP-MVVM-MVI的演变Demo的详细内容,更多关于Android框架MVC MVP MVVM MVI的资料请关注Devmax其它相关文章!

Android开发框架MVC-MVP-MVVM-MVI的演变Demo的更多相关文章

  1. html5 canvas合成海报所遇问题及解决方案总结

    这篇文章主要介绍了html5 canvas合成海报所遇问题及解决方案总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Html5 video标签视频的最佳实践

    这篇文章主要介绍了Html5 video标签视频的最佳实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  3. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  4. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  5. Ionic – Splash Screen适用于iOS,但不适用于Android

    我有一个离子应用程序,其中使用CLI命令离子资源生成的启动画面和图标iOS版本与正在渲染的启动画面完美配合,但在Android版本中,只有在加载应用程序时才会显示白屏.我检查了config.xml文件,所有路径看起来都是正确的,生成的图像出现在相应的文件夹中.(我使用了splash.psd模板来生成它们.我错过了什么?这是config.xml文件供参考,我觉得我在这里做错了–解决方法在config.xml中添加以下键:它对我有用!

  6. ios – 无法启动iPhone模拟器

    /Library/Developer/CoreSimulator/Devices/530A44CB-5978-4926-9E91-E9DBD5BFB105/data/Containers/Bundle/Application/07612A5C-659D-4C04-ACD3-D211D2830E17/ProductName.app/ProductName然后,如果您在Xcode构建设置中选择标准体系结构并再次构建和运行,则会产生以下结果:dyld:lazysymbolbindingFailed:Symbol

  7. Xamarin iOS图像在Grid内部重叠

    heyo,所以在Xamarin我有一个使用并在其中包含一对,所有这些都包含在内.这在Xamarin.Android中看起来完全没问题,但是在Xamarin.iOS中,图像与标签重叠.我不确定它的区别是什么–为什么它在Xamarin.Android中看起来不错但在iOS中它的全部都不稳定?

  8. 在iOS上向后播放HTML5视频

    我试图在iPad上反向播放HTML5视频.HTML5元素包括一个名为playbackRate的属性,它允许以更快或更慢的速率或相反的方式播放视频.根据Apple’sdocumentation,iOS不支持此属性.通过每秒多次设置currentTime属性,可以反复播放,而无需使用playbackRate.这种方法适用于桌面Safari,但似乎在iOS设备上的搜索限制为每秒1次更新–在我的情况下太慢了.有没有办法在iOS设备上向后播放HTML5视频?解决方法iOS6Safari现在支持playbackRat

  9. Swift教程17-淡化MVC,使用MVVM框架开发轻巧便于维护的iOS/android app

    MVVM是微软提出一种移动开发框架,旨在针对传统的MVC框架,解决传统的MVC框架的控制器的臃肿问题.M:Model模型,也就是数据模型;比如一条微博,对应的所有字段合成一条微博整体,这个整体就是ModelV:View视图,只用来显示的视图,如iOS的UIView,Cell;当然在iOS中Storyboard中,view总是和控制器关联,这并不是严格的view如果我们纯手写代码定义一个view那么

  10. Swift 2.0 下面向协议的MVVM架构实践

    本文由CocoaChina译者lynulzy翻译原文:Swift2.0:Protocol-OrientedMVVM自从令人兴奋的[《面向协议的编程方法》]在Swift的WWDC大会上发布以来。我已经在之前的博客中使用过MVVM架构,如果你想了解更多MVVM相关知识请参考[这里]。接下来我将讲解,如何添加面向协议。switchToggle.onTintColor=switchColorself.onSwitchToggleHandler=onSwitchToggleHandler虽然在这种情况下看起来并不是

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部