Jetpack DataBinding
Posted 编程的平行世界
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jetpack DataBinding相关的知识,希望对你有一定的参考价值。
前言
🏀DataBinding只是一种工具,用来解决View和数据之间的绑定。
Data Binding,顾名思义:数据绑定,它可以将布局页面中的组件和应用中的数据进行绑定,支持单向绑定和双向绑定,单向绑定就是如果数据有变化就会驱动页面进行变化,双向绑定就是除了单向绑定之外还支持页面的变化驱动数据的变化,如果页面中有一个输入框,那么我们就可以进行双向绑定,数据变化,它的显示内容就变了,我们手动输入内容也可以改变绑定它的数据。
🌟官方文档:https://developer.android.google.cn/jetpack/androidx/releases/databinding
🌟官方Demo地址:https://github.com/googlecodelabs/android-databinding
如何使用DataBinding呢?
1.启用DataBinding
引用官方文档:
Databinding与 Android Gradle 插件捆绑在一起。您无需声明对此库的依赖项,但必须启用它。
☀注意:即使模块不直接使用数据绑定,也必须为依赖于使用数据绑定的库的所有模块启用数据绑定。
//在gradle的android下加入,然后点击sync
android
...
//android studio 4.0以下
dataBinding
//android studio4.1以后
buildFeatures
dataBinding true
2.生成DataBinding布局
在我们的布局文件中,选择根目录的View,按下Alt+回车键,点击Convert to data binding layout
,就可以转换为DataBinding
布局啦。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RtBN1kBH-1656641965877)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4e2c54b2aef04c9cbe77242578139dc7~tplv-k3u1fbpfcp-watermark.image?)]
然后我们的布局就会变成这样:
<?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">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
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>
我们可以发现,最外面变成了layout
元素,里面有data
元素。我们将在data
元素中声明这个布局中使用到的变量,以及变量的类型。
举个例子:
<data>
<import type="com.example...."/>
<variable
name="color"
type="java.lang.String" />
</data>
- data: 在标签内进行变量声明和导入等
- variable: 进行变量声明
- import: 导入需要的类
3.声明一个User实体类
class User()
var name = "Taxze"
var age = 18
fun testOnclick()
Log.d("onclick", "test")
4.在xml中使用
然后在data中声明变量,以及类名
<data>
<!-- <variable-->
<!-- name="user"-->
<!-- type="com.taxze.jetpack.databinding.User" />-->
<import type="com.taxze.jetpack.databinding.User" />
<variable
name="user"
type="User" />
</data>
然后在布局中使用@
语法
//伪代码,请勿直接CV
<TextView
...
android:text="@user.name"
/>
5.在Activity或Fragment中使用DataBinding
在Activity中通过DataBindingUtil
设置布局文件,同时省略Activity的setContentView
方法
class MainActivity : AppCompatActivity()
private lateinit var mainBinding: ActivityMainBinding
private lateinit var mainUser: User
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
mainBinding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
mainUser = User()
mainBinding.user = mainUser
在Fragment中使用:
class BlankFragment : Fragment()
private lateinit var mainFragmentBinding:FragmentBlankBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View
mainFragmentBinding = DataBindingUtil.inflate(inflater,R.layout.fragment_blank,container,false)
return mainFragmentBinding.root
系统会为每个布局文件都生成一个绑定类。一般默认情况下,类的名称是布局文件名称转化为Pascal大小写形式,然后在末尾添加Binding
后缀,例如:名称为activity_main
的布局文件,对应的类名就是ActivityMainBinding
运行之后的效果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oogYJCMc-1656641965878)(https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/485e5022d3234a8e9ee5a11237682265~tplv-k3u1fbpfcp-watermark.image?)]
注意:只有当布局文件转换为
layout
样式之后,databinding
才会根据布局文件的名字自动生成一个对应的binding
类,你也可以在build/generated/data_binding_base_source_out
目录下查看生成的类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MakflB4w-1656641965879)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/25e2c1d9d09447488bde68caaa26648e~tplv-k3u1fbpfcp-watermark.image?)]
最最最基础的使用就是这样,接下来我们来讲讲如何更好的使用DataBinding
如何在xml布局中更好的使用DataBinding
1.使用集合中的元素
-
加入我们传入了一个集合
books
,我们可以通过以下方式使用:- 获取集合的值
-
android:text="@books.get(0).name" android:text="@books.[0].name"
- 添加默认值(⚡默认值无需加引号,且只在预览视图显示)
-
android:text="@books.pages,default=330"
- 通过??或?:来实现
-
android:text="@books.pages != null ? book.pages : book.defaultPages"
-
android:text="@books.pages ?? book.defaultPages"
2.使用map中的数据
-
map
类型的结构也可以通过get和[]两种方式获取-
//需要注意单双引号 android:text="@books.get('name')" android:text="@books['name']"
-
3.转换数据类型
-
因为
DataBinding
不会自动做类型转换,所有我们需要手动转换,例如在text标签内使用String.valueOf()转换为String类型,在rating标签内我们可以使用Float.valueOf()进行转换-
android:text="@String.valueOf(book.pages)"
-
android:rating="@Float.valueOf(books.rating),default=2.0"
-
4.导入包名冲突处理
-
如果我们导入的包名有冲突,我们可以通过
alias
为它设置一个别名-
//伪代码,请勿直接CV <data> <import type="com.xxx.a.Book" alias="aBook"/> <import type="com.xxx.B.Book" alias="bBook"/> ... </data>
-
5.隐式引用属性
-
在一个
view
上引用其他view
的属性-
//伪代码,请勿直接CV <import type="android.view.View"/> ... <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent"> <CheckBox android:id="@+id/checkOne" .../> <ImageView android:visibility="@checkOne.checked ? View.VISIBLE : View.GONE" .../> </LinearLayout>
-
-
**
include
标签和ViewStub
**标签include
和merge
标签的作用是实现布局文件的重用。就是说,为了高效复用及整合布局,使布局轻便化,我们可以使用include
和merge
标签将一个布局嵌入到另一个布局中,或者说将多个布局中的相同元素抽取出来,独立管理,再复用到各个布局中,便于统一的调整。 比如,一个应用中的多个页面都要用到统一样式的标题栏或底部导航栏,这时就可以将标题栏或底部导航栏的布局抽取出来,再以include
标签形式嵌入到需要的布局中,而不是多次copy代码,这样在修改时只需修改一处即可。而我们同样可以通过DataBinding
来进行数据绑定。- 例如:
-
//layout_title.xml <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <import type="com.xxx.User" /> <variable name="userInfo" type="User" /> </data> <android.support.constraint.ConstraintLayout ... > <TextView ... android:text="@userInfo.name" /> </android.support.constraint.ConstraintLayout> </layout>
- 使用该布局,并传值
-
<include layout="@layout/layout_title" bind:test="@userInfo"/>
- ViewStub也是类似的用法,这里就不说了。
- DataBinding不支持merge标签
6.绑定点击事件
-
//伪代码,请勿直接CV οnclick="@()->user.testOnclick" οnclick="@(v)->user.testOnclick(v)" οnclick="@()->user.testOnclick(context)" οnclick="@BindHelp::staticClick" οnclick="@callback" //例如: <Button android:layout_width="match_parent" android:layout_height="match_parent" android:onClick="@()->user.testOnclick" />
💡文章到这里讲的都是DataBinding如何设置数据,以及通过DataBinding在xml中的一些基础使用。如果只是使用DataBinding这个功能,那就有点大材小用了。它还有一个很强大的功能我们还没有讲,那就是数据更新时自动刷新UI。
实现数据变化时自动更新UI
一个普通的实体类或者ViewModel被更新后,并不会让UI自动更新。而我们希望,当数据变更后UI要自动更新,那么要实现数据变化时自动更新UI,有三种方法可以使用,分别是BaseObservable
,ObservableField
,ObservableCollection
💡单向数据绑定:
-
BaseObservable
BaseObservable提供了两个刷新UI的方法,分别是 notifyPropertyChanged() 和 notifyChange() 。
-
第一步:修改实体类
将我们的实体类继承与BaseObservable。需要响应变化的字段,就在对应变量的get函数上加 @Bindable 。然后set中notifyChange是kotlin的写法,免去了java的getter setter的方式。成员属性需要响应变化的,就在其set函数中,notify一下属性变化,那么set的时候,databinding就会感知到。
import androidx.databinding.BaseObservable import androidx.databinding.Bindable import androidx.databinding.library.baseAdapters.BR class User() : BaseObservable() constructor(name: String, age: Int) : this() this.name = name this.age = age //这是单独在set上@bindable,name可以为声明private var name: String = "" set(value) field = value notifyPropertyChanged(BR.name) @Bindable get() = field //这是在整个变量上声明@bindable,所以必须是public的 @Bindable var age:Int = 18 set(value) field = value notifyPropertyChanged(BR.age) get() = field
-
第二步:在
Activity
中使用class MainActivity : AppCompatActivity() private val TAG = "MainActivity" private lateinit var mainBinding: ActivityMainBinding private lateinit var mainUser: User override fun onCreate(savedInstanceState: Bundle?) super.onCreate(savedInstanceState) mainBinding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main) mainUser = User("Taxze", 18) mainUser.addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() override fun onPropertyChanged(sender: Observable, propertyId: Int) when BR.user == propertyId -> Log.d(TAG, "BR.user") BR.age == propertyId -> Log.d(TAG, "BR.age") ) mainBinding.user = mainUser mainBinding.onClickPresenter = OnClickPresenter() inner class OnClickPresenter fun changeName() mainUser.name = "Taxze2222222"
-
需要注意的点
-
官方网站只是提示了开启
DataBinding
只需要在build.gradle
中加入下面这行代码 -
buildFeatures dataBinding true
但是,如果你想更好的使用
DataBinding
这是不够的,你还需要添加这些配置: -
compileOptions sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 kotlinOptions jvmTarget = '1.8'
-
🔥重点:在使用
DataBinding
得时候,BR
对象,发现调用不了,生成也会报错,运行,需要在咱们build.gradle
中进行一下配置:apply plugin: 'kotlin-kapt' kapt generateStubs = true
然后重新运行一遍代码,你就会发现,
BR
文件自动生成啦!
-
-
-
ObservableField
讲解了BaseObservable后,现在来将建最简单也是最常用的。只需要将实体类变化成这样即可:
//注意observable的属性需要public权限,否则dataBinding则无法通过反射处理数据响应 class User() : BaseObservable() var name: ObservableField<String> = ObservableField("Taxze") var age:ObservableInt = ObservableInt(18)
-
ObservableCollection
dataBinding 也提供了包装类用于替代原生的 List 和 Map,分别是 ObservableList 和 ObservableMap
实体类修改:
//伪代码,请勿直接cv class User() var userMap = ObservableArrayMap<String,String>() //使用时: mainUser.userMap["name"] = "Taxze" mainUser.userMap["age"] = "18"
使用
ObservableCollection
后,xml
与上面的略有不同,主要是数据的获取,需要指定Key
值//伪代码,请勿直接cv ... <import type="android.databinding.ObservableMap" /> <variable name="userMap" type="ObservableMap<String, String>" /> //使用时: <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:hint="Name" android:text="@userMap[`userName`]" />
💡双向数据绑定:
- 只需要在之前的单向绑定的基础上,将布局文件
@
变为@=
,用于针对属性的数据改变的同时监听用户的更新
DataBinding在RecyclerView中的使用
在RecyclerView中使用DataBinding稍有变化,我们在ViewHolder中进行binding对象的产生,以及数据对象的绑定。
我们通过一个非常简单的例子来讲解如何在RecyclerView中使用DataBinding。
效果图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uVREYUf6-1656641965880)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/22292b1c841a46e39d61666aa2262e8a~tplv-k3u1fbpfcp-watermark.image?)]
-
第一步:创建实体类
就是我们之前的,使用了BaseObservable的那个实体类,这里就不放代码了
-
第二步:创建activity_main用于存放recyclerview
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activty_main" 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="wrap_content" /> </RelativeLayout>
-
第三步:创建text_item.xml用于展示recyclerview中的每一行数据
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android"> <data> <import type="com.taxze.jetpack.databinding.User" /> <variable name="user" type="User" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="40dp" android:background="#ffffff" android:orientation="horizontal" android:paddingStart="10dp"> <TextView android:id="@+id/tv_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:text="@`这个人的姓名是` + user.name" /> <TextView android:id="@+id/tv_age" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginLeft="20dp" android:text="@String.valueOf(user.age)" /> </LinearLayout> </LinearLayout> </layout>
-
第四步:创建Adapter
有了之前的基础之后,大家看下面这些代码应该很容易了,就不做过多讲解啦
import android.content.Context import android.view.LayoutInflater import android.view.ViewGroup import androidx.databinding.DataBindingUtil import androidx.recyclerview.widget.RecyclerView import com.taxze.jetpack.databinding.databinding.TextItemBinding class FirstAdapter(users: MutableList<User>, context: Context) : RecyclerView.Adapter<FirstAdapter.MyHolder>() //在构造函数中声明binding变量,这样holder才能引用到,如果不加val/var,就引用不到,就需要在class的内写get函数 class MyHolder(val binding: TextItemBinding) Jetpack DataBinding