摘要
前几天突然就开始看Databinding,之前在上家公司,最后一个项目中使用过这个技术,相似的技术我接触过得还有Butter Knife,但是显然没有DataBinding功能强大,而且还是Google官方推出的,在功能和兼容性上,都要更胜一筹。
之前项目中,只是学习了怎么使用DataBinding这个技术,但是并没有深入理解,这样一个很蛋疼的问题就是:项目如果能正常运行,那么没有问题,但是一旦出现问题,你可能连编译器里面的错误都看不明白,更何况去解决问题。所以我最近几天重新学习了一下DataBinding。而且是从基础的使用方法重新学习的。(没办法,都忘了。)但是今天咱们不介绍具体使用,想要了解使用方法的可以直接去看官方文档,或者看我最近翻译的几篇Blog。(我其实还是比较推荐直接去看官方文档,简单直接好用)
下面我们就进入今天的主题:Databinding到底是怎么实现双向数据绑定的?
DataBinding生成的代码介绍
项目在buildhou,如果使用了Databinding, 那么会由DataBinding compiler生成ActivityMainBindingImpl.java
,ActivityMainBinding.java
,BR.java
,DataBinderMapperImpl.java
等代码,咱们主要看这四个类,数据和方法的绑定都是在这里。
ActivityMainBinding和ActivityMainBindingImpl
位置: app/build/generated/data_binding_base_class_source_out/debug/dataBindingGenBaseClassesDebug/out/com/example/databindingpractise/databinding/ActivityMainBinding.java
app/build/generated/source/apt/debug/com/example/databindingpractise/databinding/ActivityMainBindingImpl.java
在我们的MainActivity
中,我们使用:DataBindingUtil.setContentView(this, R.layout.activity_main);
方法获得一个ViewDataBinding
对象,这是编译器通过layout和绑定viewModel生成的,用于将数据和方法绑定到layout中
public abstract class ActivityMainBinding extends ViewDataBinding {
@NonNull
public final TextView textView;
@NonNull
public final UpAndDownChoiceLayoutBinding upanddown;
@Bindable
protected MainPresenter mPresenter;
@Bindable
protected MainViewModel mViewModel;
@Bindable
protected UpDownChoicePresenter mUpDownPresenter;
@Bindable
protected UpDownChoiceViewModel mUpDownviewModel;
....
}
public class ActivityMainBindingImpl extends ActivityMainBinding {
...
// views
@NonNull
private final android.widget.LinearLayout mboundView0;
@NonNull
private final android.widget.Button mboundView2;
@NonNull
private final android.widget.CheckBox mboundView3;
...
}
可以看到:ActivityMainBindingImpl
是继承自ActivityMainBinding
的
-
ActivityMainBinding
- 持有含有
id
的View的引用 - 持有绑定的类的引用
- 持有含有
-
ActivityMainBindingImpl
- 持有没有
id
的View的引用 - 具体实现了绑定
- 持有没有
BR
位置:app/build/generated/source/apt/debug/com/example/databindingpractise/BR.java
public class BR {
public static final int _all = 0;
public static final int presenter = 1;
public static final int upDownviewModel = 2;
public static final int viewModel = 3;
...
}
BR文件储存了VM的id, 功能与R文件类似
DataBinderMapperImpl
public class DataBinderMapperImpl extends DataBinderMapper {
....
@Override
public 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_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;
}
...
}
这个类主要是提供了从布局文件layoutId到ViewDataBinding类对象的映射,主要是用于在加载Layout返回对应的ViewDataBinding对象。
初始化
在这个模块,我们通过查看ViewDataBinding源码,来了解DataBinding是如何将activity_main.layout
上的View实例引用保存在对应的ActivityMainBinding
和ActivityMainBindingImpl
上的。
我们看一下在MainActivity
中执行:
dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
内部进行了什么操作。
进入DataBindingUtil.setContentView
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId) {
return setContentView(activity, layoutId, sDefaultComponent);
}
public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
int layoutId, @Nullable DataBindingComponent bindingComponent) {
activity.setContentView(layoutId);
View decorView = activity.getWindow().getDecorView();
ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
}
可以看到,其实内部还是调用了我们原来使用的activity.setContentView(layoutId);
填充布局,并拿到了content布局,并传入bindToAddedViews()
方法:
private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component,ViewGroup parent, int startChildren, int layoutId) {
final int endChildren = parent.getChildCount();
final int childrenAdded = endChildren - startChildren;
if (childrenAdded == 1) {
final View childView = parent.getChildAt(endChildren - 1);
return bind(component, childView, layoutId);
} else {
final View[] children = new View[childrenAdded];
for (int i = 0; i < childrenAdded; i++) {
children[i] = parent.getChildAt(i + startChildren);
}
return bind(component, children, layoutId);
}
}
判断布局是否有多个子布局,如果有则遍历存入View数组,最后调用不同参数的bind方法
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
}
最后调用DataBinderMapperImpl
中的getDataBinder()
方法, 获得对应Layout的VIewDataBinding类实例
接下来我们看看,是如何将Layout中的View赋值到ViewDataBinding中的引用:
public ActivityMainBindingImpl(@Nullable android.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
this(bindingComponent, root, mapBindings(bindingComponent, root, 5, sIncludes, sViewsWithIds));
}
private ActivityMainBindingImpl(android.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
super(bindingComponent, root, 2
, (android.widget.TextView) bindings[1]
, (com.example.databindingpractise.databinding.UpAndDownChoiceLayoutBinding) bindings[4]
);
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
this.mboundView2 = (android.widget.Button) bindings[2];
this.mboundView2.setTag(null);
this.mboundView3 = (android.widget.CheckBox) bindings[3];
this.mboundView3.setTag(null);
this.textView.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
当实例化ActivityMainBindingImpl
时,我们主要看mapBindings()
方法,这个方法将会把layout中的view 存入数组中bindings[]
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;
}
然后调用同名方法,由于这个方法比较长,我们分成三部分来看:
-
从View的tag中获取缓存,防止多次初始化
private static void mapBindings(DataBindingComponent bindingComponent, View view, Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds, boolean isRoot) { ... final int indexInIncludes; final ViewDataBinding existingBinding = getBinding(view); if (existingBinding != null) { return; } ... } static ViewDataBinding getBinding(View v) { if (v != null) { return (ViewDataBinding) v.getTag(R.id.dataBinding); } return null; }
-
将view储存在
bindings
数组内,分为三种情况:- 根布局,tag以layout开头;
- 设置@{}的,tag以binding开头
- 设置了id的view
private static void mapBindings(DataBindingComponent bindingComponent, View view, Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,boolean isRoot) { ... Object objTag = view.getTag(); final String tag = (objTag instanceof String) ? (String) objTag : null; boolean isBound = false; if (isRoot && tag != null && tag.startsWith("layout")) { final int underscoreIndex = tag.lastIndexOf('_'); if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) { final int index = parseTagInt(tag, underscoreIndex + 1); if (bindings[index] == null) { bindings[index] = view; } indexInIncludes = includes == null ? -1 : index; isBound = true; } else { indexInIncludes = -1; } } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) { int tagIndex = parseTagInt(tag, BINDING_NUMBER_START); if (bindings[tagIndex] == null) { bindings[tagIndex] = view; } isBound = true; indexInIncludes = includes == null ? -1 : tagIndex; } else { // Not a bound view indexInIncludes = -1; } if (!isBound) { final int id = view.getId(); if (id > 0) { int index; if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 && bindings[index] == null) { bindings[index] = view; } } } .... }
第三部分判断根布局是不是ViewGroup,如果是则遍历根布局,并判断子View是不是include的,如果是的话,则使用
DataBindingUtil.bind
进行递归;如果不是include,则直接使用mapBindings
进行递归。
通过这三个步骤,递归得到最后的bindings[]
,如果设置了id的,就将view变量设置为public,这样就避免了findViewById的代码。
这种方式从性能上比findViewById高效,因为databinding只需要遍历一次view树,而findViewById多次调用会遍历多次。
private ActivityMainBindingImpl(android.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
super(bindingComponent, root, 2
, (android.widget.TextView) bindings[1]
, (com.example.databindingpractise.databinding.UpAndDownChoiceLayoutBinding) bindings[4]
);
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
this.mboundView2 = (android.widget.Button) bindings[2];
this.mboundView2.setTag(null);
this.mboundView3 = (android.widget.CheckBox) bindings[3];
this.mboundView3.setTag(null);
this.textView.setTag(null);
setRootTag(root);
// listeners
invalidateAll();
}
最后通过setRootTag()
方法将View缓存起来。
调用invalidateAll()
方法,更新布局,这个方法我们在初始化绑定中讲。
这样我们ActivityMainBinding
和ActivityMainBindingImpl
中View引用都对应上View对象了。
初始化绑定
初始化绑定使用如下代码:
dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
dataBinding.setVariable(BR.viewModel, mainViewModel);
//
dataBinding.setViewModel(mainViewMOdel);
上面两个绑定方式其实本质上一致的,最后调用的方法都是 setViewModel()
@Override
public boolean setVariable(int variableId, @Nullable Object variable) {
boolean variableSet = true;
if (BR.viewModel == variableId) {
setViewModel(
(com.example.databindingpractise.viewModel.MainViewModel) variable);
}
else {
variableSet = false;
}
return variableSet;
}
我们主要看一下setViewModel()
方法:
public void setViewModel(@Nullable com.example.databindingpractise.viewModel.MainViewModel ViewModel) {
this.mViewModel = ViewModel;
synchronized(this) {
mDirtyFlags |= 0x20L;
}
notifyPropertyChanged(BR.viewModel);
super.requestRebind();
}
mDirtyFlags
用于表示哪个属性发生变化,notifyPropertyChanged(BR.viewModel)
,顾名思义,是发出viewModel数据变化的通知。看看requestRebind
是干什么的:
protected void requestRebind() {
if (mContainingBinding != null) {
mContainingBinding.requestRebind();
} else {
synchronized (this) {
if (mPendingRebind) {
return;
}
mPendingRebind = true;
}
if (mLifecycleOwner != null) {
Lifecycle.State state = mLifecycleOwner.getLifecycle().getCurrentState();
if (!state.isAtLeast(Lifecycle.State.STARTED)) {
return; // wait until lifecycle owner is started
}
}
if (USE_CHOREOGRAPHER) {
mChoreographer.postFrameCallback(mFrameCallback);
} else {
mUIThreadHandler.post(mRebindRunnable);
}
}
}
mContainingBinding
当include子布局的时候,该属性不为空;mPendingRebind
属性表示,是否正在绑定,如果是就直接返回;mLigecyeleOwner
对象可用于追踪View的生命周期,如果不为空,且不为start状态,直接return;最后然后根据api版本做了点不同的处理,16及以上的,会往mChoreographer
发一mFrameCallback
;否则直接往UI线程发一个mRebindRunnable
。其实这里俩个分支的结果基本一致,mChoreographer
会在界面刷新时执行mRebindRunnable
,Choreographer
是api16后引入的用于解决UI卡顿的,当收到VSYNC(定时中断)时,在doFrame里去执行相应的操作。
我们再看看mRebindRUnnable
里面做了什么:
/**
* Runnable executed on animation heartbeat to rebind the dirty Views.
*/
private final Runnable mRebindRunnable = new Runnable() {
@Override
public void run() {
synchronized (this) {
mPendingRebind = false;
}
processReferenceQueue();
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
// Nested so that we don't get a lint warning in IntelliJ
if (!mRoot.isAttachedToWindow()) {
// Don't execute the pending bindings until the View
// is attached again.
mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
return;
}
}
executePendingBindings();
}
};
将mPendingRebind
置为false;
processReferenceQueue()
,移除ViewDataBindings
中存的weakListener
/**
* Polls sReferenceQueue to remove listeners on ViewDataBindings that have been collected.
*/
private static void processReferenceQueue() {
Reference<? extends ViewDataBinding> ref;
while ((ref = sReferenceQueue.poll()) != null) {
if (ref instanceof WeakListener) {
WeakListener listener = (WeakListener) ref;
listener.unregister();
}
}
}
如果api19及以上时,判断rootView
是否attach到window上,如果没有的话,则对这个attach的状态进行监听。最终都会执行executePendingBindings()
:
/**
* Evaluates the pending bindings, updating any Views that have expressions bound to
* modified variables. This <b>must</b> be run on the UI thread.
*/
public void executePendingBindings() {
if (mContainingBinding == null) {
executeBindingsInternal();
} else {
mContainingBinding.executePendingBindings();
}
}
继而调用executeBindingsInternal()方法。
private void executeBindingsInternal() {
if (mIsExecutingPendingBindings) {
requestRebind();
return;
}
if (!hasPendingBindings()) {
return;
}
mIsExecutingPendingBindings = true;
mRebindHalted = false;
if (mRebindCallbacks != null) {
mRebindCallbacks.notifyCallbacks(this, REBIND, null);
// The onRebindListeners will change mPendingHalted
if (mRebindHalted) {
mRebindCallbacks.notifyCallbacks(this, HALTED, null);
}
}
if (!mRebindHalted) {
executeBindings();
if (mRebindCallbacks != null) {
mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
}
}
mIsExecutingPendingBindings = false;
}
hasPendingBindings
方法返回是否有数据需要绑定,如果当前没有需要需要绑定的数据,则返回不处理。接下来通知所有的RebindCallback
,RebindCallback
可以通过ActivityMainBinding.addOnRebindCallback
设置。RebindCallback
里可以把mRebindHalted
置为true,以终止后面的executeBindings()
方法。如果被终止了,同样HALTED事件也会通知给所有的RebindCallback
。
executeBindings()
是一个抽象的方法,具体实现在编译时生成的ActivityMainBindingImpl
里。
由于代码很多,我们也分成两部分:
-
将View中的值与viewModel中的值做绑定,并设置监听值的变化和View变化
protected void executeBindings() { long dirtyFlags = 0; synchronized(this) { dirtyFlags = mDirtyFlags; mDirtyFlags = 0; } android.databinding.ObservableField<java.lang.String> viewModelName = null; java.lang.String viewModelNameGet = null; com.example.databindingpractise.viewModel.MainViewModel viewModel = mViewModel; if ((dirtyFlags & 0x62L) != 0) { if (viewModel != null) { // read viewModel.name viewModelName = viewModel.name; } updateRegistration(1, viewModelName); if (viewModelName != null) { // read viewModel.name.get() viewModelNameGet = viewModelName.get(); } } if ((dirtyFlags & 0x62L) != 0) { // api target 1 android.databinding.adapters.TextViewBindingAdapter.setText(this.textView, viewModelNameGet); } if ((dirtyFlags & 0x40L) != 0) { // api target 1 android.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.textView, (android.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, textViewandroidTextAttrChanged); } }
将Layout中的事件监听动作与对应的
viewModel
方法绑定
protected void executeBindings() {
if ((dirtyFlags & 0x44L) != 0) {
if (presenter != null) {
// read presenter::onCheckedChanged
presenterOnCheckedChangedAndroidWidgetCompoundButtonOnCheckedChangeListener = (((mPresenterOnCheckedChangedAndroidWidgetCompoundButtonOnCheckedChangeListener == null) ? (mPresenterOnCheckedChangedAndroidWidgetCompoundButtonOnCheckedChangeListener = new OnCheckedChangeListenerImpl()) : mPresenterOnCheckedChangedAndroidWidgetCompoundButtonOnCheckedChangeListener).setValue(presenter));
// read presenter::changeName
presenterChangeNameAndroidViewViewOnClickListener = (((mPresenterChangeNameAndroidViewViewOnClickListener == null) ? (mPresenterChangeNameAndroidViewViewOnClickListener = new OnClickListenerImpl()) : mPresenterChangeNameAndroidViewViewOnClickListener).setValue(presenter));
}
}
// batch finished
if ((dirtyFlags & 0x44L) != 0) {
// api target 1 this.mboundView2.setOnClickListener(presenterChangeNameAndroidViewViewOnClickListener);
android.databinding.adapters.CompoundButtonBindingAdapter.setListeners(this.mboundView3, (android.widget.CompoundButton.OnCheckedChangeListener)presenterOnCheckedChangedAndroidWidgetCompoundButtonOnCheckedChangeListener, (android.databinding.InverseBindingListener)null);
}
}
事件与方法的绑定这里就不说了,上述代码基本已经很清晰的展示了,我们主要分析一下ViewModel
上值的变化,他是通过updateRegistration(1, viewModelName);
方法实现的:
protected boolean updateRegistration(int localFieldId, Observable observable) {
return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
}
这里面传入同名方法的第三个参数需要我们注意一下:
private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
@Override
public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();
}
};
private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
implements ObservableReference<Observable> {
final WeakListener<Observable> mListener;
public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
mListener = new WeakListener<Observable>(binder, localFieldId, this);
}
@Override
public WeakListener<Observable> getListener() {
return mListener;
}
@Override
public void addListener(Observable target) {
target.addOnPropertyChangedCallback(this);
}
@Override
public void removeListener(Observable target) {
target.removeOnPropertyChangedCallback(this);
}
@Override
public void setLifecycleOwner(LifecycleOwner lifecycleOwner) {
}
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
ViewDataBinding binder = mListener.getBinder();
if (binder == null) {
return;
}
Observable obj = mListener.getTarget();
if (obj != sender) {
return; // notification from the wrong object?
}
binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
}
}
private static class WeakListener<T> extends WeakReference<ViewDataBinding> {
private final ObservableReference<T> mObservable;
protected final int mLocalFieldId;
private T mTarget;
public WeakListener(ViewDataBinding binder, int localFieldId,
ObservableReference<T> observable) {
super(binder, sReferenceQueue);
mLocalFieldId = localFieldId;
mObservable = observable;
}
// ……
}
从上面知道CREATE_PROPERTY_LISTENER是一个CreateWeakListener
对象,CreateWeakListener.create()
能得到WeakPropertyListener
,WeakPropertyListener
内有变量WeakListener
,WeakListener
持有ViewDataBinding
以及Observable
(即VM)。
我们接着上面看看updateRegistration里面的事情:
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;//nothing to do, same object
}
unregisterFrom(localFieldId);
registerTo(localFieldId, observable, listenerCreator);
return true;
}
先从mLocalFieldObservers[]
取localFieldId对应的WeakListener
,如果为null的话,就调用registerTo()
进行注册;如果不为空,而且与之前注册过的不一致的话,则重新注册。那registerTo
里面如何进行注册?
protected void registerTo(int localFieldId, Object observable,
CreateWeakListener listenerCreator) {
if (observable == null) {
return;
}
WeakListener listener = mLocalFieldObservers[localFieldId];
if (listener == null) {
listener = listenerCreator.create(this, localFieldId);
mLocalFieldObservers[localFieldId] = listener;
}
listener.setTarget(observable);
}
registerTo把CreateWeakListener存储在mLocalFieldObservers里面。
这样一来V和VM的联系就通过ViewDatabinding
建立起来了。V内有ViewDatabinding
,而ViewDatabinding
里持有各个View的引用。ViewDataBinding
有VM的变量,而VM内的PropertyChangeRegistry
监听实则为WeakPropertyListener
,WeakListener
能获取到ViewDatabinding
。
VM变化如何通知View
我们知道,如果要达到VM变化时自动绑定到View上,有下面俩种方式:
- 继承自
BaseObservable
,在getter上增加@Bindable注解,在setter里增加代码notifyPropertyChanged(BR.xxx)
。 - 无需继承,需要将属性替换为
Observable
类,例如ObservableInt
、ObservableField
等。
/**
* Notifies listeners that all properties of this instance have changed.
*/
public void notifyChange() {
synchronized (this) {
if (mCallbacks == null) {
return;
}
}
mCallbacks.notifyCallbacks(this, 0, null);
}
/**
* Notifies listeners that a specific property has changed. The getter for the property
* that changes should be marked with {@link Bindable} to generate a field in
* <code>BR</code> to be used as <code>fieldId</code>.
*
* @param fieldId The generated BR id for the Bindable field.
*/
public void notifyPropertyChanged(int fieldId) {
synchronized (this) {
if (mCallbacks == null) {
return;
}
}
mCallbacks.notifyCallbacks(this, fieldId, null);
}
不同的是,一个回调的方法是notifyChange()
, 一个回调的方法是notifyPropertyChanged()
,不过最后都是通过mCallbacks.notifyCallbacks()
返回。
然后,这个监听是在绑定的时候做的设定:
当调用listener.setTarget(observable)
时,其实是设置了viewModel的监听事件:
@Override
public void addListener(Observable target) {
target.addOnPropertyChangedCallback(this);
}
V的变化如何同步到VM
这个相对就简单一下了,是直接对对应的View设置setTextWatcher方法
:
if ((dirtyFlags & 0x40L) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.textView, (android.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (android.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, textViewandroidTextAttrChanged);
总结
这篇文章主要跟根据源码来熟悉DataBinding是如何双向绑定数据和方法在Layout上的,在弄清楚这一套逻辑之后,个人觉得DataBinding感觉是通过一种语法规则,将绑定数据和方法自动化,省却了很多需要人为的重复的工作,例如findViewById,setOnClickListener等等,让我们能有更多的精力专注于逻辑控制。值得一提的是,使用Databinding很容易构造出MVVM模式的代码。
参考文献:DataBinding源码解析