DataBinding是一个实现数据和UI绑定的框架,同时也是实现MVVM模式所依赖的工具。
今天文章主要记录一下DataBinding在Kotlin中的简单使用。
配置
在应用的build.gradle文件中添加以下代码:
android {
...
dataBinding {
enabled = true
}
...
}
基本使用功能
- 替代findViewById
布局文件根节点必须是,同时layout只能包含一个View标签,不能直接包含。layout里面的View标签就是我们要展示的布局。
<?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"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/tv_nick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
</layout>
布局通过DataBindingUtils.setContentView()加载到代码中,而且会生成对应一个Binding对象,对象名是布局文件文称加上Binding后缀。
通过Binding对象.id名称,就相当于拿到了布局中指定id的控件了,使用起来和findViewById获取的控件是一样的。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val dataBinding: ActivityMainBinding =
DataBindingUtil.setContentView(this, R.layout.activity_main)
dataBinding.tvNick.text = "这是昵称"
}
}
- 绑定基本数据类型及String类型
在布局文件根节点下添加标签,里面就是要跟UI进行绑定的数据。name是自定义的数据名字,type表示该数据的类型。
在布局中是通过@{}来绑定数据的,{}中是布局中该控件属性对应的数据类型数据
<?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="content"
type="String" />
<variable
name="enabled"
type="boolean" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<Button
android:id="@+id/bt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="@{enabled}"
android:text="@{content}" />
</LinearLayout>
</layout>
同样通过Binding对象.name名字,就可以拿到指定名字的数据对象了,通过给数据对象赋值,就能改变对应绑定的UI属性。
给控件设置点击事件,发现其实点击无效,因为在布局文件中给cilckable属性绑定了enabled数据,而在代码中设置enabled数据值为false,所以点击事件无效。enabled数据值改为true后,点击事件就会有效。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val dataBinding: ActivityMainBinding =
DataBindingUtil.setContentView(this, R.layout.activity_main)
dataBinding.enabled = false
dataBinding.content = "这是一个按钮"
dataBinding.bt.setOnClickListener {
Toast.makeText(this, "成功点击了", Toast.LENGTH_SHORT).show()
}
}
}
- 绑定Model数据
Model数据类型:
class User constructor(var name: String, var nick: String, var isMale: Boolean, var age: Int)
标签中数据type为model数据类型的全路径,或者通过标签先导入model数据类型,然后type指定为model数据类型即可。
在布局中是通过@{}来绑定数据,如果绑定的属性需要String类型,但数据类型不是String类型,必须转化为String。
<?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>
<import type="com.example.myapplication.User" />
<variable
name="user"
type="User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}" />
<!-- boolean 要转为String来显示 不然编译异常-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(user.isMale)}" />
<!-- age是int类型 必须转化为String 不然编译异常-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(user.age)}" />
</LinearLayout>
</layout>
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val dataBinding: ActivityMainBinding =
DataBindingUtil.setContentView(this, R.layout.activity_main)
val user = User("名称", "昵称", true, 20)
dataBinding.user = user
}
}
- 绑定接口数据
接口数据:
interface EventListener {
fun onClick1(view: View)
fun onClick2(view: View)
fun onClick3(string: String)
}
同样type需要为接口的全路径,然后在布局中通过@{}来绑定数据。接口绑定有三种写法:event.onClick1、event::onClick2、()->event.onClick3(title4)
<?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="event"
type="com.example.myapplication.EventListener" />
<variable
name="title1"
type="String" />
<variable
name="title2"
type="String" />
<variable
name="title3"
type="String" />
<variable
name="title4"
type="String" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<Button
android:id="@+id/bt1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{event.onClick1}"
android:text="@{title1}" />
<Button
android:id="@+id/bt2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{event::onClick2}"
android:text="@{title2}" />
<Button
android:id="@+id/bt3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{()->event.onClick3(title4)}"
android:text="@{title3}" />
</LinearLayout>
</layout>
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val dataBinding: ActivityMainBinding =
DataBindingUtil.setContentView(this, R.layout.activity_main)
dataBinding.title1 = "这是按钮1"
dataBinding.title2 = "这是按钮2"
dataBinding.title3 = "这是按钮3"
dataBinding.title4 = "这是点击更新了"
dataBinding.event = object : EventListener {
override fun onClick1(view: View) {
Toast.makeText(this@MainActivity, "绑定接口1点击成功", Toast.LENGTH_SHORT).show()
}
override fun onClick2(view: View) {
Toast.makeText(this@MainActivity, "绑定接口2点击成功", Toast.LENGTH_SHORT).show()
}
override fun onClick3(string: String) {
dataBinding.title3 = string
}
}
}
}
- 绑定静态方法
静态方法:
class Utils {
companion object {
//注解方式实现静态方法只能用在单例类中或companion object关键中
@JvmStatic
fun getContent(user: User): String {
return user.name
}
}
}
在标签中通过导入路径,在布局中通过@{}直接调用静态方法即可。
<?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>
<import type="com.example.myapplication.Utils" />
<variable
name="user"
type="com.example.myapplication.User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{Utils.getContent(user)}" />
</LinearLayout>
</layout>
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val dataBinding: ActivityMainBinding =
DataBindingUtil.setContentView(this, R.layout.activity_main)
val user = User("名称", "昵称", true, 20)
dataBinding.user = user
}
}
- 通过运算符操作数据
在布局中通过@{}绑定数据时,用``字符包裹的表示字符串,可用作拼接字符串。
<?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="user"
type="com.example.myapplication.User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{`拼接了`+user.name}" />
</LinearLayout>
</layout>
此外支持的运算符表达式还有:
- 数学计算 + - / * %
- 字符串连接 +
- 逻辑表达式 && ||
- 位操作符 & | ^
- 一元运算符 + - ! ~
-位移操作符 >> >>> << - 比较操作符 == > < >= <=
- instanceof
- 分组操作符 ()
- 类型转换
- 方法调用
- 字段访问
- 数组访问 []
- 三目运算符 ?:
- 聚合判断语法 ??
- 绑定List/Map等集合数据
需要在中写全路径,或者引入List和Map的全路径,然后泛型<>的表达需要通过转义字符,有两种方式,如下所见。
List集合既可以和数组一样通过索引获取值list[index]方式,也可以调用API。
Map集合既可以通过map[key]的方式,也可以通过调用API。
<?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>
<import type="java.util.ArrayList" />
<!--泛型的支持需要通过转义字符才行,如:<数据类型> 或者 <数据类型> -->
<variable
name="list"
type="ArrayList<String>" />
<import type="java.util.Map" />
<variable
name="map"
type="Map<String,String>" />
<variable
name="arrays"
type="String[]" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<!--List集合既可以和数组一样通过索引获取值list[index]方式,也可以调用API-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{list[0]}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{list.get(1)}" />
<!--Map集合既可以通过map[key]的方式,也可以通过调用API-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{map[`name`]}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{map.get(`name`)}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{arrays[0]}" />
</LinearLayout>
</layout>
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val dataBinding: ActivityMainBinding =
DataBindingUtil.setContentView(this, R.layout.activity_main)
val list = ArrayList<String>()
list.add("list-first")
list.add("list-second")
val map = HashMap<String, String>()
map["name"] = "map-name"
val arrays = arrayOf("arrays-first")
dataBinding.list = list
dataBinding.map = map
dataBinding.arrays = arrays
}
}
- Observable数据改变,动态更新UI
常规的数据Model与UI绑定之后,数据再次改变,绑定的UI并不会自动更新。但是实现了Observable的数据改变数据内容之后,绑定的UI也会动态更新。
Observable是一个接口,其子类有BaseObservable, ObservableField, ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, ObservableParcelable, ObservableArrayList, ObservableArrayMap。
Observable数据模型:
/**
*
使用ObservableField<>,泛型可以填入自己需要的类型,注意必须要初始化。
对于基本数据类型也可以直接使用ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble和ObservableParcelable。
*/
class ObservableModel {
var field = ObservableField<String>()
var age = ObservableInt()
}
中引入需要的数据模型,注意ObservableArrayList,ObservableArrayMap泛型的表达需要转义字符。
<?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="observableModel"
type="com.example.myapplication.ObservableModel" />
<variable
name="observableList"
type="androidx.databinding.ObservableArrayList<String>" />
<variable
name="observableMap"
type="androidx.databinding.ObservableArrayMap<String,String>" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<Button
android:id="@+id/bt_observable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击进行数据更新" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{observableModel.field}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(observableModel.age)}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{observableList[0]}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{observableMap[`name`]}" />
</LinearLayout>
</layout>
点击操作之后更改数据,对应绑定的UI也会动态更新。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val dataBinding: ActivityMainBinding =
DataBindingUtil.setContentView(this, R.layout.activity_main)
val observableModel = ObservableModel()
observableModel.field.set("更新前")
observableModel.age.set(5)
val observableList = ObservableArrayList<String>()
observableList.add("更新前")
val observableMap = ObservableArrayMap<String, String>()
observableMap["name"] = "更新前"
dataBinding.observableModel = observableModel
dataBinding.observableList = observableList
dataBinding.observableMap = observableMap
dataBinding.btObservable.setOnClickListener {
observableModel.field.set("更新后")
observableModel.age.set(10)
observableList[0] = "更新后"
observableMap["name"] = "更新后"
}
}
}
- 在Fragment中使用DataBinding
在Activity中,布局通过DataBindingUtils.setContentView()加载到代码中,而且会生成对应一个Binding对象,对象名是布局文件文称加上Binding后缀。
在Fragment中,布局通过DataBindingUtil.inflate()加载到代码中,通过dataBinding.root获取到布局View。
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
//fragment中通过DataBinding加载布局
val dataBinding: DataBindingFragmentBinding =
DataBindingUtil.inflate(inflater, R.layout.data_binding_fragment, container, false)
return dataBinding.root
}
- 在RecyclerView中使用DataBinding
RecyclerView的Item的布局:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="model"
type="com.example.databindingdemo.ItemModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:onClick="@{model::onItemClick}"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{model.content}" />
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginTop="5dp"
android:background="#C8C8C8" />
</LinearLayout>
</layout>
Adapter的定义方式和普通方式相同,都是继承了RecyclerView.Adapter。
自定义ViewHolder传入Binding对象,通过Binding对象拿到UI和绑定的数据。
onCreateViewHolder方法中通过DataBindingUtil.inflate()加载布局,生成自定义ViewHolder,传入Binding对象。
onBindViewHolder中通过ViewHolder拿到Binding对象,传入绑定的数据,更新UI。
private inner class ItemAdapter(var context: Context, var list: List<ItemModel>) :
RecyclerView.Adapter<ItemViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val dataBinding: ItemRvBinding =
DataBindingUtil.inflate(
LayoutInflater.from(context),
R.layout.item_rv,
parent,
false
)
return ItemViewHolder(dataBinding)
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val binding = holder.binding
binding.model = list[position]
}
override fun getItemCount(): Int = list.size
}
private inner class ItemViewHolder(var binding: ItemRvBinding) :
RecyclerView.ViewHolder(binding.root)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
//fragment中通过DataBinding加载布局
val dataBinding: DataBindingFragmentBinding =
DataBindingUtil.inflate(inflater, R.layout.data_binding_fragment, container, false)
//数据
val list = ArrayList<ItemModel>()
list.add(ItemModel("first item"))
list.add(ItemModel("second item"))
list.add(ItemModel("third item"))
dataBinding.rv.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = ItemAdapter(context, list)
addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
val position: Int = parent.getChildLayoutPosition(view)
if (position != 0) {
outRect.top = 30
}
}
})
}
return dataBinding.root
}