前言
在上一篇中,对Jetpack里面的ViewModel以及LiveData进行了详解。在本篇中,将会对DataBinding进行详解!
那么DataBinding有什么作用呢?
让布局文件承担了部分原本属于页面的工作,使页面与布局耦合度进一步降低!
不过要想是用DataBinding功能,需要在对应AppModule的build.gradle开启dataBinding功能:
android {
//...略
defaultConfig {
//...略
dataBinding {
//需要开启dataBinding功能
enabled = true
}
}
}
知道了对应的作用,以及开启了对应的功能,接下来开始实战部分!(将会循序渐进,由浅入深,依次讲解)
1、示例一
第一个讲细一点,后面的可以稍微快点!
1.1 先看布局文件
如图所示
就现在这个布局,我们再熟悉不过了!不过现在需要将鼠标光标移动至<?xml
前面,随后按Alt+enter
,就会出现该弹窗,选择第一个按回车,布局就变成了:
<?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>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
...略
这时我们看到:
- 布局里面多了一对
<data>...</data>
标签。 - 在这队标签里面将会放与布局相关的实体类以及辅助显示类。
我们先来看看对应的实体类长啥样:
1.2 实体类UserInfo.kt
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 回到布局,添加实体类
<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
类型和布局绑定了,现在实体类里面还差image
和star
,它们都是int
类型,那该怎么和布局绑定呢?
1.4 辅助类登场
StarUtils.kt
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
class ImageUtils {
companion object {
@JvmStatic
fun getDrawable(context: Context, resourceId: Int): Drawable? {
return ContextCompat.getDrawable(context, resourceId)
}
}
}
注意:当布局调用对应方法时,它是通过Java的方式,并非Kotlin方式,所以想要达到直接静态点出来的效果(不想出现companion
关键字),需要在对应方法上加@JvmStatic
注解。
现在辅助类准备好了,继续回到布局进行改造!
1.5 再次改造布局
<?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>
<variable
name="userInfo"
type="com.hqk.databinding.UserInfo" />
<import type="com.hqk.databinding.StarUtils" />
<import type="com.hqk.databinding.ImageUtils" />
</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"
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逻辑代码
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
一致<variable name="userInfo" type="com.hqk.databinding.UserInfo" />
到现在为止,所有准备工作已经做好了,运行看下效果:
如图所示
我们发现,对应的数据已经成功的显示在布局上。
现在问题来了,那按钮呢?能用DataBinding么?? 当然可以!
1.7 按钮事件注入
class ClickHandle {
fun buttonOnClick(view: View) {
Toast.makeText(view.context, "喜欢", Toast.LENGTH_SHORT).show()
}
}
对应布局改造:
<?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>
<variable
name="userInfo"
type="com.hqk.databinding.UserInfo" />
<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>
</layout>
这时又通过<variable
将对应的事件类绑定与布局里,和userInfo
一样。
既然这里和userInfo
一样,那么对应的activity应该也要加对应逻辑吧?
1.8 再次进入Activity逻辑代码
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 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>
<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>
</layout>
这里我们看到:
- 我这将对应的
TextView
与Button
,放入了次布局,然后通过<include
标签导入进来。 - 随后使用了
app:userInfo
和app:clickHandle
将当前userInfo
和clickHandle
导入了@layout/sub
布局里
2.2 再来看次布局
<?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>
<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>
</layout>
这里我们看到:
- 依然通过
<variable
接收了对应的实体类以及事件注入类, - 而辅助类还是通过
<import
导入进布局里 - 其他的依旧都没变
不过注意的是:
- 次布局的
<variable name=“userInfo”
与 主布局赋值次布局时app:userInfo
相同, - 因此同一个实体类或事件注入类,与次布局,主布局对应的的别名最好一致
- 对应的
<import
可以根据不同的功能分别导入(不用全部注入在主布局里)
好了示例二到这也结束了,业务逻辑和示例一的一致。
现在示例一和二,玩的都是静态数据,要不整个网络数据玩玩?好嘞!!
3、示例三
3.1 先来看看布局
<?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>
<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>
</layout>
从这个布局里面可以看出:
- 导入的实体类分别为:
String
和int
。 - 在
ImageView
控件里,分别表示加载网络图片和本地图片的功能。 - 这里使用的是
app:image
加载网络图片 - 使用
app:defaultImage
加载本地默认图片
既然如此,那看看业务逻辑怎么实现呢?
3.2 实现加载图片
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下,需要引入对应的插件plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-kapt' //需要引入该插件 } android { ...略 }
现在准备工作都做好了,就差Activity了
3.3 对应Activity业务逻辑
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
}
}
别忘了网络权限
<uses-permission android:name="android.permission.INTERNET" />
这时,我们就可以通过activityMainBinding.networkImage
、activityMainBinding.localImage
,动态加载对应的网络图片和本地图片。(运行效果就不贴了,就两张不一样的图)
不过现在发现没,加载图片的方式,分别定义了两个方法。
一个ImageView
这么傲娇么?万一网络图片为空想要加载本地图片岂不是还要调两个方法?
不能忍!必须要改!
3.4 重构加载图片的方式
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.networkImage
与activityMainBinding.localImage
就能加载不同的图片了。(运行效果,读者可亲自运行)
好了,示例三也完了!
在前三个例子里,我时不时提到过单向绑定。单向绑定,顾名思义就是对应的业务逻辑内容单方面与布局内容绑定、赋值。
既然有单向绑定,那么肯定有双向绑定!不然我们EditText
输入的内容怎么获取?
4、示例四
4.1 还是老规矩,先看布局
<?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>
<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>
</layout>
注意布局里面:
- 赋值的时候使用的是:
android:text="@={userViewModel.userName}"
, - 也就是说:单向绑定使用的是
@{xxx}
- 双向绑定使用的是
@={xxx}
- 访问的内容为:
userViewModel.userName
里面的userName
属性或者getUserName
方法
这里面使用的是userViewModel
,因此
4.2 userViewModel.kt
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
class User {
constructor(userName: String?) {
this.userName = userName
}
var userName: String? = null
}
这个狠简单,直接过掉。来看看Activity对应的业务逻辑
4.4 对应Activity
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()
}
}
这个不用说了吧,这几个例子下来,想必读者应该很熟悉这段代码了!
来看看运行效果
如图所示
当我输入对应的内容时,监听了对应EditText输入的内容。
这里我们看到,示例四的双向绑定用到了注解,那能否不使用注解呢?当然可以哇!
5、示例五
本示例基于示例四!
5.1 UserViewModel.kt
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
也没有用注解了
其他代码不动,本示例结束,来看看运行效果:
特意换了个TAG打印,运行效果一致,下一个!
前几个示例都是比较简单的页面,那如果说换至RecycleView呢?将会产生怎样的故事?
6、示例六
老样子,看布局!
6.1 先看对应布局
activity_main.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:tools="http://schemas.android.com/tools">
<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>
</layout>
这里就一个RecyclerView
,没有直接的数据交互,因此data标签内为空
来看看item布局
太长了,所以甩个截图上来
如图所示
从这个布局可以看出:
- 通过这个
@{xx}
,可以看出,这里面数据都是单向绑定 - 实体类为
com.hqk.databinding6.UserInfo
,别名为userInfo
因此
6.2 UserInfo.kt
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
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 网络图片加载
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
class RecyclerViewAdapter() : RecyclerView.Adapter<RecyclerViewAdapter.MyViewHolder>() {
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
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
}
}
这里不用多说了,直接看运行效果:
OK,DataBinding内容已经讲解完毕!现在开始最后一个实战,将前几篇所讲的知识点和本篇结合起来!
来做一个裁判记分牌!
7、示例七(裁判记分牌)
7.1 一如既往,先看布局
如图所示
- 这里分为两个队伍,分别为:
Team A
和Team B
- 加分,分别为 1分、2分、3分
- 拥有撤销单次操作,以及清空积分的功能
- 这里绑定的Model为
MyViewModel
- 对应按钮点击事件使用的是
android:onClick="@{()->viewModel.aTeamAdd(1)}"
7.2 来看MyViewModel
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使用:
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所有讲解,到这里就结束了!下一篇讲解另一个组件Room,敬请期待吧!
Demo下载: 点我下载