使用了很久的DataBinding和ViewBinding,突然想看ViewBinding是怎样生成XxxBinding类,所以打算写两篇文章来,最后发现两篇文章不够,就写了三篇。
代码中用到的示例已经上传github:示例地址
DataBinding 包含三篇博客:
DataBinding(含ViewBinding)(二)绑定原理
一、ViewBinding的使用
1.1 生成ViewBinding文件
ViewBinding
是as3.6版本加入的,只需要在app.build
中如下引入即可
android {
buildFeatures{
viewBinding = true
}
}
同步之后即可在app/build/generated/data_binding_base_class_source_out/{包名}/databinding生成 ViewBinding文件
。
1.2 使用及封装
-
Activity 中使用
// MainActivity.kt class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) initView(binding) } private fun initView(binding: ActivityMainBinding) { binding.tvTitle.text = "viewBindingTitle" val fragmentManager = supportFragmentManager val fragmentTransaction = fragmentManager.beginTransaction() fragmentTransaction.replace(R.id.container, MainFragment()) fragmentTransaction.commit() } }
通过 ActivityMainBinding.inflate()
获取到 ActivityMainBinding
后,可以直接调用其属性来进行各个View的配置。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:id="@+id/container"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_title"/>
</androidx.constraintlayout.widget.ConstraintLayout>
2. 在Fragment中使用
class MainFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = FragmentMainBinding.inflate(inflater)
initView(binding)
return binding.root
}
private fun initView(binding: FragmentMainBinding) {
binding.tvShowText.text = "在MainFragment中设置的"
}
}
对应xml为
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_show_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="我是fragment" />
</androidx.constraintlayout.widget.ConstraintLayout>
如果不想使用ViewBinding可以在根ViewGroup中加入如下属性 tools:viewBindingIgnore="true"
就不会生成 XxxBinding文件
封装到BaseActivity中
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
lateinit var binding: VB
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val parameterizedType = this.javaClass.genericSuperclass as ParameterizedType
val types = parameterizedType.actualTypeArguments
val vb = types[0] as Class<VB>
binding = vb.getMethod("inflate", LayoutInflater::class.java)
.invoke(null, layoutInflater) as VB
setContentView(binding.root)
initView()
initData()
}
protected abstract fun initView()
protected abstract fun initData()
}
继承BaseActivity就可以直接使用binding或者各个View了。同样的还有BaseFragment:
abstract class BaseFragment<VB : ViewBinding> : Fragment() {
protected lateinit var binding: VB
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val parameterizedType = javaClass.genericSuperclass as ParameterizedType
val types = parameterizedType.actualTypeArguments
val vb = types[0] as Class<VB>
binding = vb.getMethod("inflate", LayoutInflater::class.java)
.invoke(null, layoutInflater) as VB
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
initData()
}
abstract fun initView()
abstract fun initData()
}
DataBinding的使用
启用DataBinding的方法是在相应module的build.gradle中加入如下代码,同步后就能引入对DataBinding的支持:
buildFeatures{
dataBinding = true
}
二、基础入门
打开布局文件,选中根布局 ViewGroup
,按按住 Alt + 回车键
,点击 Convert to data binding layout
,或者鼠标右键选择 Show Context Action
选择 Convert to data binding
就可以生成 DataBinding 需要的布局规则:
<?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_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
和原始布局的区别在于多出一个 layout
标签将原布局包裹了起来,data
标签用于声明要用到的变量以及变量类型,data
标签的作用就像一个桥梁搭建了 View 和 Model 之间的通道,将数据(Model)与UI(View)进行绑定。
先来创建一个model
data class EmployeeBean(val id: String, val name: String, var email: String)
在xml中使用
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="com.zbt.databinding.EmployeeBean"/>
<variable
name="employeeBean"
type="EmployeeBean" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_name"
style="@style/TvStyle"
android:text="@{employeeBean.name, default=姚dae}" />
<TextView
style="@style/TvStyle"
android:text="@{employeeBean.email}" />
</LinearLayout>
</layout>
其中导入数据,可以有多种方式,使用 import
或者type中直接导入类包含包名。而 java.lang.*
包中的类会被自动导入,可以直接使用
上面的代码中生命了一个EmployeeBean类型变量 employeeBean。可以在控件中直接使用该变量。通过 @{employeeBean.name}
使TextView引用到相关的变量,DataBinding会将 employeeBean.name
映射到相应的 getter
方法。
在 Activity
中通过 DataBindingUtil
设置布局文件,并且获取 XxxBinding文件
,然后通过 XxxBinding
设置变量 employeeBean
,我们可以看到显示的数据
protected lateinit var dataBinding: DB
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
dataBinding = DataBindingUtil.setContentView(this, getLayoutId())
initData()
}
override fun getLayoutId(): Int {
return R.layout.activity_test1
}
override fun initData() {
dataBinding.employeeBean = EmployeeBean("12345", "张三", "zhangsan@163.com")
dataBinding.tvName.text = "yao da e"
}
employeeBean还没有赋值过,所以我们可以给个默认值。需要注意的是,默认值在代码中是不会显示的。
android:text="@{employeeBean.name, default=姚dae}"
也可以在代码中对单个控件赋值,需要主要的是, 下面的赋值不能和 android:text="@{employeeBean.email}"
同时使用。
dataBinding.tvEmail.text = "xxx@163.com"
当数据变化,需要重新对ViewDataBinding变量值进行重新赋值,UI才能刷新
三、单向数据绑定
上面提到,数据改变后需要重新赋值,那么如何实现自动数据更新呢。实现数据变化自动刷新UI的方式有三种:BaseObservable
、ObservableField
、ObservableArray
3.1 BaseObservable
BaseObservable
提供了 notifyChange()
和 notifyPropertyChanged(int fieldId)
两个方法,前者会刷新所有的值,后者则只更新对应 BR的flag
,该 BR
的生成通过注释 @Bindable
生成,可以通过 BR notify
特定属性关联的视图,如下model
class WorkBean : BaseObservable() {
//如果是 private 修饰符,则在成员变量的 get 方法上添加 @Bindable 注解
@Bindable
var workName: String = ""
get() = field
set(value) {
field = value
notifyPropertyChanged(com.zbt.databinding.BR.workName)
}
var workContent: String = ""
//如果是 private 修饰符,则在成员变量的 get 方法上添加 @Bindable 注解,在kotlin中属性的get()使用get()是不对在BR中生成id
@Bindable
get() = field
set(value) {
field = value
/**
* 如果是notifyChange(),可以不用@Bindable注解,@Bindable主要是在BR文件中生成相应的id,id可以关联相应属性的视图
*/
// 更新所有字段
notifyChange()
}
var workTime: String = ""
}
对于 name
字段的 set()
只更新 name
字段,对于 workContent
字段其 set()
会更新所有的字段。
看一下演示示例,三个按钮,分别更改相应数据
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="workBean"
type="com.zbt.databinding.WorkBean" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
style="@style/TvStyle"
android:text="@{workBean.workName}" />
<TextView
style="@style/TvStyle"
android:text="@{workBean.workContent}" />
<TextView
style="@style/TvStyle"
android:text="@{workBean.workTime}" />
<Button
style="@style/BtnStyle"
android:onClick="changeNameAndTime"
android:text="改变属性 name = 写代码 time = 4小时" />
<Button
style="@style/BtnStyle"
android:onClick="changeContentAndTime"
android:text="改变属性 content = 加快速度 time = 8小时" />
<Button
style="@style/BtnStyle"
android:onClick="changeTime"
android:text="改变属性 time = 2个工作日" />
</LinearLayout>
</layout>
class Test2Activity :
BaseActivity<ActivityTest2Binding>() {
private val workBean = WorkBean()
override fun getLayoutId(): Int {
return R.layout.activity_test2
}
override fun initData() {
workBean.workName = "dataBinding"
workBean.workContent = "数据刷新"
workBean.workTime = "1个小时"
/**
* 注册实现androidx.databinding.Observable中的OnPropertyChangedCallback监听器,当可观察对象的数据发生更改时,
* 监听器就会收到通知,其中 propertyId 就是@Bindable 编译后生成的字段
*/
workBean.addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(sender: Observable?, propertyId: Int) {
when (propertyId) {
BR.workName -> println("BR.workName changed")
BR._all -> println("all changed")
else -> println("other changed")
}
}
})
dataBinding.workBean = workBean
}
fun changeNameAndTime(view: View) {
workBean.workName = "写代码"
workBean.workTime = "4小时"
}
fun changeContentAndTime(view: View) {
workBean.workContent = "加快速度"
workBean.workTime = "8小时"
}
fun changeTime(view: View) {
workBean.workTime = "2个工作日"
}
}
可以看到,更改 workName
只更新了 workName
对应的视图;更改 workContent
则刷新了所有视图;对于只更改 workTime
字段,则不回更新 TextView
。
3.2 ObservableField
继承 Observable
需要设置 notifyXxx()
才能更新UI,那么 ObservableField
则更为简单,不需要notify操作即可更新UI。ObservableField
也是间接继承自 Observable
,那么可以理解为其是对 BaseObsevable
中字段的注解和刷新等操作的封装。官方提供了对八大基本类型数据的封装,如 ObservableByte
、ObservableInt
等及 ObservableParcelable
,也可以通过 Observable<T>
泛型来声明其他类型数据。
在Model对每个字段都使用相应的 Obserable
,如下:
class ObservableWorkBean(name: String, card: Boolean) {
var name: ObservableField<String> = ObservableField(name)
var card: ObservableBoolean = ObservableBoolean(card)
}
在使用时,用 ObservableFiled.set()
赋值即可刷新UI
override fun initData() {
val workBean = ObservableWorkBean("张三", false)
dataBinding.workBean = workBean
dataBinding.clickHandler = ObservableWorkBeanChangeHandler(workBean)
}
class ObservableWorkBeanChangeHandler(val workBean: ObservableWorkBean) {
fun changeName() {
/**
* ObservableField 中的set()方法
*/
workBean.name.set("李四 ${Random.nextInt(1000)}")
}
fun changeCard() {
workBean.card.set(true)
}
}
3.3 ObservableArray
DataBinding
提供了替换 List
和 Map
的字段,分别为 ObservableArrayList
和ObservableArrayMap
,所以我将其命名为 ObservableArray
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="strs"
type="androidx.databinding.ObservableList<String>" />
<variable
name="map"
type="androidx.databinding.ObservableArrayMap<String,String>" />
<variable
name="index"
type="int" />
<variable
name="key"
type="String" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
style="@style/TvStyle"
android:text="@{strs[index]}" />
<TextView
style="@style/TvStyle"
android:text="@{map[key]}" />
<Button
style="@style/BtnStyle"
android:onClick="changeIndexAndKey"
android:text="改变index和key" />
<Button
style="@style/BtnStyle"
android:onClick="changeValue"
android:text="改变value" />
</LinearLayout>
</layout>
Test4Activity
override fun initData() {
val list = ObservableArrayList<String>()
list.add("12345")
list.add("67890")
val map = ObservableArrayMap<String, String>()
map.put("map", "key shi map")
map.put("list", "key shi list")
dataBinding.strs = list
dataBinding.map = map
dataBinding.index = 0
dataBinding.key = "map"
}
fun changeIndexAndKey(view: View) {
dataBinding.index = 1
dataBinding.key = "list"
}
fun changeValue(view: View) {
dataBinding.strs?.set(1, "abc")
dataBinding.map?.put("list", "key shi list value changed")
}
可以看到改变index和改变value都会刷新UI,需要注意的是要判断下标越界等情况。
DataBinding
中只提供了ObservableArrayList
和ObservableArrayMap
。如若要使用HashMap或者是其结构的ObservableList或者是ObservableMap可以如下:
class ObservableHashMap<K, V> : HashMap<K, V>(), ObservableMap<K, V> {
override fun addOnMapChangedCallback(callback: ObservableMap.OnMapChangedCallback<out ObservableMap<K, V>, K, V>?) {
TODO("Not yet implemented")
}
override fun removeOnMapChangedCallback(callback: ObservableMap.OnMapChangedCallback<out ObservableMap<K, V>, K, V>?) {
TODO("Not yet implemented")
}
}
class ObservableLinkedList<T> : LinkedList<T>(), ObservableList<T> {
override fun addOnListChangedCallback(callback: ObservableList.OnListChangedCallback<out ObservableList<T>>?) {
TODO("Not yet implemented")
}
override fun removeOnListChangedCallback(callback: ObservableList.OnListChangedCallback<out ObservableList<T>>?) {
TODO("Not yet implemented")
}
}
四、双向数据绑定
双向数据绑定的意思是当数据改变时视图会刷新,当视图改变时也可以同时改变数据
4.1 双向数据绑定
如下例,当 EditText
输入的内容变化时,会同步到变量 workBean
中,绑定方式比单向绑定中多了一个等号:android:text="@={workBean.name}"
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="workBean"
type="com.zbt.databinding.ObservableWorkBean" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"
android:orientation="vertical">
<TextView
style="@style/TvStyle"
android:text="model - View : 双向数据绑定" />
<TextView
style="@style/TvStyle"
android:text="@{workBean.name}" />
<EditText
style="@style/EtStyle"
android:layout_marginTop="10dp"
android:text="@={workBean.name}" />
</LinearLayout>
</layout>
4.2 事件绑定
事件绑定也是一种变量绑定,只不过设置的变量是回调接口,事件回调多用于以下事件:
- android:onclick
- andorid:onLongClick
- android:onTextChanged
……
如设置 onClick
和 afterTextChanged
<TextView
style="@style/TvStyle"
android:layout_marginTop="60dp"
android:text="事件绑定" />
<TextView
style="@style/TvStyle"
android:onClick="@{()->eventBinding.onNameClick(workBean)}"
android:text="@{workBean.name}" />
<EditText
style="@style/EtStyle"
android:layout_marginTop="10dp"
android:afterTextChanged="@{eventBinding.onAfterTextChanged}"
android:hint="@{workBean.name}" />
Test5Activity
class Test5Activity : BaseActivity<ActivityTest5Binding>() {
override fun getLayoutId(): Int {
return R.layout.activity_test5
}
override fun initData() {
val workBean = ObservableWorkBean("张三", true)
dataBinding.workBean = workBean
dataBinding.eventBinding = EventBinding(this, workBean)
}
class EventBinding(private val context: Context, private val workBean: ObservableWorkBean) {
fun onNameClick(observableWorkBean: ObservableWorkBean) {
Toast.makeText(context, "card: ${observableWorkBean.card.get()}", Toast.LENGTH_LONG).show()
}
fun onAfterTextChanged(s: Editable) {
workBean.name.set(s.toString())
}
}
}
演示如下:
事件绑定中方法的引用可以使用,可以和保持事件回调方法一致:android:afterTextChanged="@{eventBinding.onAfterTextChanged}"
,也可以使用lambda不遵循默认的方法签名 android:onClick="@{()->eventBinding.onNameClick(workBean)}"
五、运算符的使用
5.1 基础运算符
DataBinding支持在布局文件中使一些基础的运算符、表达式和关键字:
- 算数运算+ - / * %
- 字符串合并 +
- 逻辑 && ||
- 二元 & | ^
- 一元 + - ! ~
- 移位 >> >>> <<
- 比较 == > < >= <=
- Instanceof
- character, String, numeric, null
- Cast
- 方法调用
- Field 访问
- Array 访问 []
- 三元运算符 ?:
5.2 ??运算符
空合并运算符 ?? 会取第一个不为 null
的值作为返回值,前面的不为 null
则取前面的值,否则取后面的值
<TextView
android:id="@+id/tv_email"
style="@style/TvStyle"
android:text="@{employeeBean.email??employeeBean.name}" />
5.3 属性控制
例如控制 View
的显隐
<import type="android.view.View" />
<TextView
style="@style/TvStyle"
android:text="view 可见测试"
android:visibility="@{workBean.card? View.VISIBLE : View.INVISIBLE}" />
避免空指针异常
DataBinding
也会自动帮助我们避免空指针异常 例如,如果 "@{workBean.name}"
中 workBean
为 null
的话,workBean.name
会被赋值为默认值 null
,而不会抛出空指针异常
六、集合的使用
DataBinding支持在布局文件中使用数据、List、Set和Map,通过如array[index]获取元素
为了和 varible
的<>标签做区分,在使用泛型的<>需要转义
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="array"
type="String[]" />
<variable
name="list"
type="java.util.List<String>" />
<variable
name="map"
type="java.util.Map<String, String>" />
<variable
name="set"
type="java.util.Set<String>" />
<variable
name="sparse"
type="android.util.SparseArray<String>" />
<variable
name="index"
type="int" />
<variable
name="key"
type="String" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"
android:orientation="vertical">
<TextView
style="@style/TvStyle"
android:text="集合的使用" />
<TextView
style="@style/TvStyle"
android:text="@{array[index]}" />
<TextView
style="@style/TvStyle"
android:text="@{sparse[index]}" />
<TextView
style="@style/TvStyle"
android:text="@{list[index]}" />
<TextView
style="@style/TvStyle"
android:text="@{map[key]}" />
<TextView
style="@style/TvStyle"
android:text='@{set.contains(key)?key:"不包含"}' />
</LinearLayout>
</layout>
Test6Activity:
class Test6Activity : BaseActivity<ActivityTest6Binding>() {
override fun getLayoutId(): Int {
return R.layout.activity_test6
}
override fun initData() {
dataBinding.array = arrayOf("array")
val sparseArray = SparseArray<String>()
sparseArray[0] = "sparseArray"
dataBinding.sparse = sparseArray
dataBinding.list = listOf("list")
val map = mutableMapOf<String, String>()
map.put("databinding", "map")
dataBinding.map = map
val set = mutableSetOf<String>()
set.add("set")
dataBinding.set = set
dataBinding.key = "databinding"
dataBinding.index = 0
}
}
七、标签include和viewStub中使用
对于 include
的布局文件,一样是支持通过 DataBinding
来进行数据绑定的。
在布局文件中引用 viewStub 布局
view_include.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">
<data>
<variable
name="workBean"
type="com.zbt.databinding.WorkBean" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/teal_200"
android:gravity="center"
android:padding="20dp"
android:text="@{workBean.workName}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
view_stub.xml
:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="workBean"
type="com.zbt.databinding.WorkBean" />
</data>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#42A5F5"
android:gravity="center"
android:padding="20dp"
android:text="@{workBean.workContent}" />
</layout>
使用include和viewStub
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="workBean"
type="com.zbt.databinding.WorkBean" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"
android:orientation="vertical">
<Button
android:id="@+id/btn_show"
style="@style/BtnStyle"
android:text="显示viewStub" />
<include
layout="@layout/view_include"
bind:workBean="@{workBean}" />
<ViewStub
android:id="@+id/view_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/view_stub"
bind:workBean="@{workBean}" />
</LinearLayout>
</layout>
如果在 xml
中没有使用 bind:workBean="@{workBean}"
对 ViewStub
进行数据绑定,则可以使用 setOnInflateListener
回调函数,在回调函数中进行数据绑定
八、注解
DataBinding提供了如 Bindable
、BindingAdapter
、 BindingConversion
、BindingMethod
、InverseMethod
等注解,其中Bindable
在数据绑定中已经使用过了,是用于将数据与视图绑定然后通知。
BindingAdapter
注解用于修改原有属性或者自定义属性。注解的对象可以是已有的属性,如 android:text
、android:layout_width
等,也可以自定义属性然后在布局文件中使用。
BindingConversion
注解用于对数据进行转换,或者类型转换
BindingMethod
DataBinding默认可以在布局中使用setter方法作为自定义属性, 但是如果不是setter格式的方法就要使用BindingMethod注解了。通过创建一个自定义属性来关联一个类中已有的方法.
首先我们在代码中使用者这几个注解
class Test8Activity : BaseActivity<ActivityTest8Binding>() {
companion object {
/**
* 更改原生的,这种情况下,整个报下的TextView的android:text都会跟更改
*/
@JvmStatic
@BindingAdapter("android:text")
fun setText(tv: TextView, text: String) {
tv.text = "$text 是大佬"
}
/**
* 自定义
*/
@JvmStatic
@BindingAdapter("text")
fun printText(tv: TextView, text: String) {
println("获取到的text:$text")
}
// // 放在Companion object 会报这个错误:@BindingConversion is only allowed on public static methods conversionString(java.lang.String)
// @BindingConversion
// fun conversionString(text: String): String? {
// return "$text-conversionString"
// }
}
override fun getLayoutId() = R.layout.activity_test8
override fun initData() {
val employeeBean = EmployeeBean("12345", "张三", "12345@163.com")
dataBinding.employeeBean = employeeBean
dataBinding.tvtToast.setOnClickListener {
val workBean = WorkBean()
dataBinding.workBean = workBean
workBean.workName = "码字"
}
}
}
@BindingConversion
fun convertStringToColor(str: String): Int {
return when (str) {
"红色" -> Color.parseColor("#FF1493")
"橙色" -> Color.parseColor("#0000FF")
else -> Color.parseColor("#FF4500")
}
}
@BindingMethods(
BindingMethod(
type = TextView::class,
attribute = "showToast",
method = "showToast"
)
)
class TextViewToast(context: Context, attrs: AttributeSet) :
androidx.appcompat.widget.AppCompatTextView(context, attrs) {
fun showToast(s: String?) {
if (TextUtils.isEmpty(s)) {
return
}
Toast.makeText(context, s, Toast.LENGTH_LONG).show()
}
}
然后在 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">
<data>
<import type="com.zbt.databinding.Test8Activity.Companion" />
<variable
name="employeeBean"
type="com.zbt.databinding.EmployeeBean" />
<variable
name="workBean"
type="com.zbt.databinding.WorkBean" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"
android:orientation="vertical">
<TextView
style="@style/TvStyle"
android:text="@{employeeBean.name}"
app:text="@{employeeBean.email}" />
<TextView
android:id="@+id/textView1"
style="@style/BtnStyle"
android:layout_marginTop="20dp"
android:padding="20dp"
android:text="纯蓝"
android:textColor='@{"蓝色"}' />
<TextView
style="@style/BtnStyle"
android:layout_marginTop="20dp"
android:padding="20dp"
android:text="橙色"
android:textColor='@{"橙色"}' />
<TextView
style="@style/BtnStyle"
android:layout_marginTop="20dp"
android:padding="20dp"
android:text="红色"
android:textColor='@{"红色"}' />
<com.zbt.databinding.TextViewToast
android:id="@+id/tvt_toast"
style="@style/BtnStyle"
android:layout_marginTop="20dp"
android:padding="20dp"
android:text="点我"
app:showToast="@{workBean.workName}"
android:background="@color/teal_200"/>
</LinearLayout>
</layout>
- BindAdapter 更改原生属性,整个包下的属性都会被更改
- BindAdapter 自定义属性,则在使用的地方更改
- BindingConversion 需要写成顶层函数,或者是object类中定义
- BindingMethod、BindingMethods用于类的注释
这里的例子可以查看代码中的 Test8Activity
和 Test9Activity
RecyclerView中使用
在代码中 RecyclerView
是经常使用的,结合DataBinding可以使代码的结构更为简洁。
因为 RecyclerView
是通过 adapter
设置数据的,所以没法通过 DataBinding设置数据,那么接下来我们看一下使用的。
item_view.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">
<data>
<variable
name="employeeBean"
type="com.zbt.databinding.EmployeeBean" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{employeeBean.name}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{employeeBean.id}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_email"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{employeeBean.email}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/tv_name"
app:layout_constraintRight_toLeftOf="@id/tv_id"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
adapter
class EmployeeAdapter(private val employeeBeans: List<EmployeeBean>) :
RecyclerView.Adapter<EmployeeAdapter.EmployeeViewHolder>() {
inner class EmployeeViewHolder(val binding: ItemViewBinding) :
RecyclerView.ViewHolder(binding.root) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EmployeeViewHolder {
return EmployeeViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.item_view,
parent,
false
)
)
}
override fun onBindViewHolder(holder: EmployeeViewHolder, position: Int) {
holder.binding.employeeBean = employeeBeans[position]
}
override fun getItemCount(): Int {
return employeeBeans.size
}
}
Activity中设置数据:
class Test10Activity : AppCompatActivity() {
private val employeeObservableList = ObservableArrayList<EmployeeBean>().apply {
for (i in 0..32) {
add(
EmployeeBean(
"${Random().nextInt()}",
"张三${Random().nextInt(100)}",
"${Random().nextInt(1000)}@163.com}"
)
)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test10)
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.layoutManager = LinearLayoutManager(this)
val employeeAdapter = EmployeeAdapter(employeeObservableList)
employeeAdapter.notifyDataSetChanged()
employeeObservableList.addOnListChangedCallback(
DynamicDataChangeCallback<ObservableArrayList<EmployeeBean>>(
employeeAdapter
)
)
recyclerView.adapter = employeeAdapter
}
fun addItem(view: View) {
if (employeeObservableList.size >= 1) {
val employeeBean = EmployeeBean(
"${Random().nextInt()}",
"张三${Random().nextInt(100)}",
"${Random().nextInt(1000)}@163.com}"
)
employeeObservableList.add(0, employeeBean)
}
}
fun addItemList(view: View) {
if (employeeObservableList.size >= 2) {
val employeeBeans: MutableList<EmployeeBean> = ArrayList<EmployeeBean>()
for (i in 0..4) {
val employeeBean = EmployeeBean(
"${Random().nextInt()}",
"张三${Random().nextInt(100)}",
"${Random().nextInt(1000)}@163.com}"
)
employeeBeans.add(employeeBean)
}
employeeObservableList.addAll(0, employeeBeans)
}
}
fun removeItem(view: View) {
if (employeeObservableList.size >= 2) {
employeeObservableList.removeAt(1)
}
}
fun updateItem(view: View) {
if (employeeObservableList.size >= 2) {
val employeeBean: EmployeeBean = employeeObservableList[1]
employeeBean.name = "张三是大爷"
employeeObservableList[1] = employeeBean
}
}
}
可以看到在 item_view.xml
中绑定了数据,adapter要整洁很多。
其中 ObservableList
中有 OnListChangedCallback
对数据的List数据增删的监听回调。这样可以ObservableArrayList数据改变时,能直接修改 界面
public interface ObservableList<T> extends List<T> {
void addOnListChangedCallback(OnListChangedCallback<? extends ObservableList<T>> callback);
void removeOnListChangedCallback(OnListChangedCallback<? extends ObservableList<T>> callback);
abstract class OnListChangedCallback<T extends ObservableList> {
public abstract void onChanged(T sender);
public abstract void onItemRangeChanged(T sender, int positionStart, int itemCount);
public abstract void onItemRangeInserted(T sender, int positionStart, int itemCount);
public abstract void onItemRangeMoved(T sender, int fromPosition, int toPosition,
int itemCount);
public abstract void onItemRangeRemoved(T sender, int positionStart, int itemCount);
}
}
总结
上面大概总结了现阶段 ViewBinding和DataBinding的使用方法,注意事项。系统提供的修改方法基本够用,其他的我们可以自己通过 @BindingMethods
方法去自定义属性,做操作,比如:可以通过 @BindingMethods
自定OnClick实现防双击等等