前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android基于DataBinding+Koin实现MVVM模式页面快速开发框架

Android基于DataBinding+Koin实现MVVM模式页面快速开发框架

作者头像
loongwind
发布2022-09-27 11:58:56
1.5K0
发布2022-09-27 11:58:56
举报
文章被收录于专栏:loongwind

1. 前言

上一篇介绍了 ardf(android rapid development framework,Android 快速开发框架) 基于 DataBinding 对 RecyclerView 的封装实现和使用,ardf目的是封装一系列 Android 开发框架帮助开发者快速开发提高开发效率。本篇是 ardf的第二篇,将介绍基于 DataBinding + Koin 实现的 MVVM 模式页面快速开发框架的使用和详细实现。

Android基于DataBinding封装RecyclerView实现快速列表开发

DataBinding 是 Google 官方的一个数据绑定框架,借助该库,您可以声明式的将应用中的数据源绑定到布局中的界面组件上,实现通过数据驱动界面更新,从而降低布局和逻辑的耦合性,使代码逻辑更加清晰。更多关于 DataBinding 的介绍请查阅 Google 官方文档:DataBinding[1]

Koin 是一个基于 Kotlin 的 DSL 实现的轻量级依赖注入框架,相比于 Dagger2, Koin 无反射、无代码生成且使用更简单;借助该库可轻松在基于 kotlin 的 Android 应用开发中实现依赖注入,降低代码的耦合性。更多关于 Koin 的介绍及使用请查阅官方文档:Koin[2]

2. 使用效果

在 Android 应用中页面显示几乎是每个应用必不可少的功能,要让页面布局在手机上进行显示绝大多数情况都是使用 Activity/Fragment 来承载;而创建一个 Activity/Fragment 需要先加载布局,然后从布局中找到我们需要的 View 对象再去更新其数据或为其添加相应事件处理,那么如果将这些封装成通用的 Activity/Fragment 基类则将减少很多开发代码从而提高开发效率。

先看一下封装后的代码使用效果。

2.1 项目配置

在项目 Module 的 build.gradle 中添加依赖,如下:

代码语言:javascript
复制
dependencies {
    implementation 'com.loongwind.ardf:base:1.0.1'
}

ardf基于 DataBinding 进行封装,需要开启 DataBinding,启用方式如下:

代码语言:javascript
复制
android {
    ...
    buildFeatures {
        dataBinding true
    }
}

同时在插件中添加 kotlin-kapt的插件,如下:

代码语言:javascript
复制
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    // 添加 kotlin-kapt 插件
    id 'kotlin-kapt'
}

配置完成后,点击 Sync Now同步 build.gradle 配置生效后即可进行代码开发。

2.2 自动装载布局

通过继承 ardf提供的 BaseBindingActivity/ BaseBindingFragment可快速装载页面布局。

在 layout 里创建一个 test_page.xml的布局文件:

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
  
    <data>
        <variable
            name="text"
            type="String" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="10dp">

       <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:padding="15dp"
            android:textSize="30sp"
            android:text="@{text}"/> //绑定数据

    </LinearLayout>
</layout>

然后创建一个 TestActivity:

代码语言:javascript
复制
//泛型类型是布局通过 DataBinding 自动生成的 ViewDataBinding 类型
class TestActivity : BaseBindingActivity<TestPageBinding>() {
   
    // 通过 binding 操作界面元素更新界面
    override fun initDataBinding(binding: TestPageBinding) {
        binding.text = "Hello ardf"
    }
}

代码完成了,只需继承 BaseBindingActivity泛型填写布局自动生成的 Binding 类,然后在实现的 initDataBinding方法中绑定界面数据即可。

运行效果如下:

同样 Fragment 的使用方法类似,创建一个 TestFragment :

代码语言:javascript
复制
//泛型类型是布局通过 DataBinding 自动生成的 ViewDataBinding 类型
class TestFragment : BaseBindingFragment<TestPageBinding>() {
    
    // 通过 binding 操作界面元素更新界面
    override fun initDataBinding(binding: TestPageBinding) {
        binding.text = "Hello ardf"
    }
}

运行效果跟 Activity 一样,这里就不重复贴图了。

2.3 自动注入 ViewModel

ardf除了自动装载布局以外,还支持自动注入 ViewModel 并将 ViewModel 与界面布局自动进行绑定。

首先创建一个 TestViewModel 继承自 BaseViewModel

