Android中的双向绑定是指:将Model设置给View之后,当Model中的数据发生改变时,View显示的内容也会随之改变;当View发生变化时,Model中的数据也会随之改变。双向绑定可以让开发者使用数据驱动视图,并且降低了程序中的耦合度。双向绑定本质是基于观察者模式实现的。在代码层面,主要表现为:当Model中的数据发生变化时,通过回调接口,通知数据绑定器重新设置View中的数据;当View中的属性发生变化时,通过监听器捕获发生的变化,并将此变化传递给Model。这样就实现了双向绑定。整体框架如下图所示:

双向绑定举例:

将ObservableField<String>与EditView进行双向绑定,并使用一个TextView展示。代码如下:

ViewModel代码:

class UserViewModel:ViewModel() {var name:ObservableField<String> = ObservableField("")
}

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"xmlns:MyView="http://schemas.android.com/apk/res-auto"><data><variablename="userVM"type="com.example.jetpacklearn.viewModel.UserViewModel" /></data><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><EditTextandroid:layout_width="60dp"android:layout_height="wrap_content"android:id="@+id/userName"android:text="@={userVM.name}"app:layout_constraintTop_toTopOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintEnd_toStartOf="@+id/showName"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/showName"android:text="@{userVM.name}"app:layout_constraintTop_toTopOf="parent"app:layout_constraintStart_toEndOf="@+id/userName"app:layout_constraintHorizontal_chainStyle="spread"app:layout_constraintEnd_toEndOf="parent"/></androidx.constraintlayout.widget.ConstraintLayout>
</layout>

MainActivity文件:

class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)var layoutInflater = LayoutInflater.from(this)var dataBinding = ActivityMainBinding.inflate(layoutInflater)setContentView(dataBinding.root)var userViewModel = ViewModelProvider(this).get(UserViewModel::class.java)dataBinding.userVM = userViewModel}
}

运行结果如下:

接下来,以上述例子介绍双向绑定的实现流程。

双向绑定流程代码分析:

在MainActivity中我们可以使用以下方法获取ViewDataBinding,并设置ViewModel。

var dataBinding = ActivityMainBinding.inflate(layoutInflater)
var userViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
dataBinding.userVM = userViewModel

ActivityMainBinding是自动生成的类,其inflate代码如下:

  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {return inflate(inflater, DataBindingUtil.getDefaultComponent());}public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,@Nullable Object component) {return ViewDataBinding.<ActivityMainBinding>inflateInternal(inflater, R.layout.activity_main, null, false, component);}

接下来,会去调用ViewDataBinding的inflateInternal方法。注意,在这里DataBindingUtil.getDefaultCompinent获取的结果为null,即component对象为null。

    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的inflate方法。注意,这里parent为null,attachToParent为false。

    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;//userCHildren为falsefinal int startChildren = useChildren ? parent.getChildCount() : 0;//生成Root Viewfinal View view = inflater.inflate(layoutId, parent, attachToParent);if (useChildren) {return bindToAddedViews(bindingComponent, parent, startChildren, layoutId);} else {//走这里return bind(bindingComponent, view, layoutId);}}

接下来调用bind方法。注意bindingComponent为null

    static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,int layoutId) {return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);}

sMapper的值如下:

public class DataBindingUtil {private static DataBinderMapper sMapper = new DataBinderMapperImpl();private static DataBindingComponent sDefaultComponent = null;......
}public class DataBinderMapperImpl extends MergedDataBinderMapper {DataBinderMapperImpl() {addMapper(new com.example.jetpacklearn.DataBinderMapperImpl());}
}

