安卓android Kotlin MVVM使用笔记

语言: CN / TW / HK

环境

gradle plugin version:7.2.1

gradle version:7.3.3

android studio version:Chipmunk 2021.2.1 Patch 1

开始

  1. 新建一个安卓项目,这里有一点结构写出来方便说:

java |app |build.gradle |build.gradle

  1. 启用kotlin-kapt插件:

在app/build.gradle里的最顶部。

gradle plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' id 'kotlin-kapt`//这是添加的 }

  1. 继续在这个文件添加配置

``` gradle

android {

...

kapt {
    includeCompileClasspath = false
}

buildFeatures {
    viewBinding true
    //如果需要下面就取消注释
    //dataBinding true
}

}

```

viewBinding和dataBinding区别:
viewBinding不用在布布局文件转换局文件里额外包裹\<layout>标签,直接可以在代码里调用和加载。
dataBinding使用可以将普通布局文件的第一行点击小灯泡转换为该形式,这样的可以使用\<<variable>标签,直接在布局里绑定页面数据和如点击事件之类的方法。

``` xml

<data>

</data>

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

</LinearLayout>

```

  1. 改变页面绑定方法

旧:setContentView(R.layout.activity_main)

新:ActivityMainBinding.inflate(layoutInflater),注意这里是kotlin写法,java为ActivityMainBinding.inflate(getLayoutInflater())这样子。 fragment里类似,只要有个layoutInflater,就可以初始化个布局。

``` java val binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root)

```

这样,获取里面的子view也很方便。 假定布局内有一个TextView:

``` xml

<data>

</data>

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

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

```

要在代码里设置文本。 旧:findViewById<TextView>(R.id.text).text="321" 新:binding.Text.text="321"

  1. 绑定view和variable

这是DataBinding才有的

单向绑定

``` xml

<data>
    <!-- 这里列出,name自定义,type是一个实例那样子
    再注意下面TextView里的text属性用法,灵活使用可以自动操控布局的显示隐藏,各种参数都能这样绑定处理 -->
    <variable
        name="str"
        type="java.lang.String" />
</data>

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

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{str}" />

</LinearLayout>

```

``` java

//别忘了在setContentView()差不多那些位置绑定该参数,不然就会报错。 //如果没有BR这个类或者BR.str标红,构建一下就好 binding.setVariable(BR.str, "321") ```

双向绑定

适用于EditText这样的,使用双向绑定text可以在代码里实例化一个text,之后EditText内容改变该text也会改变,相当于不用再获取EditText的text。 使用示例:

``` xml

<data>

    <variable
        name="str"
        type="java.lang.String" />
    <!-- 比起单向,中间多了个等号 -->
    <variable
        name="edt"
        type="java.lang.String" />
</data>

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

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{str}" />

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="text"
        android:text="@={edt}" />

</LinearLayout>

```

``` java

//同样别忘记 var edt = "123" binding.setVariable(BR.edt, edt)

//之后直接调用edt就可以得到EditText内容。 ```

事件绑定

适用于按钮点击事件的绑定,还是偏向于在Activity里代码方式更方便操作,在下面viewmodel说明。

ViewModel

一个activity可以绑定有多个ViewModel,ViewModel相当于是复杂业务逻辑比如加减法计算、网络请求等放置的好地方。可以一个ViewModel负责一个方面,比如一个是负责网络请求,一个是负责页面展示数据这样子。 ViewModel好处在于可以跟着activity共存亡,我理解是这样。

使用需要在app/build.gradle里添加一个:implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"

然后就可以新建类继承ViewModel类就行。 在activity里通过ViewModelProvider(owner, ViewModelProvider.NewInstanceFactory()).get(MyViewModel::class.java)绑定 这里通过ViewModel来绑定前面所说的那些:

java //先展示一个方便绑定的方法实现 @Suppress("UNCHECKED_CAST") fun <T : ViewModel> viewModel( owner: ViewModelStoreOwner, viewModel: Class<out ViewModel> ) = lazy { ViewModelProvider(owner, ViewModelProvider.NewInstanceFactory()).get(viewModel) as T } //然后使用方式 private val viewModel by viewModel<MyViewModel>(this, MyViewModel::class.java)

开始使用示例:

``` java class MyViewModel: ViewModel() {

val txt = "321"
var edt = "321"

fun onButtonClick() {
    L.i("点击了该按钮")
}

}

```

``` xml

<data>

    <variable
        name="model"
        type="top.heue.certu.activity.MyViewModel" />
</data>

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

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{model.txt}" />

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="text"
        android:text="@={model.edt}" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="@{()->model.onButtonClick()}" />

</LinearLayout>

```

``` java

class MainActivity: AppCompatActivity() {

private val binding by lazy{ ActivityMainBinding.inflate(layoutInflater)}
private val viewModel by viewModel<MyViewModel>(this, MyViewModel::class.java)
override fun onCreate() {
    super.onCreate()
    setContentView(binding.root)
    binding.setVariable(BR.model, viewModel)
}

} ```

函数绑定可以有这样几种形式:@{() -> model.onButtonClick()}onButtonClick是一个自定义的方法,可以传递参数,因此可以(view) -> model.onButtonClick(view),这样onButtonClick就可以用到view,以view作点击事件来自哪个按钮的区分;也可以@{model::onButtonClick},不带参数。