代码语言:javascript
复制
class TestViewModel : BaseViewModel(){
    val text = "Hello ardf ViewModel"
}

修改上面的 test_page.xml 布局接收vm变量的 TestViewModel 数据:

代码语言:javascript
复制
<?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:tools="http://schemas.android.com/tools">
  
    <data>

        <!--通过 DataBinding 接收 ViewModel 对象-->
        <variable
            name="vm"
            type="com.loongwind.ardf.demo.TestViewModel" />

    </data>
  
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="10dp">

       <TextView
            android:id="@+id/text"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:textSize="30sp"
            android:text="@{vm.text}" //绑定 TestViewModel 中的 text 数据
            android:padding="15dp"/>

    </LinearLayout>
</layout>

通过 DataBinding 方式将 ViewModel 中的数据绑定到界面元素中。

然后再创建 TestActivity 继承自 BaseBindingViewModelActivity

代码语言:javascript
复制
//第一个泛型类型是布局通过 DataBinding 自动生成的 ViewDataBinding 类型
//第二个泛型就是上面创建的 ViewModel 类型
class TestActivity : BaseBindingViewModelActivity<TestPageBinding, TestViewModel>() {

}

可以发现,Activity 的代码又简介了许多。

最后一步是实现 ViewModel 的注入,ardf基于 koin实现依赖注入,需要创建 appModule 将 实现的 TestViewModel 添加到依赖中,然后在 Application 中初始化 koin,代码如下:

代码语言:javascript
复制
val appModule = module {
    // 将 ViewModel 添加到 koin 依赖
    viewModel{ TestViewModel()}
}

class App : Application() {

    override fun onCreate() {
        super.onCreate()
        // 启动 koin
        startKoin{
            androidLogger()
            androidContext(this@App)
            // 添加 appModule
            modules(appModule)
        }
    }
}

代码实现完成,运行效果如下:

跟之前的实现效果一致,同样的 Fragment 使用方法是一样的,只需继承 BaseBindingViewModelFragment即可,如下:

代码语言:javascript
复制
//第一个泛型类型是布局通过 DataBinding 自动生成的 ViewDataBinding 类型
//第二个泛型就是上面创建的 ViewModel 类型
class TestFragment : BaseBindingViewModelFragment<TestPageBinding, TestViewModel>() {

}

2.4 事件处理

前面界面加载完成了,数据也可以在 ViewModel 中进行更新,常规事件也可以在 ViewModel 中进行处理,但是跟 Context 相关的处理在 ViewModel 中是没办法进行处理的,因为 ViewModel 中没办法拿到 Context 实例,比如 toast 提示、弹框、页面跳转等,这些情况怎么处理呢?

ardf提供了事件的处理机制,可以将事件传递到 Activity / Fragment 中,然后在 Activity / Fragment 中进行涉及 Context 的处理,并且 ardf提供了两种事件的默认处理:toast(弹出 toast 提示)、back(返回上一个页面)。

2.4.1 toast 提示

BaseViewModel 的子类中调用 postHintText即可在界面上弹出对应的 toast 提示:

代码语言:javascript
复制
class TestViewModel : BaseViewModel(){
    val text = "Hello ardf ViewModel"
    
    fun showToastString(){
        //传入字符串
        postHintText("Hello ardf toast")
    }
    
    fun showToastStringRes(){
        //传入字符串资源
        postHintText(R.string.hello)
    }
}

在布局里添加两个按钮,事件绑定对应的 showToast 方法,运行效果:

2.4.2 back 返回

BaseViewModel 的子类中调用 back()方法即可:

代码语言:javascript
复制
class TestViewModel : BaseViewModel(){
    val text = "Hello ardf ViewModel"
    
    fun goBack(){
        //调用父类提供的 back 方法
        back()
    }
}

同样在布局里添加按钮事件触发 goBack 方法,运行效果如下:

目前 back 方法只在 BaseBindingViewModelActivity 宿主的 BaseViewModel 子类中使用下有效

2.4.3 自定义事件

自定义事件可通过调用 postEvent方法将事件传递到 Activity / Fragment 中,代码如下:

代码语言:javascript
复制
class TestViewModel : BaseViewModel(){
    val text = "Hello ardf ViewModel"
    companion object {
        // 定义跳转到详情页的事件 id
        const val EVENT_TO_DETAILS = 0x00
        // 定义弹出 Dialog 的事件 id
        const val EVENT_SHOW_DIALOG = 0x01
    }
    
