Android—Jetpack教程(三)

语言: CN / TW / HK

theme: smartblue

这是我参与11月更文挑战的第24天,活动详情查看:2021最后一次更文挑战

前言

在上一篇中,对Jetpack里面的ViewModel以及LiveData进行了详解。在本篇中,将会对DataBinding进行详解!

那么DataBinding有什么作用呢?

让布局文件承担了部分原本属于页面的工作,使页面与布局耦合度进一步降低!

不过要想是用DataBinding功能,需要在对应AppModule的build.gradle开启dataBinding功能:

groovy android { //...略 defaultConfig { //...略 dataBinding { //需要开启dataBinding功能 enabled = true } } }

知道了对应的作用,以及开启了对应的功能,接下来开始实战部分!(将会循序渐进,由浅入深,依次讲解

1、示例一

第一个讲细一点,后面的可以稍微快点!

1.1 先看布局文件

1.png

如图所示

就现在这个布局,我们再熟悉不过了!不过现在需要将鼠标光标移动至<?xml前面,随后按Alt+enter,就会出现该弹窗,选择第一个按回车,布局就变成了:

```xml

</data>
<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

...略 `` 这时我们看到: - 布局里面多了一对...`标签。 - 在这队标签里面将会放与布局相关的实体类以及辅助显示类。

我们先来看看对应的实体类长啥样:

1.2 实体类UserInfo.kt

kotlin class UserInfo { constructor(name: String?, image: Int, star: Int) { this.name = name this.star = star this.image = image } var name: String? = null var star = 0 var image = 0 } 我们看到就是一个很标准的实体类,刚刚提到<data>...</data>标签里就是放实体类以及辅助类,那该怎样在布局里面使用呢?

1.3 回到布局,添加实体类

xml <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> <variable name="userInfo" type="com.hqk.databinding.UserInfo" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> ...略 略略 <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="44dp" android:text="@{userInfo.name}" android:textSize="24sp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.498" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline2" tools:text="姓名" /> ...略 略略

这时我们看到: - 使用了<variable 标签将对应的实体类与布局相互绑定; - name 这个顾名思义,就是定义对应的别名,在布局里以及在逻辑代码里,将通过这个别名相互绑定数据 - type 这个表示,对应实体类的具体位置

现在我们看到已经有效的将对应的String类型和布局绑定了,现在实体类里面还差imagestar,它们都是int类型,那该怎么和布局绑定呢?

1.4 辅助类登场

StarUtils.kt

```kotlin class StarUtils {

companion object {
    @JvmStatic
    fun getStar(star: Int): String? {
        when (star) {
            1 -> return "一星"
            2 -> return "二星"
            3 -> return "三星"
            4 -> return "四星"
            5 -> return "五星"
        }
        return ""
    }
}

} ``` ImageUtils.kt

```kotlin class ImageUtils {

companion object {
    @JvmStatic
    fun getDrawable(context: Context, resourceId: Int): Drawable? {
        return ContextCompat.getDrawable(context, resourceId)
    }
}

} `` **注意:当布局调用对应方法时,它是通过Java的方式,并非Kotlin方式,所以想要达到直接静态点出来的效果(不想出现companion关键字),需要在对应方法上加@JvmStatic`注解。**

现在辅助类准备好了,继续回到布局进行改造!

1.5 再次改造布局

```xml

...略 略 略

   <ImageView
        android:id="@+id/imageView"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:src="@{ImageUtils.getDrawable(context,userInfo.image)}"
        app:layout_constraintBottom_toTopOf="@+id/guideline2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:srcCompat="@tools:sample/avatars" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="44dp"
        android:text="@{userInfo.name}"
        android:textSize="24sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline2"
        tools:text="姓名" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="40dp"
        android:text="@{StarUtils.getStar(userInfo.star)}"
        android:textSize="18sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView"
        tools:text="五星" />

...略 略 略 `` 这时我们通过<import将对应的辅助类也添加至布局中,并且**通过@{xxx}`实现了单向绑定数据。**

那这样就好了么?数据就能自动在布局上赋值?

当然不行!仔细想下到目前为止是不是忽略了对应Activity的逻辑代码?不可能什么都不做就实现了数据的绑定!那么!!

1.6 进入对应Activity逻辑代码

```kotlin class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // setContentView(R.layout.activity_main)

    val activityMainBinding: ActivityMainBinding =
        DataBindingUtil.setContentView(this, R.layout.activity_main)

    val userInfo =
        UserInfo("斯嘉丽.约翰逊", R.drawable.scarlettjohansson, 4)

    activityMainBinding.userInfo = userInfo

}

} `` 这里我们看到: - 原有的setContentView(xxx)已成历史, - 而是使用了DataBindingUtil.setContentView(xx)- 这里的activityMainBinding.userInfo对应的userInfo与布局里面的name`一致

```xml
        <variable
            name="userInfo"
            type="com.hqk.databinding.UserInfo" />
```

到现在为止,所有准备工作已经做好了,运行看下效果:

2.png

如图所示

我们发现,对应的数据已经成功的显示在布局上。

现在问题来了,那按钮呢?能用DataBinding么?? 当然可以!

1.7 按钮事件注入

kotlin class ClickHandle { fun buttonOnClick(view: View) { Toast.makeText(view.context, "喜欢", Toast.LENGTH_SHORT).show() } }

对应布局改造:

```xml

    <import type="com.hqk.databinding.StarUtils" />

    <variable
        name="clickHandle"
        type="com.hqk.databinding.ClickHandle" />

    <import type="com.hqk.databinding.ImageUtils" />

</data>
<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
  ...略  略  略
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="56dp"
        android:onClick="@{clickHandle.buttonOnClick}"
        android:text="点我"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView2" />
</androidx.constraintlayout.widget.ConstraintLayout>


`` 这时又通过<variable将对应的事件类绑定与布局里,和userInfo`一样。

既然这里和userInfo一样,那么对应的activity应该也要加对应逻辑吧?

1.8 再次进入Activity逻辑代码

```kotlin class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // setContentView(R.layout.activity_main)

    val activityMainBinding: ActivityMainBinding =
        DataBindingUtil.setContentView(this, R.layout.activity_main)

    val userInfo =
        UserInfo("斯嘉丽.约翰逊", R.drawable.scarlettjohansson, 4)

    activityMainBinding.userInfo = userInfo

    activityMainBinding.clickHandle = ClickHandle()
}

} ```

这次就不用多说了吧。相信通过上面的详解,看到这的你应该能看懂这里的逻辑!

OK,到这里,第一个示例就结束了!当然本篇远不止这些内容!像这样的例子我还有六个!

2、示例二

刚刚我们实现了无需findViewById方法,将数据动态赋值给对应的控件。但对应的布局是一层布局,那如果说对应布局使用了<include这种标签,导入主布局的呢?要怎么将数据绑定在布局上呢?

既然提出了问题,那就实践一番!(在示例一的基础上)

2.1 先看对应主布局:

```xml

<data>
    <variable
        name="userInfo"
        type="com.hqk.databinding2.UserInfo" />

    <variable
        name="clickHandle"
        type="com.hqk.databinding2.ClickHandle" />

    <import type="com.hqk.databinding2.ImageUtils" />

</data>

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.5" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:src="@{ImageUtils.getDrawable(context,userInfo.image)}"
        app:layout_constraintBottom_toTopOf="@+id/guideline2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:srcCompat="@tools:sample/avatars" />

    <include
        layout="@layout/sub"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="48dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline2"
        app:userInfo="@{userInfo}"
        app:clickHandle="@{clickHandle}" />


</androidx.constraintlayout.widget.ConstraintLayout>

``` 这里我们看到:

  • 我这将对应的TextViewButton,放入了次布局,然后通过<include标签导入进来。
  • 随后使用了app:userInfoapp:clickHandle将当前userInfoclickHandle导入了@layout/sub布局里

2.2 再来看次布局

```xml

<data>
    <variable
        name="userInfo"
        type="com.hqk.databinding2.UserInfo" />

    <import type="com.hqk.databinding2.StarUtils" />

    <variable
        name="clickHandle"
        type="com.hqk.databinding2.ClickHandle" />

</data>

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="44dp"
        android:textSize="24sp"
        android:text="@{userInfo.name}"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        tools:text="姓名"
        tools:ignore="MissingConstraints" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{StarUtils.getStar(userInfo.star)}"
        android:layout_marginTop="40dp"
        android:textSize="18sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView"
        tools:text="五星" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="56dp"
        android:text="点我"
        android:onClick="@{clickHandle.buttonOnClick}"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView2" />

</androidx.constraintlayout.widget.ConstraintLayout>

`` 这里我们看到: - 依然通过<variable接收了对应的实体类以及事件注入类, - 而辅助类还是通过 <import`导入进布局里 - 其他的依旧都没变

不过注意的是: - 次布局的<variable name=“userInfo” 与 主布局赋值次布局时app:userInfo相同, - 因此同一个实体类或事件注入类,与次布局,主布局对应的的别名最好一致 - 对应的<import 可以根据不同的功能分别导入(不用全部注入在主布局里)

好了示例二到这也结束了,业务逻辑和示例一的一致。

现在示例一和二,玩的都是静态数据,要不整个网络数据玩玩?好嘞!!

3、示例三

3.1 先来看看布局

```xml

<data>
    <variable
        name="networkImage"
        type="String" />

    <variable
        name="localImage"
        type="int" />

</data>

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="300dp"
        android:layout_height="300dp"
        app:defaultImage="@{localImage}"
        app:image="@{networkImage}"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:srcCompat="@tools:sample/avatars" />

</androidx.constraintlayout.widget.ConstraintLayout>

``` 从这个布局里面可以看出:

  • 导入的实体类分别为:Stringint
  • ImageView控件里,分别表示加载网络图片和本地图片的功能。
  • 这里使用的是app:image加载网络图片
  • 使用app:defaultImage加载本地默认图片

既然如此,那看看业务逻辑怎么实现呢?

3.2 实现加载图片

```kotlin class ImageViewBindingAdapter {

companion object {

    // app:image="@{networkImage}" 和对应的app:image的image相互对应
    @BindingAdapter("image")
    @JvmStatic
    fun setImage(imageView: ImageView, url: String?) {
        if (!TextUtils.isEmpty(url)) {
            Glide.with(imageView.context).load(url).into(imageView)
            return
        }
        imageView.setBackgroundColor(Color.GRAY)
    }

    // app:defaultImage="@{localImage}" 和对应的localImage的image相互对应
    @BindingAdapter("defaultImage")
    @JvmStatic
    fun setImage(imageView: ImageView, id: Int) {
        imageView.setImageResource(id)
    }
}

} ``` 这时我们看到:

  • 通过@BindingAdapter("xxx")这个注解将布局app:xxx加载图片的方式与业务逻辑单向绑定了
  • 因为这里用到了注解@BindingAdapter,所以在App的Module下,需要引入对应的插件

    ```groovy plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-kapt' //需要引入该插件 }

    android { ...略 } ``` 现在准备工作都做好了,就差Activity了

3.3 对应Activity业务逻辑

```kotlin class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // setContentView(R.layout.activity_main)

    val activityMainBinding: ActivityMainBinding =
        DataBindingUtil.setContentView(this, R.layout.activity_main)

// activityMainBinding.networkImage=null activityMainBinding.networkImage = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2F1114%2F060421091316%2F210604091316-6-1200.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1640276707&t=16e9f137e14ca63ce8ed7c3fd461f12e"

// activityMainBinding.localImage=R.drawable.angelinajolie

}

} ``` 别忘了网络权限

bash <uses-permission android:name="android.permission.INTERNET" />

这时,我们就可以通过activityMainBinding.networkImageactivityMainBinding.localImage,动态加载对应的网络图片和本地图片。(运行效果就不贴了,就两张不一样的图)

不过现在发现没,加载图片的方式,分别定义了两个方法。

一个ImageView这么傲娇么?万一网络图片为空想要加载本地图片岂不是还要调两个方法?

不能忍!必须要改!

3.4 重构加载图片的方式

```kotlin class ImageViewBindingAdapter {

companion object {

// // app:image="@{networkImage}" 和对应的app:image的image相互对应 // @BindingAdapter("image") // @JvmStatic // fun setImage(imageView: ImageView, url: String?) { // if (!TextUtils.isEmpty(url)) { // Glide.with(imageView.context).load(url).into(imageView) // return // } // imageView.setBackgroundColor(Color.GRAY) // } // // // app:defaultImage="@{localImage}" // @BindingAdapter("defaultImage") // @JvmStatic // fun setImage(imageView: ImageView, id: Int) { // imageView.setImageResource(id) // }

    /**
     * 加载网络图片,如果图片资源为空,则加载默认图片
     *
     * requireAll 这个意思是 方法setImage 对应的形参为可传参数(三个形参可以不用全部传入)
     */
    @BindingAdapter(value = ["image", "defaultImage"], requireAll = false)
    @JvmStatic
    fun setImage(imageView: ImageView, url: String?, id: Int) {
        if (!TextUtils.isEmpty(url)) {
            Glide.with(imageView.context).load(url).into(imageView)
            return
        }
        imageView.setImageResource(id)

    }

}

} ``` 这时,我们看到:

  • 通过@BindingAdapter(value = ["image", "defaultImage"], requireAll = false),将两个方法变成了一个方法
  • value = ["image", "defaultImage"] 表示可以接受对应的加载方式(对应布局)
  • requireAll = false 这个表示,对应注解的方法里面的形参可以不用全部传入

这时我们通过动态修改activityMainBinding.networkImageactivityMainBinding.localImage 就能加载不同的图片了。(运行效果,读者可亲自运行)

好了,示例三也完了!

在前三个例子里,我时不时提到过单向绑定。单向绑定,顾名思义就是对应的业务逻辑内容单方面与布局内容绑定、赋值。

既然有单向绑定,那么肯定有双向绑定!不然我们EditText输入的内容怎么获取?

4、示例四

4.1 还是老规矩,先看布局

```xml

<data>
    <variable
        name="userViewModel"
        type="com.hqk.databinding4.UserViewModel" />

</data>

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/editText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10"
        android:inputType="textPersonName"
        android:text="@={userViewModel.userName}"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

```

注意布局里面:

  • 赋值的时候使用的是:android:text="@={userViewModel.userName}"
  • 也就是说:单向绑定使用的是@{xxx}
  • 双向绑定使用的是@={xxx}
  • 访问的内容为:userViewModel.userName里面的userName属性或者getUserName方法

这里面使用的是userViewModel,因此

4.2 userViewModel.kt

```kotlin class UserViewModel : BaseObservable {

constructor() {
    user = User("Hqk")
}

var user: User? = null


// id 'kotlin-kapt'
@Bindable
fun getUserName(): String? {
    return user!!.userName
}

fun setUserName(userName: String?) {
    if (userName != null && userName != user!!.userName) {
        user!!.userName = userName
        Log.d("hqk", "set userName: $userName")
        notifyPropertyChanged(BR.userName)
    }
}

} ``` 这里我们看到:

  • 对应的Model继承了BaseObservable
  • 对应的getUserName方法与布局android:text="@={userViewModel.userName}" 一致
  • 使用了@Bindable注解,因此需要在对应的build.gradle里面加入'kotlin-kapt'插件
  • 通过setUserName方法监听EditText输入的内容,
  • 最后通过notifyPropertyChanged(BR.userName)通知刷新!

这里用到了User

4.3 User.kt

```kotlin class User { constructor(userName: String?) { this.userName = userName }

var userName: String? = null

} ``` 这个狠简单,直接过掉。来看看Activity对应的业务逻辑

4.4 对应Activity

```kotlin class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // setContentView(R.layout.activity_main)

    var activityMainBinding =
        DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)

    activityMainBinding.userViewModel = UserViewModel()
}

} ``` 这个不用说了吧,这几个例子下来,想必读者应该很熟悉这段代码了!

来看看运行效果

3.png

如图所示

当我输入对应的内容时,监听了对应EditText输入的内容。

这里我们看到,示例四的双向绑定用到了注解,那能否不使用注解呢?当然可以哇!

5、示例五

本示例基于示例四!

5.1 UserViewModel.kt

```kotlin class UserViewModel {

private var userObservableField: ObservableField<User>? = null

constructor() {
    user = User("Hqk")
    userObservableField = ObservableField<User>()
    userObservableField!!.set(user)
}

var user: User? = null

fun getUserName(): String? {
    return userObservableField!!.get()!!.userName
}

fun setUserName(userName: String) {
    Log.d("hqk", "userObservableField: $userName")
    userObservableField!!.get()!!.userName = userName
}

} ``` 这里可以看出:

  • 并没有继承BaseObservable
  • 而对应的User也改成了ObservableField<User>
  • 对应的getUserName也没有用注解了

其他代码不动,本示例结束,来看看运行效果:

4.png

特意换了个TAG打印,运行效果一致,下一个!

前几个示例都是比较简单的页面,那如果说换至RecycleView呢?将会产生怎样的故事?

6、示例六

老样子,看布局!

6.1 先看对应布局

activity_main.xml

```xml

<data>

</data>

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

`` 这里就一个RecyclerView`,没有直接的数据交互,因此data标签内为空

来看看item布局

太长了,所以甩个截图上来

5.png

如图所示

从这个布局可以看出:

  • 通过这个@{xx},可以看出,这里面数据都是单向绑定
  • 实体类为com.hqk.databinding6.UserInfo,别名为userInfo

因此

6.2 UserInfo.kt

```kotlin class UserInfo {

var chName:String?=null
var enName:String?=null

constructor(chName: String?, enName: String?, image: String?) {
    this.chName = chName
    this.enName = enName
    this.image = image
}

var image:String?=null

} ``` 没啥可说的,想要加载内容,肯定要有对应的数据。我这直接写了个本地数据,没有从网络获取

6.3 UserInfoUtils.kt

```kotlin class UserInfoUtils {

companion object {

    fun get(): List<UserInfo> {
        val list: MutableList<UserInfo> = ArrayList<UserInfo>()
        val i1 = UserInfo(
            "斯嘉丽.约翰逊",
            "Scarlett Johansson",
            "https://5b0988e595225.cdn.sohucs.com/images/20190624/d93dbf866aa2405f8b9b1d660c15db9d.jpeg"
        )
        list.add(i1)
        //....略
        //....略
        //....略
        val i10 = UserInfo(
            "詹妮弗·洛芙·休伊特",
            "Jennifer Love Hewitt",
            "https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=791729948,2390587761&fm=26&gp=0.jpg"
        )
        list.add(i10)
        return list
    }
}

} ```

这个就是模拟从网络获取的数据。没啥可说的。

这里看到有网络图片的,因此查看网络图片加载

6.4 网络图片加载

```kotlin class ImageViewBindingAdapter {

companion object{
    @BindingAdapter("itemImage")
    @JvmStatic
    fun setImage(imageView: ImageView, url: String?) {
        if (!TextUtils.isEmpty(url)) {
            Glide.with(imageView.context).load(url).into(imageView)
            return
        }
        imageView.setBackgroundColor(Color.GRAY)
    }
}

} ``` 这个讲过,直接过!重点不在这!

6.5 重点,RecycleView绑定的Adapter

```kotlin class RecyclerViewAdapter() : RecyclerView.Adapter() {

var listUserInfo: List<UserInfo> = ArrayList<UserInfo>()

constructor(listUserInfo: List<UserInfo>) : this() {
    this.listUserInfo = listUserInfo
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
    var itemBinding: ItemBinding = DataBindingUtil.inflate(
        LayoutInflater.from(parent.context),
        R.layout.item, parent, false
    )
    return MyViewHolder(itemBinding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
    var userInfo = listUserInfo[position]
    holder.itemBinding!!.userInfo = userInfo
}

override fun getItemCount(): Int {
    return listUserInfo.size
}

class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

    var itemBinding: ItemBinding? = null

    constructor(itemBinding: ItemBinding) : this(itemBinding.root) {
        this.itemBinding = itemBinding
    }

}

} ``` 代码解析

  • 前面的直接过掉,直接看onCreateViewHolder
  • 在这里使用的是DataBindingUtil.inflate(xxx),返回是ItemBinding,并非ActivityMainBinding
  • 在方法onBindViewHolder里,使用了holder.itemBinding!!.userInfo,而这里的.userInfo
  • 与item布局绑定的别名name="userInfo"一致

OK,这里讲解完了,

6.6 来看看对应的Activity

```kotlin class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // setContentView(R.layout.activity_main)

    var activityMainBinding: ActivityMainBinding =
        DataBindingUtil.setContentView(this, R.layout.activity_main)

    activityMainBinding.recyclerView.layoutManager = LinearLayoutManager(this)
    var adapter = RecyclerViewAdapter(UserInfoUtils.get())
    activityMainBinding.recyclerView.adapter = adapter
}

} ``` 这里不用多说了,直接看运行效果:

6.png

OK,DataBinding内容已经讲解完毕!现在开始最后一个实战,将前几篇所讲的知识点和本篇结合起来!

来做一个裁判记分牌!

7、示例七(裁判记分牌)

7.1 一如既往,先看布局

7.png

如图所示

  • 这里分为两个队伍,分别为:Team ATeam B
  • 加分,分别为 1分、2分、3分
  • 拥有撤销单次操作,以及清空积分的功能
  • 这里绑定的Model为MyViewModel
  • 对应按钮点击事件使用的是android:onClick="@{()->viewModel.aTeamAdd(1)}"

7.2 来看MyViewModel

```kotlin class MyViewModel : ViewModel() {

//使用LiveData定义A队积分
private var aTeamScore: MutableLiveData<Int?>? = null
//使用LiveData定义B队积分
private var bTeamScore: MutableLiveData<Int?>? = null

private var aLast: Int? = null//A队上一次分数
private var bLast: Int? = null//B对上一次分数

//A队积分获取
fun getaTeamScore(): MutableLiveData<Int?>? {
    if (aTeamScore == null) {
        aTeamScore = MutableLiveData()
        aTeamScore!!.value = 0
    }
    return aTeamScore
}

//B队积分获取
fun getbTeamScore(): MutableLiveData<Int?>? {
    if (bTeamScore == null) {
        bTeamScore = MutableLiveData()
        bTeamScore!!.value = 0
    }
    return bTeamScore
}

//A队单次添加的积分
fun aTeamAdd(i: Int) {
    saveLastScore()
    aTeamScore!!.value = aTeamScore!!.value!! + i
}

//B队单次添加的积分
fun bTeamAdd(i: Int) {
    saveLastScore()
    bTeamScore!!.value = bTeamScore!!.value!! + i
}

//回退分数
fun undo() {
    aTeamScore!!.value = aLast
    bTeamScore!!.value = bLast
}

//重置分数
fun reset() {
    aTeamScore!!.value = 0
    bTeamScore!!.value = 0
}

//记录上一次的分数
private fun saveLastScore() {
    aLast = aTeamScore!!.value
    bLast = bTeamScore!!.value
}

} ``` 代码解析

  • 这次的MyViewModel才是真正的ViewModel,因为继承了ViewModel()
  • 前面几个示例都是单个DataBinding使用
  • 对应的按钮交互变成了@{()->viewModel.按钮点击方法名(方法参数)}

7.3 来看看Activity使用:

```kotlin class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // setContentView(R.layout.activity_main)

    var activityMainBinding: ActivityMainBinding =
        DataBindingUtil.setContentView(this, R.layout.activity_main)

    var viewModel: MyViewModel = ViewModelProvider(this)[MyViewModel::class.java]

    // appcompat:1.3.0 及以后的版本,可以不用下面那种方式,用上面更简便

// var viewModel = // ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory(application))[MyViewModel::class.java]

    activityMainBinding.viewModel = viewModel
    activityMainBinding.lifecycleOwner = this //使对应的dataBinding拥有Lifecycle功能
}

} ``` 这里不用多说了吧,不过注意看注释,一切都在注释中。

来看看运行效果

在这里插入图片描述 OK!完美运行!

总结

从上面的示例中,可以看出DataBinding的优势:

  • 不再需要findViewById,项目更简洁,可读性更高
  • 布局文件可以包含简单的业务逻辑

结束语

好了,本篇DataBinding所有讲解,到这里就结束了!下一篇讲解另一个组件Room,敬请期待吧!

Demo下载: 点我下载