DataBinding的双向绑定实现原理
Posted 一代小强
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DataBinding的双向绑定实现原理相关的知识,希望对你有一定的参考价值。
“ 悄悄咪咪告诉你,DataBinding是怎么实现双向绑定的“
在讲DataBinding之前,有必要讲讲ViewBinding
1、ViewBinding
1) 配置
要使用ViewBinding,只需要在gradle 添加如下配置即可
android
...
viewBinding
enabled = true
如果需要在生成绑定类时忽略某个布局文件,请将 tools:viewBindingIgnore=“true” 属性添加到相应布局文件的根视图中:
<LinearLayout
...
tools:viewBindingIgnore="true" >
...
</LinearLayout>
2)用法
在创建xml文件后,Android Studio会自动创建对应的类,类名格式为:XML 文件的名称转换为驼峰式大小写,并在末尾添加Binding
一词。
比如,创建了一个activity_view.xml
<LinearLayout
...>
<TextView
android:id="@+id/viewBindingView"
android:layout_width="match_parent"
android:layout_height="20dp" />
</LinearLayout>
生成的绑定类就叫做ActivityViewBinding,然后在对应的activity中使用布局绑定
public class ViewBindingActivity extends AppCompatActivity
private ActivityViewBinding activityViewBinding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
super.onCreate(savedInstanceState);
// 初始化布局和view
activityViewBinding = ActivityViewBinding.inflate(getLayoutInflater());
// 设置contentView
setContentView(activityViewBinding.getRoot());
// 获取到id为viewBindingView的textView,并设置文本
activityViewBinding.viewBindingView.setText("你好,我是viewBinding");
3) 原理
在build之后,会在如下路径生成对应的类
生成如下代码
public final class ActivityViewBinding implements ViewBinding
@NonNull
private final LinearLayout rootView;
// 通过遍历xml,找到声明id了的view,id作为变量名
@NonNull
public final TextView viewBindingView;
private ActivityViewBinding(@NonNull LinearLayout rootView, @NonNull TextView viewBindingView)
this.rootView = rootView;
this.viewBindingView = viewBindingView;
@Override
@NonNull
public LinearLayout getRoot()
return rootView;
// 传入LayoutInflater,用于获取对应布局的view
@NonNull
public static ActivityViewBinding inflate(@NonNull LayoutInflater inflater)
return inflate(inflater, null, false);
@NonNull
public static ActivityViewBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup parent, boolean attachToParent)
// 通过指定的布局获取到rootView
View root = inflater.inflate(R.layout.activity_view, parent, false);
if (attachToParent)
parent.addView(root);
return bind(root);
// 核心代码,通过代码模板,找到对应的view
@NonNull
public static ActivityViewBinding bind(@NonNull View rootView)
int id;
missingId:
id = R.id.viewBindingView;
TextView viewBindingView = rootView.findViewById(id);
if (viewBindingView == null)
break missingId;
return new ActivityViewBinding((LinearLayout) rootView, viewBindingView);
String missingId = rootView.getResources().getResourceName(id);
throw new NullPointerException("Missing required view with ID: ".concat(missingId));
生成的类通过LayoutInflater获取布局,然后依次初始化view。
2、DataBinding
1)配置
DataBinding 跟ViewBinding一样,只需要在gradle中添加如下配置即可
android
...
dataBinding
enabled = true
2)用法
我们先看activity_main.xml布局:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="video"
type="com.example.databinding.VideoBean" />
</data>
<LinearLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/bind_text1"
android:layout_width="match_parent"
android:layout_height="30dp"
android:gravity="center"
android:text="@video.title" />
<Button
android:id="@+id/bind_text2"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="@String.valueOf(video.score + 1)" />
</LinearLayout>
</layout>
与正常的xml布局不一样的是,DataBinding 以layout作为第一层。第二层分别是 data和view的根布局。
其中data下的name作为对象,type为类路径名
package com.example.databinding;
public class VideoBean
public String title;
public int score;
public VideoBean(String title, int score)
this.title = title;
this.score = score;
然后在布局中就可以把对象的属性值显示到TextView上
<TextView
...
android:text="@video.title" />
在对应的activity中,使用的方式与ViewBinding类似
public class MainActivity extends AppCompatActivity
private ActivityMainBinding mainBinding;
private VideoBean video;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState)
super.onCreate(savedInstanceState);
// 获取对应的binding类
mainBinding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(mainBinding.getRoot());
video = new VideoBean("小黄人", 8);
// 设置对应的数据
mainBinding.setVideo(video);
// 设置周期监听,可选
mainBinding.setLifecycleOwner(this);
全程我们没有初始化对应的view,只是获取bean,然后更新一下即可
当然,我们也可以在布局中添加对应的事件响应
<Button
android:id="@+id/bind_text2"
android:layout_width="match_parent"
android:layout_height="50dp"
android:onClick="@video::increaseScore"
android:text="@String.valueOf(video.score + 1)" />
在类中添加 increaseScore方法,如下所示,即可实现点击button修改score的值,并更新ui。
public class VideoBean extends BaseObservable
public String title;
// 声明该属性是可绑定监听的
@Bindable
public int score;
public VideoBean(String title, int score)
this.title = title;
this.score = score;
public void increaseScore(View view)
score++;
// 通知score属性变化了
notifyPropertyChanged(BR.score);
我们只需要修改少量代码即可实现双向绑定,接下来将会讲解对应的原理。
更多见布局和绑定表达式
3)原理
view的初始化
生成的类路径与ViewBinding一样,直接看看对应的类
// 抽象类
public abstract class ActivityMainBinding extends ViewDataBinding
@NonNull
public final Button bindText2;
@Bindable
protected VideoBean mVideo;
protected ActivityMainBinding(Object _bindingComponent, View _root, int _localFieldCount,
TextView bindText1, Button bindText2)
super(_bindingComponent, _root, _localFieldCount);
this.bindText1 = bindText1;
this.bindText2 = bindText2;
// 把data下的参数都声明了get/set方法
public abstract void setVideo(@Nullable VideoBean video);
@Nullable
public VideoBean getVideo()
return mVideo;
@NonNull
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater)
return inflate(inflater, DataBindingUtil.getDefaultComponent());
@NonNull
@Deprecated
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
@Nullable Object component)
return ViewDataBinding.<ActivityMainBinding>inflateInternal(inflater, R.layout.activity_main, null, false, component);
...
这里的ActivityMainBinding 继承了ViewDataBinding,但是内部却没有类似ViewBinding那样,直接通过findViewById方式初始化我们需要的view,那这些view是怎么来的?
这个问题我们先放一放,上面的inflate方法调用了ViewDataBinding的inflateInternal 方法
public abstract class ViewDataBinding extends BaseObservable implements ViewBinding
...
protected static <T extends ViewDataBinding> T inflateInternal(
@NonNull LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent,
boolean attachToParent, @Nullable Object bindingComponent)
return DataBindingUtil.inflate(
inflater,
layoutId,
parent,
attachToParent,
checkAndCastToBindingComponent(bindingComponent)
);
来到DataBindingUtil 这个工具类
public class DataBindingUtil
private static DataBinderMapper sMapper = new DataBinderMapperImpl();
public static <T extends ViewDataBinding> T inflate(
@NonNull LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent,
boolean attachToParent, @Nullable DataBindingComponent bindingComponent)
final boolean useChildren = parent != null && attachToParent;
final int startChildren = useChildren ? parent.getChildCount() : 0;
final View view = inflater.inflate(layoutId, parent, attachToParent);
if (useChildren)
return bindToAddedViews(bindingComponent, parent, startChildren, layoutId);
else
// activity里面传的parent为null ,所以会走这里
return bind(bindingComponent, view, layoutId);
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
int layoutId)
return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
DataBinderMapperImpl 的实现如下
package androidx.databinding;
public class DataBinderMapperImpl extends MergedDataBinderMapper
DataBinderMapperImpl()
// 将impl添加到mMappers中
addMapper(new com.example.databinding.DataBinderMapperImpl());
// 对应的基类
public class MergedDataBinderMapper extends DataBinderMapper
....
@Override
public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view,
int layoutId)
// 遍历 mMappers
for(DataBinderMapper mapper : mMappers)
ViewDataBinding result = mapper.getDataBinder(bindingComponent, view, layoutId);
if (result != null)
return result;
if (loadFeatures())
return getDataBinder(bindingComponent, view, layoutId);
return null;
那就看看com.example.databinding.DataBinderMapperImpl 的实现
public class DataBinderMapperImpl extends DataBinderMapper
private static final int LAYOUT_ACTIVITYMAIN = 1;
private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(1);
static
// 保存我们创建的layout与 index的映射
INTERNAL_LAYOUT_ID_LOOKUP.put(com.example.databinding.R.layout.activity_main, LAYOUT_ACTIVITYMAIN);
@Override
public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId)
// 通过layoutId,获取到index,上面传的是R.layout.activity_main,
// 所以这里localizedLayoutId为LAYOUT_ACTIVITYMAIN
int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
if(localizedLayoutId > 0)
// 获取到TAG,上面的代码没有设置tag的地方,但是通过debug得知对应的tag为"layout/activity_main_0"
final Object tag = view.getTag();
if(tag == null)
throw new RuntimeException("view must have a tag");
switch(localizedLayoutId)
case LAYOUT_ACTIVITYMAIN:
if ("layout/activity_main_0".equals(tag))
return new ActivityMainBindingImpl(component, view);
throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
return null;
...
至于view的tag是怎么来的,笔者猜测是Android Studio自动添加的
最终我们获取到了ActivityMainBindingImpl的实例,对应实现如下
public class ActivityMainBindingImpl extends ActivityMainBinding
@Nullable
private static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes;
@Nullable
private static final android.util.SparseIntArray sViewsWithIds;
static
sIncludes = null;
sViewsWithIds = null;
// views
@NonNull
private final android.widget.LinearLayout mboundView0;
// listeners
private OnClickListenerImpl mVideoIncreaseScoreAndroidViewViewOnClickListener;
// Inverse Binding Event Handlers
public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root)
// 通过mapBindings方法获取view的数组
this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));
private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings)
// 调用super即ActivityMainBinding的构造函数初始化view
super(bindingComponent, root, 0
, (android.widget.TextView) bindings[1]
, (android.widget.Button) bindings[2]
);
this.bindText1.setTag(null);
this.bindText2.setTag(null);
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
setRootTag(root);
invalidateAll();
...
public void setVideo(@Nullable com.example.databinding.VideoBean Video)
this.mVideo = Video;
synchronized(this)
mDirtyFlags |= 0x1L;
<以上是关于DataBinding的双向绑定实现原理的主要内容,如果未能解决你的问题,请参考以下文章