    fun toDetailsPage(){
        // 发送跳转详情页事件
        postEvent(EVENT_TO_DETAILS)
    }
    
    fun showDialog(){
        // 发送弹出 Dialog 事件
        postEvent(EVENT_SHOW_DIALOG)
    }
}

然后在 Activity / Fragment 中重写 onEvent方法接收事件进行相应处理:

代码语言:javascript
复制
class TestActivity : BaseBindingViewModelActivity<TestPageBinding, TestViewModel>() {
    
    // 接收事件
    override fun onEvent(eventId: Int) {
        super.onEvent(eventId)
        // 判断事件 id 并进行对应处理
        when(eventId){
            TestViewModel.EVENT_TO_DETAILS -> startActivity(Intent(this, DetailsActivity::class.java))
            TestViewModel.EVENT_SHOW_DIALOG -> showXxxDialog()
        }

    }
}

运行效果如下:

3. 源码解析

前面介绍了 ardf实现自动装载布局、自动注入 ViewModel 和事件的处理的使用,那么 ardf是如何实现这些功能的呢?

首先来看一下 ardf关于页面封装的整体结构,如下:

主要分为四层:依赖库、基础支撑、布局自动绑定、ViewModel 自定绑定:

  • 依赖库ardf关于页面封装所依赖的第三方库,核心是 databinding 和 koin 库,用于数据绑定和依赖注入。
  • 基础支撑:封装工具类、扩展和事件的 Model 及接口。
  • 布局自动绑定:基于 DataBinding 封装的 BaseBindingActivity 和 BaseBindingFragment。
  • ViewModel 自动绑定:在 BaseBindingActivity 和 BaseBindingFragment 的基础上再基于 koin 实现 ViewModel 的注入与绑定。

下面将通过源码详细介绍对应功能的实现原理。

3.1 自动装载布局的实现

在 2.2 的使用介绍中可以发现,自动装载布局的实现依赖了 DataBinding,将 DataBinding 通过布局文件生成的 Binding 类作为泛型传递给了 BaseBindingActivity/ BaseBindingFragment,那么在 BaseBindingActivity/ BaseBindingFragment中是如何通过这个 Binding 类去将布局与我们的 Activity/Fragment 进行绑定的呢?

为了帮助大家更好的理解我画了一个简单的时序图:

从时序图中可以发现核心实现是在 BaseBindingActivity 的 onCreate 中,主要分为以下三步:

  • • 调用 createDataBinding 创建对应布局的 Binding 类,也就是传入的泛型的实例
  • • 通过 setContentView 将实例化的 Binding 对象的 root View 设置给当前 Activity
  • • 调用子类实现的 initDataBinding 方法初始化界面数据

结合时序图再来看一下源码:

代码语言:javascript
复制
abstract class BaseBindingActivity<BINDING :ViewDataBinding>:AppCompatActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        //创建 ViewDataBinding 实例
        val binding = createDataBinding()
        //绑定当前 Activity 生命周期
        binding.lifecycleOwner = this
        //设置 View
        setContentView(binding.root)

        // 初始化数据绑定
        initDataBinding(binding)
    }

    /**
     * 根据泛型 BINDING 创建 ViewDataBinding 实例
     */
    private fun createDataBinding(): BINDING {
        return getBindingType(javaClass) // 获取 ViewDataBinding 泛型实际类型
            ?.getMethod("inflate", LayoutInflater::class.java) // 反射获取 inflate 方法
            ?.invoke(null, LayoutInflater.from(this)) as BINDING // 通过反射调用 inflate 方法
    }

    /**
     * 初始化数据绑定
     * 子类实现该方法通过 binding 绑定数据
     */
    abstract fun initDataBinding(binding: BINDING)
}

代码不多,具体作用也写了相应注释,关键代码在 createDataBinding方法,做了三件事:

  • • 获取当前 Activity 上 ViewDataBinding 的实际类型,即 DataBinding 通过布局文件生成的 Binding 类。
  • • 通过反射获取到 ViewDataBinding 的 inflate方法,该方法会返回当前 Binding 实例。
  • • 通过反射调用 inflate方法初始化 Binding 实例

getBindingType是一个全局的工具方法,源码如下:

