声明 : https://www.jianshu.com/p/714062a9af75
目录:
简介
原理
使用方法1,一节界面数据绑定(基础使用)
2,二级界面的绑定
3,响应事件
4,BindAdapter
5,RecycleView 绑定机制
6,双向绑定
简介:
DataBinding 是一种库,借助该库,可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。简单来说,就是帮我们实现 view 和 data 绑定的工具.DataBinding 的出现让布局文件承担了部分原本属于界面的工作,使页面与布局之间的耦合度进一步降低.
:项目简洁,可读性高,部分与 ui 控件的代码都在布局文件里完成.
:不再需要 findViewById()
:布局文件可以包含简单的业务逻辑.ui 控件能够直接与数据模型中的字段绑定,甚至能响应用户的交互.
原理:
先略过去吧....太复杂了....,就看看下面的使用方法吧.....
使用方法:
一级界面数据绑定
1,build.gradle
android {
//....
dataBinding {
enabled = true
}
}
启动绑定数据
2,创建 Person 对象
class Person(var name: String?, var age: Int, var sex: String?)
3,修改布局文件 activity_data_binding.xml
在布局文件外层加入
我们所做的修改是在 ui 布局的最外层加上一个 layout 的标签,并将命名空间 xmlns 从ConstraintLayout移到了
4,实例化布局
有了 DataBinding 之后,就可以告别 findViewById()了,我们可以通过DataBindingUtil.setContentView()方法实例化布局文件,该方法返回实例化后布局文件对象,名字和布局文件的名字一样,并在后面加上 Binding.
class DataBindingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var activityDataBindingBinding: ActivityDataBindingBinding =
DataBindingUtil.setContentView(
this,
R.layout.activity_data_binding
)
}
}
比如,我的布局文件名字叫 activity_data_binding.xml ,布局文件对象叫 ActivityDataBindingBinding.
5,将数据传递到布局文件
为了减轻 Activity 的工作量,让布局文件也承担一部分工作,所以要将 Person 对象传递到布局文件.具体做法如下
首先在布局文件标签中定义个布局变量
标签 用于放置 ui 控件所需要的数据,数据类型可以自定义,比如代码中是的 Person 类,也可以是基本类型.
然后在 Activty 中通过 setPersonD()方法,将 Person 对象传递给布局文件中对应的布局变量.
activityDataBindingBinding.personD = Person("张三", 30, "男")
绑定布局文件和成员变量
布局文件用@{}
表达式为控件赋值
在布局文件引用静态类
有时候我们需要在布局文件中引用一些 java/kotlin 工具类,帮助我们处理简单的逻辑.
class Utils {
companion object {
@JvmStatic
fun getStr(a: Int): String {
return a.toString()
}
}
}
我们可以再布局文件中通过
接着在控件中使用
6,完整的布局和 Activty 文件
class DataBindingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var activityDataBindingBinding: ActivityDataBindingBinding =
DataBindingUtil.setContentView(
this,
R.layout.activity_data_binding
)
//单向
activityDataBindingBinding.personD = Person("张三", 30, "男")
}
}
运行截图:
二级界面的绑定
在一级界面布局中,设置好布局变量personD
之后,便可以接受来自 Activty 的数据,进而将数据和控件进行绑定,不仅如此,布局变量personD
同时也是命名空间 xmlns:app
的一个属性,一级界面正是通过命名空间xmlns:app
引用布局变量 personD
,将数据对象传递给二级页面.具体代码如下:
二级页面 layout_second.xml
将 sex 的 view 挪到二级页面去了
一级页面 activity_data_binding.xml
运行出来的截图和图一是一样的啦,就粘贴过来啦
响应事件
1,编写一个类,用于接受和响应 Button 的 onClick 事件.
命名为 HandleClickListener可以单独写一个文件,也可以写在 Activity 中,作为内部类.
inner class HandleClickListener {
fun showToast(view: View) {
Toast.makeText(view.context, "哈哈哈", Toast.LENGTH_LONG).show()
}
}
⚠️⚠️⚠️**在定义事件方法名称时需要注意:方法的名称可以和原始函数名称不一样,方法参数和返回值必须和原始的回调函数保持一致。不然会报错,比如:clickFirst(View view)必须要有view参数,如果没有会报错.**
⚠️⚠️
2,在布局文件中定义并使用
也可以用双冒号android:onClick="@{handleC::showToast}"
3,在 Avtivity 中实例化HandleClickListener类
class DataBindingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var activityDataBindingBinding: ActivityDataBindingBinding =
DataBindingUtil.setContentView(
this,
R.layout.activity_data_binding
)
//监听
activityDataBindingBinding.handleC = HandleClickListener()
}
}
运行截图
自定义 BindingAdapter
在 gradle 启动 DataBinding 库的时候,就会为我们生成所需要的各种类,其中包括大量针对 ui 控件的,名为 XXXBindingAdapter 的类,这些类中包含各种静态方法,并且在这些静态方法前都有@BindingAdapter
标签,标签中的别名对应于 ui 控件在布局文件中的属性.
看个例子,TextView的 TextViewBindingAdapter 的部分源码:
public class TextViewBindingAdapter {
private static final String TAG = "TextViewBindingAdapters";
@SuppressWarnings("unused")
public static final int INTEGER = 0x01;
public static final int SIGNED = 0x03;
public static final int DECIMAL = 0x05;
@BindingAdapter("android:text")
public static void setText(TextView view, CharSequence text) {
final CharSequence oldText = view.getText();
if (text == oldText || (text == null && oldText.length() == 0)) {
return;
}
if (text instanceof Spanned) {
if (text.equals(oldText)) {
return; // No change in the spans, so don't set anything.
}
} else if (!haveContentsChanged(text, oldText)) {
return; // No content changes, so don't set anything.
}
view.setText(text);
}
//........
}
Databinding 库以静态方法的形式为 ui 控件的各个属性绑定了响应的代码.若开发人员在 UI控件的属性中使用了表达式,那么当布局文件被渲染时,属性所绑定的方法会被自动调用.
比如,当 TextView 被渲染时,android:text
属性会自动调用 TextViewBindingAdapter.setText()
方法.UI控件通过简单的属性设置,便可以在布局文件中调用所绑定的方法.
那么我们就来自定义一个处理图片的 BindAdapter 类:
1, 添加 Glide 库
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
2, 添加网络权限
3, 编写处理图片的 BindAdapter 类
class ImageViewBindingAdapter {
companion object{
@JvmStatic
@BindingAdapter("imageV")
fun setImage(image: ImageView, imageUrl: String?) {
if (!TextUtils.isEmpty(imageUrl)) {
Glide.with(image.context).load(imageUrl).into(image)
} else {
image.setImageResource(R.mipmap.ic_launcher)
}
}
}
}
BindAdapter中的方法均为静态方法,第 1 个参数是调用者本身,即 ImageView,第 2 个参数是布局文件在调用该方法传递过来的参数.在静态方法前面需要加入@BindingAdapter()
标签,并为该方法起一个别名,此处为 imageV.布局文件正式通过别名来调用方法的.
4, 修改布局文件
在布局文件定义 String ,传递图片地址.
ImageView调用
5, Activity 中设置布局文件变量
class DataBindingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var activityDataBindingBinding: ActivityDataBindingBinding =
DataBindingUtil.setContentView(
this,
R.layout.activity_data_binding
)
//自定义 adapter
activityDataBindingBinding.internetImageUrl =
"https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3436121203,3749922833&fm=26&gp=0.jpg"
}
}
运行截图:
在上面的示例中,我们做了接受网络图片地址的展示,如果还希望,在接受网络图片的时候,也能接受本地图片资源作为参数,这样,当网络图片地址为空的时候,则显示本地图片资源所制定的图片.根据这个需求,我们优化一下 BindAdapter
class ImageViewBindingAdapter {
companion object {
@JvmStatic
@BindingAdapter(value = ["imageV", "defaultRes"], requireAll = false)
fun setImage(image: ImageView, imageUrl: String?, imageResource: Int) {
if (!TextUtils.isEmpty(imageUrl)) {
Glide.with(image.context).load(imageUrl).into(image)
} else {
image.setImageResource(imageResource)
}
}
}
}
在@BindingAdapter
标签中,方法参数以 value = ["", ""]
的形式存在,变量 requireAll 作用是告诉 DataBinding 库这些参数是否都要赋值,默认是 true.
资源文件:
Activity:
class DataBindingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var activityDataBindingBinding: ActivityDataBindingBinding =
DataBindingUtil.setContentView(
this,
R.layout.activity_data_binding
)
//自定义 adapter
activityDataBindingBinding.internetImageUrl =
"https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3436121203,3749922833&fm=26&gp=0.jpg"
activityDataBindingBinding.imageRes = R.mipmap.ic_launcher
}
}
RecycleView 绑定机制
1,布局文件
RecycleView 的布局
2,item 布局 item_recycle.xml
RecycleView 的 item 布局文件,分别绑定不同的数据.
3,编写 Adapter
class RecycleViewAdapter(private val list: MutableList) :
RecyclerView.Adapter() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(DataBindingUtil.inflate(
LayoutInflater.from(parent.context), R.layout.item_recycle,parent, false ))
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val personViewModel = list[position]
holder.itemRecycleBinding.personI = personViewModel
}
override fun getItemCount(): Int {
return list.size
}
inner class MyViewHolder(var itemRecycleBinding: ItemRecycleBinding)
: RecyclerView.ViewHolder(itemRecycleBinding.root) //.root 返回的是布局的最外层 ui 视图
}
编写 Adapter 需要注意三个地方:
⚠️ 在 onCreateViewHolder()方法中,通过 DataBindingUtil.inflate()实例化布局.
⚠️在 onBindViewHolder 方法中,设置布局变量
⚠️ ItemRecycleBinding 是 DataBinding 为布局文件 item_recycle.xml 生成的对象
4,Activity 添加数据
class DataBindingActivity : AppCompatActivity() {
private val list = mutableListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var activityDataBindingBinding: ActivityDataBindingBinding =
DataBindingUtil.setContentView(
this,
R.layout.activity_data_binding
)
//recycleview
for (i in 1..100) {
var personViewModel1 = Person("张三$i", i, "女")
list.add(personViewModel1)
}
activityDataBindingBinding.recycle.layoutManager = LinearLayoutManager(this)
activityDataBindingBinding.recycle.adapter = RecycleViewAdapter(list)
}
}
运行截图:
双向绑定
1,编写PersonViewModel类
class PersonViewModel(name: String, age: Int, sex: String) {
var name = ObservableField()
var age = ObservableField()
var sex = ObservableField()
init {
this.name.set(name)
this.age.set(age)
this.sex.set(sex)
}
}
ObservableField
2,编写布局文件
android:text="@={personVM.name}"
采用@={}
表达式完成双向绑定
3,编写 Activty
class DataBindingActivity : AppCompatActivity() {
var personViewModel = PersonViewModel("张三",20,"女")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var activityDataBindingBinding: ActivityDataBindingBinding =
DataBindingUtil.setContentView(
this,
R.layout.activity_data_binding
)
//双向
activityDataBindingBinding.personVM = personViewModel
}
inner class HandleClickListener {
fun showToast(view: View) {
Toast.makeText(view.context, personViewModel.name.get(), Toast.LENGTH_LONG).show()
}
}
}
运行图片