3.DataBinding的使用
引入DataBinding
只需要在使用DataBinding模块的build.gradle文件中添加下面的配置。
// 每个使用dataBinding的模块都应该在build.gradle中添加如下配置
android {
...
dataBinding {
enabled = true
}
}
xml布局
在布局文件中,选中根布局的标签,按住Alt+回车键,点击 Convert to databinding layout,即可转换成DataBinding布局(见下方代码段)
转换后的布局,最外层变成了layout标签,里面包裹了data标签和常规的布局元素。data元素用来声明在此布局用使用到的变量和变量的类型,以及类引用。
那么是不是所有属性都能用DataBinding来绑定呢?
当然不是!如果一个属性xxx,在该类中有setXxx方法,我们才能使用DataBinding来绑定。
例如android:layout_width,android_layout_height 就不能使用DataBinding来绑定数据。
而android:paddingLeft , android:textSize 都是可以的。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
<data>
//这里可以使用variable定义多个变量,该变量需要通过外界赋值
<variable
name="user"
type="org.devio.as.main.User" />
//通过import导入需要用到的类
<import type="android.view.View"/>
<import type="org.devio.as.main.UserManager"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/tvName"
android:layout_width="200dp" //不能使用dataBinding动态绑定
android:text="@{user.name}" //单向绑定,数据变更自动刷新UI
android:textSize="@{@dimen/16sp}"//资源引用
android:text="@{user.name+@string/suffix}" //字符串的拼接需要引用资源
android:text="@{UserManager.getUserName()}" //调用静态方法,类必须先导入
android:onClick="@{()-> UserManager.login()}"//事件绑定
/>
<EditText
//双向绑定数据变更自动更新UI,UI变更了也能自动更新user中name的数据,比单向绑定多个=
// android:text="@{user.name}"等价于tvName.text = user.name这样就将数据和View相关联了
android:text="@={user.name}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
绑定传递数据源
给DataBinding中的User对象赋值
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//此时可以通过DataBindingUtil来设置Activity的页面布局。此时会返回一个ActivityMainBinding对象。这个是编译时根据xml布局文件中的数据绑定自动生成的实现类。
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.lifecycleOwner = this
binding.user = User('张三') //完成数据绑定
//如果是在列表中使用,则可以如下编写 。ActivityMainBinding是根据activity_main布局文件自动生成的
val binding = ActivityMainBinding.inflate(layoutInflater, null, false)
binding.user = User('张三')
}
如何实现数据变化的视图自动更新呢?
想要实现数据变化的视图自动更新我们只需要让实体类User集成BaseObservable。当User中字段发生变更,只需要调用user.notifyPropertyChanged就可以让UI刷新。
public class User extends BaseObservable {
public String name;
//当使用name字段发生变更后,若想UI自动刷新,我们需要给它写个get方法并且标记Bindable注解。
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
//最后调用notifyPropertyChanged方法即可
//该字段变更了,通知UI刷新数据
notifyPropertyChanged(org.devio.as.main.BR.user);
}
//除此之外也可以使用ObserverableBoolean...observerableInt来监听数据的变化
ObserverableBoolean<Boolean> success = new ObserverableBoolean<>();
}
DataBinding也支持在布局文件中使用数组、List、Set和Map,且在布局文件中都可以通过List[index]的形式来获取元素。因为xml的特性,在声明List之类的数据类型时,需要使用尖括号的转义字符,来看下面的代码:
<?xml version="1.0" encoding="utf-8"?>
<layout >
<data>
<import type="java.util.List" /> <import type="java.util.Map" />
<import type="java.util.Set" /> <import type="android.util.SparseArray" />
<variable
name="array"
type="String[]" />
<variable
name="list"
type="List<String>" /> //List<String> 其中< 和 >俩字符需要转义
<variable
name="map"
type="Map<String, String>" /> //Map<String>
<variable
name="set"
type="Set<String>" /> //Set<String>
<variable
name="sparse"
type="SparseArray<String>" /> //SparseArray<String>
<variable
name="index"
type="int" />
<variable
name="key"
type="String" />
</data>
<LinearLayout>
<TextView
android:text="@{array[1]}" />
<TextView
android:text="@{sparse[index]}" />
<TextView
android:text="@{list[index]}" />
<TextView
android:text="@{map[key]}" />
<TextView
android:text='@{map["慕课jetpack"]}' />
<TextView
android:text='@{set.contains("xxx")?"慕课jetpack":key}' />
</LinearLayout>
</layout>
DataBinding在xml中数据绑定支持的语法表达式也是非常丰富的,支付在布局文件中使用一下运算符、表达式和关键字:
- 算术运算符:+ - * / %;
- 字符串连接运算符:+;
- 逻辑运算符:&& ||;
- 二元运算符:& | ^;
- 一元运算符: + - ! ~;
- 位移运算符: >> >>> <<;
- 比较运算符: == > < >= <= (需要被转义);
- 判断是否是类的实例:instanceof;
- 分组运算符:();
- 字面量运算符 - 字符,字符串、数据、null;
- 类型转换、方法调用;
- 字段访问;
- 数组访问: [];
- 三元运算符:?:;
- 不支持以下操作:this super new 显示泛型调用。
4.BataBinding如何扩展View属性
我们知道,以前想要给ImageView增加几个属性,必须要写个自定义的ImageView在构造函数中一顿解析。那看看使用DataBinding如何扩展View属性。
public class HiImageView extends ImageView{
//需要使用BindingAdapter注解并标记在public static方法上。
//value中的字段随意添加和方法参数一一对应即可。
@BindingAdapter(value = {"image_url", "isCircle"})
public static void setImageUrl(PPImageView view, String imageUrl, boolean isCircle) {
view.setImageUrl(view, imageUrl, isCircle, 0);
}
//requireAll = false代表是否以下三个属性在xml中同时使用才会调用到该方法
//为false的话,只要有一个属性被使用就能调用到该方法
@BindingAdapter(value = {"image_url", "isCircle", "radius"}, requireAll = false)
public static void setImageUrl(PPImageView view, String imageUrl, boolean isCircle, int radius) {
......
}
}
//在布局文件中如下使用,便能实现图片圆角和资源Url绑定的功能
<org.devio.as.main.HiImageView
.......
app:image_url ="@{user.avatar}"
app:radius="@{50}">
</org.devio.as.main.HiImageView>
5.DataBinding使用的建议
-
如fragment_layout_my.xml布局,在编译时会生成FragmentLayoutMyImpl.java实现类,我们可以搜索这种类debug跟进解决问题。 -
不建议在列表中乱用,因为DataBinding数据绑定是延迟一帧的,如果列表中的ItemView的宽高需要计算后才能正确展示,不建议使用DataBinding操作。否则会看到列表ItemView明显的撑开动画,体验不好。 此处可以使用dataBinding.executePendingBindings()快速渲染布局解决 -
实体类配合BaseObservable可以友好的解决数据双向绑定的问题。
6.ViewBinding又是什么?
Android Studio更新到3.6之后,多了一个ViewBinding的功能,看到这个名字就感觉和DataBinding很相似,那么它们有什么区别呢?
-
DataBinding可以将View和界面上的数据进行双向绑定,ViewBinding不行,也就是不能再xml中绑定数据,若要使用则需要在Gradle中开启如下配置: viewBinding {
enabled = true
}
-
如果你想要实现双向数据绑定,那么可以选择DataBinding; -
ViewBinding主要是帮我们省却了findViewById的过程,但是它在编译阶段比DataBinding耗时更短; -
如果你已经使用了Kotlin,那其实ViewBinding就没必要使用了。
7.DataBinding源码分析
布局文件的加载确认
XML分离后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"
xmlns:tools="http://schemas.android.com/tools">
<data><!--这里面就用来定义数据源-->
<variable
name="user"
type= "com.example.databindingdemo_20210117.User"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:textSize="50sp"
android:id="@+id/tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:textSize="50sp"
android:id="@+id/tv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.pwd}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
</layout>
绑定了@{}的View添加一个tag
app/build/imtermediates/data_binding_layout_info_type_merge/debug/activity_main-layout.xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout directory="layout" filePath="app\src\main\res\layout\activity_main.xml"
isBindingData="true" isMerge="false" layout="activity_main"
modulePackage="com.example.databindingdemo_20210117" rootNodeType="android.widget.LinearLayout">
<Variables name="user" declared="true" type="com.example.databindingdemo_20210117.User">
<location endLine="7" endOffset="62" startLine="5" startOffset="8" />
</Variables>
<Targets>
<Target tag="layout/activity_main_0" view="LinearLayout">
<Expressions />
<location endLine="35" endOffset="18" startLine="9" startOffset="4" />
</Target>
<Target id="@+id/tv1" tag="binding_1" view="TextView">
<Expressions>
<Expression attribute="android:text" text="user.name">
<Location endLine="19" endOffset="38" startLine="19" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="19" endOffset="36" startLine="19" startOffset="28" />
</Expression>
</Expressions>
<location endLine="23" endOffset="55" startLine="14" startOffset="8" />
</Target>
<Target id="@+id/tv2" tag="binding_2" view="TextView">
<Expressions>
<Expression attribute="android:text" text="user.pwd">
<Location endLine="30" endOffset="37" startLine="30" startOffset="12" />
<TwoWay>false</TwoWay>
<ValueLocation endLine="30" endOffset="35" startLine="30" startOffset="28" />
</Expression>
</Expressions>
<location endLine="34" endOffset="55" startLine="25" startOffset="8" />
</Target>
</Targets>
</Layout>
app/build/imtermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" android:tag="layout/activity_main_0" 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">
<TextView
android:textSize="50sp"
android:id="@+id/tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="binding_1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:textSize="50sp"
android:id="@+id/tv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="binding_2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>
DataBindingUtil类
setContentView
DataBindingUtil.setContentView(Activity activitiy,int layoutId);
---> bindToAddedViews(bindingComponent,contentView,startChildren,layoutId)9;
--->bind(compent,children,layoutId); //使用DataBindingUtil.inflate也是一样走到这里
//sMapper的实现类是APT生成的DataBinderMapperImpl类
--->sMapper.getDataBinder(dindingComponent,root,layoutId);
APT生成的DataBinderMapperImpl类
在app/build/generated/source/kapt/debug/com.xxx.xxx/DataBinderMapperImpl下,他是sMapper.getDataBinder(…)的实现。
其中可以根据layoutId,拿到每个布局的Binding实现。
//DataBinderMapperImpl
@Overridepublic ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
if(localizedLayoutId > 0) {
final Object tag = view.getTag();
if(tag == null) {
throw new RuntimeException("view must have a tag");
}
switch(localizedLayoutId) {
case LAYOUT_ACTIVITYMAINTEST: {
if ("layout/activity_main_test_0".equals(tag)) {
return new ActivityMainTestBindingImpl(component, view);
}
throw new IllegalArgumentException("The tag for activity_main_test is invalid. Received: " + tag); }
}
}
布局的Binding的Java实现
在app/build/generated/source/kapt/debug/com.xxx.xxx/databinding/ActivityMainTestBindingImpl,它是布局页面activity_main_test.xml 的具体实现,但是在调用的时候我们用的是ActivityMainTestBinding 。
public class ActivityMainTestBindingImpl extends ActivityMainTestBinding {
public ActivityMainTestBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));
}
private ActivityMainTestBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
super(bindingComponent, root, 1
, (android.widget.TextView) bindings[1]
, (android.widget.TextView) bindings[2]
);
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
this.tv1.setTag(null);
this.tv2.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
}
ViewDataBinding
//在这里对XML文件信息读取,并存入数组中
protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root,
int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
Object[] bindings = new Object[numBindings];
mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);
return bindings;
}
源码分析
核心原理从setVariable(id,value)开始分析
//ActivityMainTestBindingImpl
@Override
public boolean setVariable(int variableId, @Nullable Object variable) {
boolean variableSet = true;
if (BR.user == variableId) {
setUser((com.nearme.plugin.pay.activity.User) variable);
}
else {
variableSet = false;
}
return variableSet;
}
public void setUser(@Nullable com.nearme.plugin.pay.activity.User User) {
//1.注册监听器
updateRegistration(0, User);
this.mUser = User;
synchronized(this) {
mDirtyFlags |= 0x1L;
}
//2.调用监听器回调
notifyPropertyChanged(BR.user);
super.requestRebind();
}
1.注册监听
//ViewDataBinding
/**
* @hide
*/
protected boolean updateRegistration(int localFieldId, Observable observable) {
//更新观查者模式发通知需要相关的信息,CREATE_PROPERTY_LISTENER是一个回调接口
return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
}
//注册监听
private boolean updateRegistration(int localFieldId, Object observable,
CreateWeakListener listenerCreator) {
if (observable == null) {
return unregisterFrom(localFieldId);
}
WeakListener listener = mLocalFieldObservers[localFieldId];
if (listener == null) {
registerTo(localFieldId, observable, listenerCreator);
return true;
}
if (listener.getTarget() == observable) {
return false;
}
//1.删除监听器
//调用WeakPropertyListener的unregister方法,然后把刚才建立的联系取消掉
unregisterFrom(localFieldId);
//2.注册监听器
//listenerCreator就是之前的那个CREATE_PROPERTY_LISTENER
registerTo(localFieldId, observable, listenerCreator);
//3.注册完成
return true;
}
//注销监听
protected boolean unregisterFrom(int localFieldId) {
//调用WeakPropertyListener的unregister方法,然后把刚才建立的联系取消掉
WeakListener listener = mLocalFieldObservers[localFieldId];
if (listener != null) {
return listener.unregister();
}
return false;
}
//注册监听
protected void registerTo(int localFieldId, Object observable,
CreateWeakListener listenerCreator) {
if (observable == null) {
return;
}
WeakListener listener = mLocalFieldObservers[localFieldId];
if (listener == null) {
//调用create方法就是调用 return new WeakPropertyListener(viewDataBinding, localFieldId).getListener()
listener = listenerCreator.create(this, localFieldId);
mLocalFieldObservers[localFieldId] = listener;
if (mLifecycleOwner != null) {
listener.setLifecycleOwner(mLifecycleOwner);
}
}
//688
listener.setTarget(observable);
}
public void setTarget(T object) {
unregister();
**【延伸Android必备知识点】**
![](https://img-blog.csdnimg.cn/img_convert/9f676ecc970e26fe7871dc20e730bbd8.png)
**【Android部分高级架构视频学习资源】**
**Android精讲视频学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
**[CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](https://codechina.csdn.net/m0_60958482/android_p7)**
**任何市场都是优胜略汰适者生存,只要你技术过硬,到哪里都不存在饱和不饱和的问题,所以重要的还是提升自己。懂得多是自己的加分项 而不是必须项。门槛高了只能证明这个市场在不断成熟化!**另外一千个读者就有一千个哈姆雷特,所以以上只是自己的关键,不喜勿喷!
*【延伸Android必备知识点】**
[外链图片转存中...(img-gBwQgVnN-1630669464445)]
**【Android部分高级架构视频学习资源】**
**Android精讲视频学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
**[CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](https://codechina.csdn.net/m0_60958482/android_p7)**
**任何市场都是优胜略汰适者生存,只要你技术过硬,到哪里都不存在饱和不饱和的问题,所以重要的还是提升自己。懂得多是自己的加分项 而不是必须项。门槛高了只能证明这个市场在不断成熟化!**另外一千个读者就有一千个哈姆雷特,所以以上只是自己的关键,不喜勿喷!
如果你是卡在缺少学习资源的瓶颈上,那么刚刚好我能帮到你。欢迎关注会持续更新和分享的。
|