代码语言:javascript
复制
fun getBindingType(clazz: Class<*>) : Class<*>? {
    val superclass = clazz.genericSuperclass
    if (superclass is ParameterizedType ) {
        //返回表示此类型实际类型参数的 Type 对象的数组
        val actualTypeArguments = superclass.actualTypeArguments
        return actualTypeArguments.firstOrNull {
            // 判断是 Class 类型 且是 ViewDataBinding 的子类
            it is Class<*> && ViewDataBinding::class.java.isAssignableFrom(it)
        } as? Class<*>
    }
    return null
}

实现是通过反射获取传入类型的所有泛型,然后取出第一个是 ViewDataBinding子类的类型进行返回。

这样就实现了通过泛型传入 Binding 自动加载布局并与当前 Activity 进行绑定。

BaseBindingFragment的实现逻辑与 BaseBindingActivity的实现逻辑基本一致,只是将实现换到了 onCreateView方法中,如下:

代码语言:javascript
复制
abstract class BaseBindingFragment<BINDING:ViewDataBinding>: Fragment() {
    
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        //创建 ViewDataBinding 实例
        val binding = createDataBinding(inflater, container)
        //绑定当前 Fragment 生命周期
        binding.lifecycleOwner = this

        // 初始化数据绑定
        initDataBinding(binding)
        //返回布局 View 对象
        return binding.root
    }

    /**
     * 根据泛型 BINDING 创建 ViewDataBinding 实例
     */
    private fun createDataBinding(inflater: LayoutInflater, container: ViewGroup?): BINDING {
        return getBindingType(javaClass)// 获取泛型类型
            ?.getMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java) // 反射获取 inflate 方法
            ?.invoke(null, inflater, container, false) as BINDING // 通过反射调用 inflate 方法
    }

    /**
     * 初始化数据绑定
     * 子类实现该方法通过 binding 绑定数据
     */
    abstract fun initDataBinding(binding: BINDING)

}

3.2 自动注入 ViewModel 的实现

在 MVVM 模式的开发中,一般是通过 DataBinding 将布局与 ViewModel 绑定使用,ViewModel 中的数据变化自动刷新界面,实现数据驱动 UI 刷新,那么我们怎么将这个过程进行通用的封装呢?

还是先来看一个简单的时序图:

从时序图中不难发现,核心是基于上面介绍的 BaseBindingActivity 实现的 BaseBindingViewModelActivity类,重写了 initDataBinding方法并实现了如下功能:

  • • 调用 createViewModel方法创建 ViewModel 实例对象
  • • 调用 Binding 的 setVariable方法绑定 ViewModel 对象

BaseBindingViewModelActivity源码如下:

代码语言:javascript
复制
open class BaseBindingViewModelActivity<BINDING : ViewDataBinding, VM : BaseViewModel>:
    BaseBindingActivity<BINDING>(){

    //创建 ViewModel 变量并延迟初始化
    val viewModel:VM by lazy {
        createViewModel()
    }

    override fun initDataBinding(binding: BINDING) {
        //绑定 viewModel
        //绑定变量为 vm。
        // 具体业务实现中在实际的布局 xml 文件中声明当前视图的 ViewModel 变量为 vm 即可自动进行绑定。
        binding.setVariable(BR.vm,viewModel)
    }

    /**
     * @description 初始化 ViewModel 并自动进行绑定
     * @return VM ViewModel 实例对象
     */
    private fun createViewModel():VM{
        try {
            //注入 ViewModel,并转换为 VM 类型
            return injectViewModel() as VM
        }catch (e:Exception){
            // 抛出异常
            throw Exception("ViewModel is not inject", e)
        }
    }
}

定义 viewModel 变量并延迟调用 createViewModel 方法进行初始化;在 initDataBinding将 viewModel 与布局的 vm变量进行绑定。

vm变量来源是因为在框架里创建了一个空的 ardf_base_activity.xml布局中定义后生成的:

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>

        <variable name="vm" type="Object"/>

    </data>
  
</layout>

createViewModel方法里调用了扩展方法 injectViewModel通过 Koin 注入 ViewModel,源码如下:

代码语言:javascript
复制
@OptIn(KoinInternalApi::class)
fun ComponentActivity.injectViewModel() : ViewModel?{
    return getViewModel(javaClass, getKoinScope(), this, viewModelStore )

}