DataBinderMapperImpl继承了MergedDataBinderMapper,MergedDataBinderMapper包含了一组DataBinderMapper对象。DataBinderMapper类是用来获取ViewDataBinding的,它会根据View和layoutId返回一个数据绑定类(xxxxxBinding)的实例。在DataBinderMapperImpl初始化,其会添加一个项目中自动生成的jetpacklearn.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 {INTERNAL_LAYOUT_ID_LOOKUP.put(com.example.jetpacklearn.R.layout.activity_main, LAYOUT_ACTIVITYMAIN);}@Overridepublic ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);if(localizedLayoutId > 0) {//获取视图的Tagfinal Object tag = view.getTag();if(tag == null) {throw new RuntimeException("view must have a tag");}//根据视图的Tag生成不同的ViewDataBinding实例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;}......
}

而sMapper.getDataBinder方法最终会调用上面类中的getDataBinder方法,以生成数据绑定类的实例。在getDataBinder方法,会根据根View的tag来生成不同的ViewDataBinding对象。但是,我们在xml文件中并没有设置tag,那么这里的tag是哪里来的呢?实际上,我们在写完xml文件后,在编译时,数据绑定系统会根据我们的xml文件再生成一个XML文件,并在此XML文件中设置tag。生成的XML文件如下所示:

<androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity" 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" xmlns:MyView="http://schemas.android.com/apk/res-auto"><EditTextandroid:layout_width="60dp"android:layout_height="wrap_content"android:id="@+id/userName"android:tag="binding_1"       app:layout_constraintTop_toTopOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintEnd_toStartOf="@+id/showName"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/showName"android:tag="binding_2"      app:layout_constraintTop_toTopOf="parent"app:layout_constraintStart_toEndOf="@+id/userName"app:layout_constraintHorizontal_chainStyle="spread"app:layout_constraintEnd_toEndOf="parent"/></androidx.constraintlayout.widget.ConstraintLayout>

在编译器生成的文件中,不但会给根View设置tag,而且还会给里面使用数据绑定的View设置tag。

接下来,看一下在实例化ActivityMainBindingImpl时,都会做些什么。

public class ActivityMainBindingImpl extends ActivityMainBinding  {@Nullableprivate static final androidx.databinding.ViewDataBinding.IncludedLayouts sIncludes;@Nullableprivate static final android.util.SparseIntArray sViewsWithIds;static {sIncludes = null;sViewsWithIds = null;}// views@NonNullprivate final androidx.constraintlayout.widget.ConstraintLayout mboundView0;......public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));}private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {super(bindingComponent, root, 1, (android.widget.TextView) bindings[2], (android.widget.EditText) bindings[1]);this.mboundView0 = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0];this.mboundView0.setTag(null);this.showName.setTag(null);this.userName.setTag(null);setRootTag(root);// listenersinvalidateAll();}......
}

在实例化ActivityMainBindingImpl时,其做的工作如下:首先,调用mapBindings方法生成一个视图数组bindings,此视图数组中即含有根View(下标为0),亦有使用数据绑定的视图(下标参考生成的XML中的tag)。然后,调用super方法,去执行父类ViewDataBinding的构造方法。在父类的构造方法中,会构建一个进行数据绑定的Runnable对象,以在收到绑定通知时,进行数据绑定。最后,将ActivityMainBindingIMpl实例放到根View的tag里,并发送一个数据绑定请求。接下来,依次对上面3个工作进行详细分析。

