目录
定义
将程序中的变量和方法通过声明的方式绑定到XML布局上。
(Android原文:数据绑定库是一个支持库,可让您使用声明性格式(而不是以程序化方式)将布局中的界面组件绑定到应用中的数据源)
使用步骤
一、在app模块的build.gradle文件中启用dataBinding构建选项。
android {
...
buildFeatures {
dataBinding true
}
}
二、编写数据绑定文件布局,以根标记layout开头,后跟data元素和view根元素。
例如:aty_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 用于声明需要绑定的变量-->
<data>
<variable
name="user"
type="com.lifeidroid.databindingsimple.entity.User" />
</data>
<!-- 用于声明需要绑定的布局-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 用@{}语法来赋值属性-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"></TextView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"></TextView>
</LinearLayout>
</layout>
三、在Activity中使用视图绑定。
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.lifeidroid.databindingsimple.databinding.AtyMainBinding
import com.lifeidroid.databindingsimple.entity.User
class MainAcitivty : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//方式一 可以直接使用dataBinding自带的工具类DataBindingUtil直接进行绑定。也可以用下面两种方式,先获取视图然后进行绑定
val binding:AtyMainBinding = DataBindingUtil.setContentView(this,R.layout.aty_main)
//方式二
// val binding: AtyMainBinding = AtyMainBinding.inflate(layoutInflater)
// setContentView(binding.root)
//方式三
// val binding: AtyMainBinding =
// DataBindingUtil.inflate(layoutInflater, R.layout.aty_main, null, false)
// setContentView(binding.root)
//为布局变量关联数据
binding.user = User("张","三丰")
}
}
Fragment的视图绑定可以参考方式二或方式三,先获取视图,然后进行绑定。
XML 中常用的表达式语言
常见功能运算符
类型 | 示例 |
---|---|
数学: | android:progress="@{user.score +5}" |
字符串串联: | android:text="@{user.firstName+user.lastName}" |
逻辑: | android:checked="@{user.age > 10 && user.age < 15}" |
二进制文件: | |
一元组: | |
位运算符: | android:onClick="@{()-> viewModel.btnClick( 0x10 >> 2 ) }" |
比较: | |
| android:checked="@{ user instanceof User }" |
分类: | |
字面量,例如字符、字符串、数字、 | |
类型转换 | |
方法调用 | android:onClick="@{()-> viewModel.btnClick( 0x10 >> 2 ) }" |
字段访问 | android:text="@{user.lastName}" |
数组访问: | android:text="@{userList[0].name}" |
三元运算符: | android:text="@{user.displayName != null ? user.displayName : user.lastName}" |
Null 合并运算符 | android:text="@{user.displayName ?? user.lastName}" |
引用同一个布局控件的值 | <EditText android:id="@+id/example_text" .../> <!--TextView引用EditText中的值--> <TextView android:id="@+id/example_output" android:text="@{exampleText.text}" .../> |
在布局文件中不支持this、super、new、Explicit generic invocation
集合类型数据的设置或访问
<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<!--集合类型数据,需要引入对应集合类型-->
<variable name="list" type="List<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<String, String>"/>
<variable name="index" type="int"/>
<variable name="key" type="String"/>
</data>
...
android:text="@{list[index]}"
...
android:text="@{sparse[index]}"
...
android:text="@{map[key]}"
...
表达式中的字符串可用以下两种方式
android:text='@{map["firstName"]}'
android:text="@{map[`firstName`]}"
资源文件的引入
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
<string name="nameFormat">%s - %s</string>
android:text="@{@string/nameFormat(user.firstName , user.lastName)}"
<!--表达式中用到R文件中的值,需要在data中引入对应的R文件-->
<import type="com.example.android.databinding.basicsample.R"/>
app:srcCompat="@{user.likes > 4 ? R.drawable.ic_max : R.drawable.ic_min }"
XML 中事件处理
事件属性名称由监听器方法名称决定,也可以直接这只监听器中对应的方法,一般为on开头的方法,如下面示例:
Button | SetOnClickListener->onClick | android:onClick="@{}" |
setOnLongClickListener ->onLongClick | android:onLongClick="@{}" | |
CheckBox | setOnCheckedChangeListener->onCheckedChanged | android:onCheckedChanged="@{}" |
方式一:方法引用
将事件处理的方法引用绑定到XML中对应的事件属性上。相当于事件属性在XML中不做任何业务处理。(注意:因为是方法的引用,所以被引用方法的参数和返回值必须与事件属性对应的方法相同。)
class MyHandlers {
//参数和返回值必须与setOnClickListener中onClick方法的相同
fun onClickFriend(view: View) { ... }
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.MyHandlers"/>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
android:onClick="@{handlers::onClickFriend}"/>
<!--这里直接将方法的引用绑定到了onClick方法属性上-->
</LinearLayout>
</layout>
方式二:监听器绑定
通过 lambda 表达式在XML中给事件属性设置一个监听器,事件触发带动相应的监听器。在监听器内可以做一些逻辑,但要避免复杂的监听器。
class Presenter {
fun onSaveClick(task: Task){}
fun onSaveClick2(view: View, task: Task){}
fun onCompletedChanged(task: Task, completed: Boolean){}
//如果您正在监听的事件返回类型不是 void 的值,则您的表达式也必须返回相同类型的值。
fun onLongClick(view: View, task: Task): Boolean { }
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="task" type="com.android.example.Task" />
<variable name="presenter" type="com.android.example.Presenter" />
</data>
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
<!--如果没用到事件方法中的参数,可以忽略方法的所有参数-->
<Button android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onClick="@{() -> presenter.onSaveClick(task)}" />
<!--可以为参数重新命名-->
<Button android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onClick="@{(mView) -> presenter.onSaveClick2(mView, task)}" />
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />
<!--注意事件属性的返回值-->
<Button android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}" />
</LinearLayout>
</layout>
data标签中的操作
import
在XML布局文件中导入引用类,比如用到的实体类、常量类、工具类等。
<data>
<!--表达式中用到View类的VISIBLE和GONE常量,需要对View类进行import导入。-->
<import type="android.view.View" />
<!--当引用类有冲突时,可以设置别名-->
<import
alias="CustomView"
type="com.lifeidroid.databindingsimple.view.View" />
<!--当变量中用到List或者其他集合类时需要导入对应的类-->
<import type="java.util.List" />
<!--当变量中用到实体类时需要导入对应的类-->
<import type="com.lifeidroid.databindingsimple.entity.User" />
<!--当表达式中用到工具类或自定义方法时时需要导入-->
<import type="com.lifeidroid.databindingsimple.utils.StringUtils"/>
</data>
variable
每一个variable元素就相当于是XML布局文件(aty_main.xml)对应的绑定类(AtyMainBinding)上创建的变量,在布局文件绑定的时候可以设置该变量。
布局文件中创建了user、image、note三个变量。系统会自动生成BR类,里面包含了生成的变量索引
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.graphics.drawable.Drawable"/>
<variable name="user" type="com.example.User"/>
<variable name="image" type="Drawable"/>
<variable name="note" type="String"/>
</data>
....
</layout>
在Activity中对布局文件的变量进行绑定
class MainAcitivty : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: AtyMainBinding = DataBindingUtil.setContentView(this, R.layout.aty_main)
// 为布局变量关联数据
// 方式一、直接为变量赋值
binding.user = User("张", "三丰", -20, 8, true)
// 方式二、根据变量索引赋值
// binding.setVariable(BR.user, User("张", "三丰", -20, 8, true))
binding.image = getDrawable(R.drawable.ic_launcher_background)
binding.note = "这是一个测试"
}
变量在未赋值时会自动分配默认值(在生成的绑定类(AtyMainBinding)中,每个描述的变量都有一个对应的 setter 和 getter。在调用 setter 之前,这些变量会采用默认的托管代码值 - null
用于引用类型,0
用于 int
,false
用于 boolean
,等等)。
include布局标签中的传值操作
以下示例展示了从main.xml布局中的user传递给 name.xml
和 contact.xml
布局文件中的 user
变量:
<?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="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</LinearLayout>
</layout>
数据绑定不支持 include 作为 merge 元素的直接子元素。例如:
<?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="user" type="com.example.User"/>
</data>
<merge><!-- Doesn't work -->
<include layout="@layout/name"
bind:user="@{user}"/>
</merge>
</layout>
可观察数据的使用
以上介绍的都是传统对象在数据绑定中的使用,但是这些对象的变化不会通知UI(XML)更新。如果修改了数据源需要立即刷新UI,就需要用到可观察数据类型。
可观察字段
使某个字段变成可观察类型,只需要将字段替换成对应的可观察类型。
传统字段 | 可观察字段 |
Boolean | ObservableBoolean |
Byte | ObservableByte |
Char | ObservableChar |
Short | ObservableShort |
Int | ObservableInt |
Long | ObservableLong |
Float | ObservableFloat |
Double | ObservableDouble |
Parcelable | ObservableParcelable |
String | ObservableField<String> |
示例:
class User {
val firstName = ObservableField<String>()
val lastName = ObservableField<String>()
val age = ObservableInt()
}
...
//字段的设置和取值
user.firstName.set("张三丰")
user.firstName.get()
可观察集合
使用可变集合来保存数据,使集合具有可观察性。
传统集合 | 可观察集合 |
ArrayList | ObservableArrayList |
List | ObservableList |
ArrayMap | ObservableArrayMap |
Map | ObservableMap |
示例:注意'<' 、'>'的转义
//集合定义
ObservableArrayMap<String, Any>().apply {
put("firstName", "Google")
put("lastName", "Inc.")
put("age", 17)
}
//布局文件
<data>
<import type="android.databinding.ObservableMap"/>
<variable name="user" type="ObservableMap<String, Object>"/>
</data>
…
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text="@{String.valueOf(1 + (Integer)user.age)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
可观察对象
使对象的属性更改时能及时刷新页面,需要将对象转为可观察对象。
步骤:
- 继承BaseObservable类
- 给getter分配Bindable注解
- 在setter中调用notifyPropertyChanged()方法
示例:
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import com.lifeidroid.databindingsimple.BR
class User2 : BaseObservable() {
@get:Bindable
var firstName: String = ""
set(value) {
field = value
//Bindable 注解会在编译期间在 BR 类文件中生成一个条目。
notifyPropertyChanged(BR.firstName)
}
@get:Bindable
var age: Int = 0
set(value) {
field = value
notifyPropertyChanged(BR.age)
}
}
注:如遇到BR的字段不生成或者unresolved reference: BR的错误时,需要将app的build.gradle进行如下配置:
plugins {
...
id("kotlin-kapt")
}
android {
...
kapt {
generateStubs = true
}
}
dependencies {
...
//版本与project的build.gradle中com.android.application版本保持一致
kapt("androidx.databinding:databinding-compiler:8.2.1")
}
生命周期感知型对象
XML中可以绑定数据源StateFlow或LiveData,数据源发生变化会自动通知页面有关数据更新。这种绑定能够感知生命周期,在界面显示在屏幕上时才会触发。
StateFlow示例:
//XML
...
<data>
<variable
name="viewModel"
type="com.lifeidroid.databindingsimple.ScheduleViewModel" />
</data>
<TextView
android:id="@+id/name"
android:text="@{viewmodel.username}" />
//Activity-------------------------------------------------------------
class MainActivity : AppCompatActivity() {
val viewModel:MainViewModel = ScheduleViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
val binding: UserBinding = DataBindingUtil.setContentView(this, R.layout.aty_main)
// 将页面的生命周期绑定到Activity
binding.lifecycleOwner = this
// 设置页面的viewModel属性
binding.viewModel = viewModel
}
}
//ViewModel-------------------------------------------------------------
class ScheduleViewModel : ViewModel() {
private val _username = MutableStateFlow<String>("")
val username: StateFlow<String> = _username
init {
viewModelScope.launch {
_username.value = Repository.loadUserName()
}
}
}
双向数据绑定
以上介绍的为单向数据绑定(数据变更—>页面更新)。但如果页面数据改变也会同步到数据源变化,这样就是双向数据绑定。
实现方式:
方式一:运用事件监听将页面状态通知数据源变化
<CheckBox
android:id="@+id/rememberMeCheckBox"
<!--绑定数据源-->
android:checked="@{viewmodel.rememberMe}"
<!--通过对onCheckChanged方法监听同步数据源-->
android:onCheckedChanged="@{viewmodel.rememberMeChanged}"
/>
方式二:@={}表达式
<CheckBox
android:id="@+id/rememberMeCheckBox"
android:checked="@={viewmodel.rememberMe}"
/>
binding 适配器
RecycleView中使用DataBinding
adapter示例:
class ListAdapter(var context: Context,var datas:List<User3>) : RecyclerView.Adapter<ListAdapter.MyHolder>() {
class MyHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyHolder {
var binding = ItemListBinding.inflate(LayoutInflater.from(context),parent,false)
return MyHolder(binding.root)
}
override fun getItemCount(): Int {
return datas.size
}
override fun onBindViewHolder(holder: MyHolder, position: Int) {
//通过getBinding方法返回绑定类
var binding = DataBindingUtil.getBinding<ItemListBinding>(holder.itemView)
//绑定变量方式一
binding!!.user = datas[position]
//绑定变量方式二
//binding!!.setVariable(BR.user,datas[position])
//通过binding可以获取视图控件
binding!!.tvName.setOnClickListener {
Log.d("TAG", "onBindViewHolder: "+datas[position].firstName)
}
//当变量发生改变后,需要调用下面方法强制立即绑定,让页面改变
binding!!.executePendingBindings()
}
}
「完」
以上为个人参考Android官网整理的基本用法,可能有疏漏,仅供参考。