/**
 * @param javaClass Class类型
 * @param scope koin生命周期范围
 * @param owner ViewModelStoreOwner 类型,ViewModel 绑定什么周期对象,Activity、Fragment 都实现了该接口
 * @param viewModelStore 存储 ViewModel 的对象
 */
@OptIn(KoinInternalApi::class)
fun getViewModel(javaClass : Class<*>,
                 scope: Scope,
                 owner: ViewModelStoreOwner,
                 viewModelStore: ViewModelStore) : ViewModel?{
    // 获取当前 Activity 上 ViewModel 泛型的实际类型
    val viewModel = getViewModelType(javaClass)?.let {
        // 获取 ViewModelFactory
        val viewModelFactory = getViewModelFactory(owner, it.kotlin, null, null, null, scope)
        //获取注入的 ViewModel
        ViewModelLazy(it.kotlin, { viewModelStore }, { viewModelFactory} ).value
    }
    return viewModel
}

injectViewModel中调用 getViewModel方法:

  • • 通过 getViewModelType获取 ViewModel 的类型
  • • 调用 Koin 提供的 getViewModelFactory 获取 ViewModelFactory
  • • 调用 Koin 提供的 ViewModelLazy获取注入的 ViewModel

getViewModelType 的实现跟上面的 getBindingType 的原理一样,源码如下:

代码语言:javascript
复制
fun getViewModelType(clazz: Class<*>) : Class<out ViewModel>? {
    val superclass = clazz.genericSuperclass
    if (superclass is ParameterizedType) {
        //返回表示此类型实际类型参数的 Type 对象的数组
        val actualTypeArguments = superclass.actualTypeArguments
        //返回第一个符合条件的 Type 对象
        return actualTypeArguments.firstOrNull{
            it is Class<*> && BaseViewModel::class.java.isAssignableFrom(it)
        } as? Class<out ViewModel>
    }
    return null
}

最终实现自动注入 ViewModel 并与当前 Activity / Fragment 布局进行绑定的功能。

BaseBindingViewModelFragment 的实现原理与 BaseBindingViewModelActivity 的实现原理相同,这里就不在重复贴代码,有兴趣的可以直接去看源码

3.3 事件处理的实现

ViewModel 的自动绑定实现了,那怎么实现事件的处理呢?我们知道通过 DataBinding 可以将事件传递到 ViewModel 中进行处理,那么又怎么将需要用到 Context 等特殊事件传递到 Activity / Fragment 里去处理呢?

同样的先看一个简单的时序图:

时序图解析:

  • • 事件通过 Activity 传到到 View
  • • Binding 里监听到事件后将事件传递到 ViewModel
  • • ViewModel 中调用父类 BaseViewModelpostEvent方法将事件传递到 Activity

前面两步是由 Android 本身事件机制和 DataBinding 来完成的,第三步是 ardf实现的 BaseViewModel来完成的,源码如下:

代码语言:javascript
复制
open class BaseViewModel: ViewModel() {
    // 提示文字
    var hintText = MutableLiveData<Event<String>>()
    // 提示文字资源
    var hintTextRes = MutableLiveData<Event<Int>>()
    // 事件
    var event = MutableLiveData<Event<Int>>()
    
    protected fun postHintText(msg: String) {
        hintText.value = Event(msg)
    }

    protected fun postHintText(msgRes: Int) {
        hintTextRes.value = Event(msgRes)
    }

    protected fun postEvent(eventId: Int) {
        event.value = Event(eventId)
    }

    /**
     * 返回事件
     */
    fun back(){
        postEvent(EVENT_BACK)
    }
}

声明了三个变量:hintTexthintTextResevent分别用于传递提示文字、提示文字资源和事件,并提供了对应的 post方法用于快速调用;另外提供了一个 back方法用于传递返回事件。

所有事件都是通过一个 Event 类进行包裹,源码如下:

代码语言:javascript
复制
class Event<T>(private val value: T) {

    //是否已被处理
    private var handled = false

    /**
     * @description 防止粘性事件被多次消费,多个观察者场景下,只会被一个观察者消费
     */
    fun getValueIfNotHandled(): T? {
        return if (handled) {
            // 已处理返回 null
            null
        } else {
            // 标记为已处理
            handled = true
            value
        }
    }

    fun get(): T {
        return value
    }
}