1、mapBindings方法的代码如下:

    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;}private static void mapBindings(DataBindingComponent bindingComponent, View view,Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,boolean isRoot) {final int indexInIncludes;//还没将数据绑定实例放到view的Tag中,此时getBinding的返回值为nullfinal ViewDataBinding existingBinding = getBinding(view);if (existingBinding != null) {return;}//获取视图的TAG,参考生成的XML文件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)) {//从生成的XML文件中知,index为0final int index = parseTagInt(tag, underscoreIndex + 1);if (bindings[index] == null) {//添加根视图bindings[index] = view;}//在案例中,未使用include标签,此值为-1indexInIncludes = includes == null ? -1 : index;isBound = true;} else {indexInIncludes = -1;}} else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {//将子View放到bindings数组中//生成的XML文件中,子View的tag格式为binding_{x},此时tagIndex的值就是xint tagIndex = parseTagInt(tag, BINDING_NUMBER_START);if (bindings[tagIndex] == null) {bindings[tagIndex] = view;}isBound = true;indexInIncludes = includes == null ? -1 : tagIndex;} else {// Not a bound viewindexInIncludes = -1;}......//将xml文件中的子View放到bindings数组中if (view instanceof  ViewGroup) {final ViewGroup viewGroup = (ViewGroup) view;final int count = viewGroup.getChildCount();int minInclude = 0;for (int i = 0; i < count; i++) {final View child = viewGroup.getChildAt(i);boolean isInclude = false;//未使用include标签,此处先略过if (indexInIncludes >= 0 && child.getTag() instanceof String) {......}if (!isInclude) {//递归调用当前方法,传入child,且isRoot为false以绑定子视图mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);}}}}

2、super方法对应的父类ViewDataBinding的构造方法如下:

    protected ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount) {mBindingComponent = bindingComponent;//此数组用于Observable数据变化时的回调mLocalFieldObservers = new WeakListener[localFieldCount];this.mRoot = root;if (Looper.myLooper() == null) {throw new IllegalStateException("DataBinding must be created in view's UI Thread");}if (USE_CHOREOGRAPHER) {//SDK>16时走这里mChoreographer = Choreographer.getInstance();mFrameCallback = new Choreographer.FrameCallback() {@Overridepublic void doFrame(long frameTimeNanos) {mRebindRunnable.run();}};} else {mFrameCallback = null;mUIThreadHandler = new Handler(Looper.myLooper());}}private final Runnable mRebindRunnable = new Runnable() {@Overridepublic 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 IntelliJif (!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();}};

3、将绑定类的实例放到根View的Tag,并发送绑定请求

        setRootTag(root);// listenersinvalidateAll();

setRootTag代码如下:

    protected void setRootTag(View view) {view.setTag(R.id.dataBinding, this);}

invalidateAll方法代码如下:

    public void invalidateAll() {synchronized(this) {mDirtyFlags = 0x4L;}requestRebind();}protected void requestRebind() {if (mContainingBinding != null) {mContainingBinding.requestRebind();} else {final LifecycleOwner owner = this.mLifecycleOwner;......synchronized (this) {if (mPendingRebind) {return;}mPendingRebind = true;}if (USE_CHOREOGRAPHER) {//SDK>16走这里//发送数据绑定请求,最终会调用mRebindRunnable的executePendingBindings方法mChoreographer.postFrameCallback(mFrameCallback);} else {mUIThreadHandler.post(mRebindRunnable);}}}

调用的executePendingBindings方法的代码如下:

    public void executePendingBindings() {if (mContainingBinding == null) {//走这里executeBindingsInternal();} else {//include标签下的View走这里mContainingBinding.executePendingBindings();}}private void executeBindingsInternal() {if (mIsExecutingPendingBindings) {requestRebind();return;}if (!hasPendingBindings()) {return;}mIsExecutingPendingBindings = true;mRebindHalted = false;......if (!mRebindHalted) {//调用此方法进行数据绑定,此方法的真正实现在生成的xxxxBindingImpl类中(如ActivityMainBindingImpl)executeBindings();if (mRebindCallbacks != null) {mRebindCallbacks.notifyCallbacks(this, REBOUND, null);}}mIsExecutingPendingBindings = false;}

executeBindings方法的代码如下:

    protected void executeBindings() {long dirtyFlags = 0;synchronized(this) {dirtyFlags = mDirtyFlags;mDirtyFlags = 0;}com.example.jetpacklearn.viewModel.UserViewModel userVM = mUserVM;androidx.databinding.ObservableField<java.lang.String> userVMName = null;java.lang.String userVMNameGet = null;if ((dirtyFlags & 0x7L) != 0) {if (userVM != null) {// read userVM.nameuserVMName = userVM.getName();}//给userVMName添加一个数据变化的监听器;实现双向绑定的步骤1updateRegistration(0, userVMName);if (userVMName != null) {// read userVM.name.get()userVMNameGet = userVMName.get();}}// batch finishedif ((dirtyFlags & 0x7L) != 0) {// api target 1androidx.databinding.adapters.TextViewBindingAdapter.setText(this.showName, userVMNameGet);androidx.databinding.adapters.TextViewBindingAdapter.setText(this.userName, userVMNameGet);}if ((dirtyFlags & 0x4L) != 0) {// api target 1//给userName(EditView)添加文本监听器;实现双向绑定的步骤2
androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.userName, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, userNameandroidTextAttrChanged);}}

为了实现双向绑定,既需要为数据设置监听器,监听数据变化;也需要为View设置监听器,以监听View的变化。在此,通过updateRegistration方法对数据设置监听器;通过最后一行的setTextWatcher方法对View设置监听器。因此,主要介绍中updateRegistration方法和setTextWatcher中的参数userNameAndroidTextAttrChanged。这两部分是实现双向绑定的关键。先介绍updateRegistration方法。

3.1 给Modle设置监听器

updateRegistration方法代码如下:

    protected boolean updateRegistration(int localFieldId, Observable observable) {return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);}

CREATE_PROPERTY_LISTENER的作用是 为Observable类添加一个回调接口,此接口会在数据变化时,通知观察者。其代码如下:

    private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {@Overridepublic WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();}};
private static class WeakPropertyListener extends Observable.OnPropertyChangedCallbackimplements ObservableReference<Observable> {final WeakListener<Observable> mListener;public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {//创建一个WeakListener对象,此对象在给Observable类设置回调接口时,起到承接作用。//WeakListener中包含了ViewDataBinding实例,回调接口的引用(当前类)mListener = new WeakListener<Observable>(binder, localFieldId, this);}@Overridepublic WeakListener<Observable> getListener() {return mListener;}@Overridepublic void addListener(Observable target) {//给Observable添加回调接口target.addOnPropertyChangedCallback(this);}......//Observable类的子类会在其set方法中调用此方法@Overridepublic 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);}}

WeakListener类的代码如下:

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) {//binder为ViewDataBinding实例super(binder, sReferenceQueue);//标识ViewDataBinding中的Observable字段的IDmLocalFieldId = localFieldId;//实现了回调接口OnPropertyChangedCallbackmObservable = observable;}......
}

handleFieldChange代码如下:

    private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {......//调用ActivityMainBinding的onFieldChange方法,标注哪些数据发送了变化boolean result = onFieldChange(mLocalFieldId, object, fieldId);if (result) {//请求重新进行数据绑定requestRebind();}}

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) {//给observable设置回调接口OnPropertyChangedCallbackregisterTo(localFieldId, observable, listenerCreator);return true;}if (listener.getTarget() == observable) {return false;//nothing to do, same object}unregisterFrom(localFieldId);registerTo(localFieldId, observable, listenerCreator);return true;}

registerTo方法代码如下:

    protected void registerTo(int localFieldId, Object observable,CreateWeakListener listenerCreator) {if (observable == null) {return;}WeakListener listener = mLocalFieldObservers[localFieldId];if (listener == null) {//生成一个WeakListener对象,此对象包含了OnPropertyChangedCallback的引用listener = listenerCreator.create(this, localFieldId);mLocalFieldObservers[localFieldId] = listener;if (mLifecycleOwner != null) {listener.setLifecycleOwner(mLifecycleOwner);}}//给observable添加回调接口(OnPropertyChangedCallback)listener.setTarget(observable);}

setTarget方法的代码如下:

        public void setTarget(T object) {unregister();mTarget = object;if (mTarget != null) {//mObservable为WeakPropertyListener类实例,其addListener代码在上面//mTarget是一个observable对象,此方法作用是给此对象添加一个回调接口mObservable.addListener(mTarget);}}

题外话:在第一次看到此代码时,感觉很绕,回调很多,使人不容易看懂,觉着为什么不直接使用以下这样的写法:

userVMName.addOnPropertyChangedCallback(new XXXXCallBack())

这样的写法,可以让人一看就懂。一眼就能看出此行代码的作用是什么,添加了什么样的回调接口。而官方代码则看的绕来绕去的,不易理解。后来思考了以下,发现官方的写法其实挺有道理的。官方代码的写法使代码的耦合度降低了,符合“开闭原则”,“依赖倒置原则”。而我们的简单写方法则不符合这两个原则。比如,当我们要更换一个新的Callback时,简单写法需要修改addxxxCallback方法的参数,而官方的代码,则采用扩展的方法实现这种需求,不需要对原来代码进行修改。即只需要重新实现一个CreateListener的类,并添加一个新的updateRegistration方法就好。这样虽然多写了一些代码,但是保证了“开闭原则”,即对修改关闭,对扩展开放。

在此,通过下面的类图再回顾一下给Observable添加PropertyChangedCallback的过程。首先,创建一个WeakListener对象。然后,调用此对象的setTarget(Observable)方法,给Observable添加回调接口。此过程的核心是WeakListener类,此类是一个“虚假”的监听器类,其并没有实现监听功能。而是持有了回调接口的引用,并负责给Observable对象设置回调接口。官方将“给Observable对象设置回调接口”这个功能抽象为了一个接口ObservableReference,此举符合“依赖倒置”原则,方便日后对代码进行扩展。

3.2、给View设置监听器

上面介绍了如何给Model设置监听器,接下来介绍如何给View设置监听器。以上面的案例代码为例,绑定系统通过setTextWatcher给EditView设置监听器,以在EditView的文字变化时,通知Model去同步数据,setTextWatcher方法如下:

protected void executeBindings() {......if ((dirtyFlags & 0x4L) != 0) {// api target 1androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.userName, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, userNameandroidTextAttrChanged);}}

userNameandroidTextAttrChanged对应的类的代码如下:

    private androidx.databinding.InverseBindingListener userNameandroidTextAttrChanged = new androidx.databinding.InverseBindingListener() {@Overridepublic void onChange() {//EditView的文本变化时会调用此方法java.lang.String callbackArg_0 = androidx.databinding.adapters.TextViewBindingAdapter.getTextString(userName);boolean userVMNameJavaLangObjectNull = false;// userVMcom.example.jetpacklearn.viewModel.UserViewModel userVM = mUserVM;// userVM != nullboolean userVMJavaLangObjectNull = false;// userVM.nameandroidx.databinding.ObservableField<java.lang.String> userVMName = null;// userVM.name.get()java.lang.String userVMNameGet = null;userVMJavaLangObjectNull = (userVM) != (null);if (userVMJavaLangObjectNull) {userVMName = userVM.getName();userVMNameJavaLangObjectNull = (userVMName) != (null);if (userVMNameJavaLangObjectNull) {//将EditView的text赋值给userVMNameuserVMName.set(((java.lang.String) (callbackArg_0)));}}}};

userVMName的set方法代码如下:

public class ObservableField<T> extends BaseObservableField implements Serializable {static final long serialVersionUID = 1L;private T mValue;....../*** Set the stored value.** @param value The new value*/public void set(T value) {if (value != mValue) {//避免无线循环,只有数据真正改变时,才通知数据绑定器进行数据绑定mValue = value;notifyChange();}}
}

至此,双向绑定原理的介绍基本结束。其本质可以看作分别给数据源Model和视图View设置监听器,以在数据变化时,通知彼此同步变化。

Android DataBinding双向绑定原理相关推荐

  1. Android开发提升效率之DataBinding——双向绑定

    DataBingding Android开发提升效率之DataBinding--基本使用 Android开发提升效率之DataBinding--进阶开发 Android开发提升效率之DataBindi ...

  2. angular的双向绑定原理

    http://sentsin.com/web/779.html AngularJS是一款优秀的前端JS框架,已经被用于Google的多款产品当中.AngularJS有着诸多特性,最为核心的是:MVVM ...

  3. v-model双向绑定原理_Vue数据绑定

    这是一篇简单的学习笔记.在学习一段时间Vue后,尝试实现一下Vue的数据绑定. 相关源码:https://github.com/buchuitoudegou/Data-Binding-demo Vue ...

  4. 前端技巧|vue双向绑定原理,助你面试成功

    在面试一些大厂的时候,面试官可能会问到你vue双向数据绑定的原理是什么?有些小伙伴不知道是什么东西,这样你在面试官的眼里就大打折扣了.今天小千就来给大家介绍一下vue的双向绑定原理,千万不要错过啦. ...

  5. 西安电话面试:谈谈Vue数据双向绑定原理,看看你的回答能打几分

    最近我参加了一次来自西安的电话面试(第二轮,技术面),是大厂还是小作坊我在这里按下不表,先来说说这次电面给我留下印象较深的几道面试题,这次先来谈谈Vue的数据双向绑定原理. 情景再现: 当我手机铃声响 ...

  6. vue的双向绑定原理及实现

    前言 使用vue也好有一段时间了,虽然对其双向绑定原理也有了解个大概,但也没好好探究下其原理实现,所以这次特意花了几晚时间查阅资料和阅读相关源码,自己也实现一个简单版vue的双向绑定版本,先上个成果图 ...

  7. vue双向绑定原理及实现

    vue双向绑定原理及实现 一.MVC模式 二.MVVM模式 三.双向绑定原理 1.实现一个Observer 2.实现一个Watcher 3.实现一个Compile 4.实现一个MVVM 四.最后写一个 ...

  8. 记录vue的双向绑定原理及实现

    这里写自定义目录标题 思路分析 实现过程 1.实现一个Observer 2.实现Watcher 此文章是学习以为大神____chen的 <vue的双向绑定原理及实现> vue数据双向绑定是 ...

  9. vue 的双向绑定原理

    目录 一.一句话描述 vue 的双向绑定原理 二.细说 vue 的双向绑定原理 1.vue 2.x 的双向绑定 2.vue 3.x 的双向绑定 3.一个完整的案例 一.一句话描述 vue 的双向绑定原 ...

最新文章

  1. python args_Python可变参数*args和**kwargs用法实例小结
  2. linux shell中各种分号和括号,linux shell 各种分号,括号使用方法总结
  3. c语言迷宫问题输出坐标,C语言数据结构之迷宫求解问题
  4. discuz核心函数库function_core的函数注释
  5. 多线程的实现方式01 Thread
  6. 盘古搜索:上市是既定策略 寻求股权多元化
  7. 总结 27 类深度学习主要神经网络:结构图及应用
  8. 菜鸟-需求预测与分仓规划
  9. 第三章总体均数的估计与假设检验(2)
  10. PostgreSQL 怎么通过命令来恢复删除的数据
  11. cesium打雷闪电的效果
  12. Vue设置浏览器小图标(ICON)
  13. HTML实现一个简单的图片自动显示特效
  14. 最新论文笔记(+16):K-Time Modifiable and Epoch-Based Redactable Blockchain / TIFS 2021
  15. uni-app获取元素高度
  16. UVa 1665 岛屿 (并查集)
  17. 进制在c语言中的作用,C语言中的进制关系
  18. 【向上取整/向下取整】C语言向上或向下取整 函数[内容与错误,请看评论]
  19. 【数字识别】基于模板匹配实现OCR印刷字母+数字识别含Matlab源码
  20. 红帽财报公布 开源如何走的更远?

热门文章

  1. JVM7、8详解及优化
  2. 深入理解JVM——字节码
  3. 将jar包转换成可执行.exe文件
  4. sobel算子 matlab实现6,Sobel算子matlab实现
  5. js判断数组内元素是否全相同
  6. @Value注入不生效,@Value注入静态变量
  7. 服务器开机自动修复失败怎么办,Windows Server 2012 R2 异常关机自动修复失败循环处理方法...
  8. Python2.7 安装教程
  9. java解法——寻找两个有序数组的中位数
  10. python乌鸦喝水问题总结