使用 value存放传入的值并提供获取值的 get 方法,其中定义 handled变量标记事件是否已处理,通过 getValueIfNotHandled获取值时如果已处理则返回空,未处理则返回对应的值并将事件标记为已处理,以防止一个事件被多次消费,当然如果需求如此的话可以调用 get() 方法获取事件值。

在 ViewModel 中传递事件以及事件的封装完成了,那怎么将这个事件传递到 Activity / Fragment 呢?

首先为 ViewModel 扩展一个 bind 方法:

代码语言:javascript
复制
fun BaseViewModel.bind(activity: BaseBindingViewModelActivity<*,*>) {
    observe(activity, activity){
        activity.onEvent(it)
    }
}

fun  BaseViewModel.observe( owner: LifecycleOwner, context: Context?, onEvent: (Int) -> Unit){
    // 订阅提示文字变化
    hintText.observe(owner){
        val content = hintText.value?.getValueIfNotHandled()
        if (!content.isNullOrBlank()) {
            context?.toast(content)
        }
    }
    // 订阅提示文字资源变化
    hintTextRes.observe(owner) {
        val contentRes = hintTextRes.value?.getValueIfNotHandled() ?: -1
        if (contentRes > 0) {
            context?.toast(contentRes)
        }
    }

    // 订阅事件变化
    event.observe(owner) {
        event.value?.getValueIfNotHandled()?.let {
            onEvent(it)
        }
    }
}

作用是订阅 hintTexthintTextRes的变化后弹出 toast提示;同时订阅事件 event 的变化调用 onEvent方法, onEvent是接口 OnEventListener提供的方法:

代码语言:javascript
复制
interface OnEventListener {
    /**
     *
     * @description ViewModel 事件响应
     * @param eventId 事件 id,根据实际业务自定义
     * @return
     *
     */
    fun onEvent(eventId:Int)
}

BaseBindingViewModelActivity需实现 OnEventListener并在初始化 ViewModel 后调用 bind 方法,如下:

代码语言:javascript
复制
open class BaseBindingViewModelActivity<BINDING : ViewDataBinding, VM : BaseViewModel>:
    BaseBindingActivity<BINDING>(), OnEventListener {

    ...
        
    override fun  onEvent(eventId: Int) {
        if(eventId == EVENT_BACK){
            onBackPressed()
        }
    }

    /**
     * @description 初始化 ViewModel 并自动进行绑定
     * @return VM ViewModel 实例对象
     */
    private fun createViewModel():VM{
        try {
            //注入 ViewModel,并转换为 VM 类型
            val viewModel = injectViewModel() as VM
            // 订阅事件
            viewModel.bind(this)  
            return viewModel
        }catch (e:Exception){
            // 抛出异常
            throw Exception("ViewModel is not inject", e)
        }
    }
}

这样就将事件传递到了 Activity / Fragment 的 onEvent 回调方法中,在该回调中就可以自定义处理 ViewModel 中传递过来的事件。

4. 总结

本文主要介绍了 ardf(Android 快速开发框架)中基于 DataBinding + Koin 的 MVVM 模式的页面快速开发及事件处理的使用方法,并通过源码解析详细介绍了其实现原理,从而进一步提高 Android 开发的效率。

源码地址:ardf[4] mavenCentral:com.loongwind.ardf:base:1.0.1

引用链接

[1] DataBinding: https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.android.com%2Ftopic%2Flibraries%2Fdata-binding [2] Koin: https://github.com/InsertKoinIO/koin [3] ardf: https://github.com/loongwind/ardf

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-09-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 loongwind 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 前言
  • 2. 使用效果
    • 2.1 项目配置
      • 2.2 自动装载布局
        • 2.3 自动注入 ViewModel
          • 2.4 事件处理
            • 2.4.1 toast 提示
            • 2.4.2 back 返回
            • 2.4.3 自定义事件
        • 3. 源码解析
          • 3.1 自动装载布局的实现
            • 3.2 自动注入 ViewModel 的实现
              • 3.3 事件处理的实现
                • 引用链接
            • 4. 总结
            相关产品与服务
            数据库一体机 TData
            数据库一体机 TData 是融合了高性能计算、热插拔闪存、Infiniband 网络、RDMA 远程直接存取数据的数据库解决方案,为用户提供高可用、易扩展、高性能的数据库服务,适用于 OLAP、 OLTP 以及混合负载等各种应用场景下的极限性能需求,支持 Oracle、SQL Server、MySQL 和 PostgreSQL 等各种主流数据库。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档