Jetpack-DataBinding双向绑定基本使用与原理分析

一、DataBinding应用场景

在Android开发初期,基本都会使用MVC模式,但随着业务的增加,Activity会变得很臃肿,业务代码与界面显示全部耦合在一起,扩展性差,维护起来很不方便,于是MVP模式就应运而生了。

MVP模式实现了V和M的解耦,分层清晰,把业务处理搬到P层,通过接口实现V层和P层之间通信。MVP模式也存在一些问题,随着项目的增大,接口类会越来越多,而且每一个小的改动,都需要通过接口来通信,有时候一个简单的功能,都需要写好几个接口,会造成接口爆炸。这时候MVVM模式就出现了。

MVVM模式同样也是解耦,将业务代码放到VM层,V层与VM层通过双向绑定来相互通知变化,不再需要那么多的接口来通信了。双向绑定,指的是将数据与界面绑定起来,当数据发生变化时会体现在界面上,反过来界面内容变化也会同步更新到数据上。

DataBinding可以轻松的实现MVVM,但并不能说DataBinding就只是属于MVVM的。DataBinding只是谷歌推出的一个支持双向绑定的库,它是工具集,能大大减少绑定app逻辑与layout文件的“胶水代码”,例如setTextfindViewById等代码,大多数情况下,MVVM都使用DataBinding,但其实小部分情况下,MVP也会使用DataBinding。我们先来简单使用一下DataBinding,再对它的工作流程做一个大概的了解。

二、DataBinding使用

1、配置gradle来开启DataBinding

android {
    ...
    buildFeatures {
        dataBinding true
    }
}

2、将布局转换为DataBinding布局

在xml布局中,点击小黄灯,选择Convert to data binding layout

此时会自动帮我们包裹一层<layout>标签,这样就转换完成了

<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>

    </data>
    
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".databinding.DataBindingActivity">
    </androidx.constraintlayout.widget.ConstraintLayout>
    
</layout>

3、数据类

创建一个数据类来模拟网络请求返回的数据类,所有字段都定义为ObservableField类型,并把实际类型通过泛型传入,这是kotlin的用法,和Java直接继承BaseObserver的写法稍有区别。
如果想要实现双向绑定,必须要使用ObservableField,这样才能收到数据变化的回调,从而实现M—>V一向的绑定,否则就只有V—>M单向的绑定了。

class User {

    val name: ObservableField<String> by lazy { ObservableField() }
    val age: ObservableField<String> by lazy { ObservableField() }

}

4、在xml的data标签中,引入数据类

<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.zry.jetpackdemo.databinding.User" />
    </data>
    
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".databinding.DataBindingActivity">
    </androidx.constraintlayout.widget.ConstraintLayout>
    
</layout>

5、双向绑定

当我们使用了DataBinding布局后,DataBinding会自动为我们生成一个以我们xml文件名字为依据创建的文件,比如我的xml文件名为activity_data_binding,于是在Activity中我就可以直接使用DataBinding为我们生成的类ActivityDataBindingBinding,如果没有的话,需要重新build一下,因为DataBinding采用的是APT技术,只有在编译期间才会生成代码。另外,还需要给DataBinding设置我们的数据类,这样DataBinding才可以监听数据的变化

class DataBindingActivity : AppCompatActivity() {

    private lateinit var mBinding: ActivityDataBindingBinding
    private var mUser = User()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_data_binding)
        mBinding.user = mUser

        mUser.name.set("阿三")
        mUser.age.set("30")
    }
}

在xml中,定义几个TextView和Edittext来测试效果,我们使用两个TextView来分别显示name和age,两个Edittext分别可以输入来改变name和age

<?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.zry.jetpackdemo.databinding.User" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".databinding.DataBindingActivity">

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="100dp"
            android:text="@{user.name}"
            android:textSize="24sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tv_age"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:text="@{user.age}"
            android:textSize="24sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tv_name" />

        <EditText
            android:id="@+id/et_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="100dp"
            android:text="@={user.name}"
            android:textSize="24sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tv_age" />

        <EditText
            android:id="@+id/et_age"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:inputType="number"
            android:text="@={user.age}"
            android:textSize="24sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/et_name" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
mBinding.user = mUser
mUser.name.set("阿三")

android:text="@{user.name}"

这些是实现了Model—>View层一向的绑定,数据类的值发生变化时,View上显示的值会马上改变

android:text="@={user.name}"

@={}实现了View—>Model层一向的绑定,当View上的值我们通过输入来改变以后,数据类的值会被相应修改
以上就实现了双向绑定。

三、DataBinding原理分析

DataBinding用起来很简单,越简单表示框架帮我们完成了越多的东西,在我们使用DataBinding时,编译后可以看到它帮我们生成了很多代码,毫无疑问自动生成的代码是通过APT技术来完成的。

1、拆布局

在布局转化为DataBinding布局时,DataBinding为根布局外再包裹了一层<layout>标签,这个布局文件,在编译的期间,通过APT注解处理器,生成两个文件,一个是DataBinding内部使用的,记录了需要的布局信息的文件,一个是Android View体系本身的文件,也就是原本我们xml布局,不同的是,DataBinding把我们每个控件都加了一个tag。我们来看一下这两个文件
第一个文件build/intermediates/data_binding_layout_info_type_merge/debug/out/activity_data_binding-layout.xml

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout directory="layout" filePath="app\src\main\res\layout\activity_data_binding.xml"
    isBindingData="true" isMerge="false" layout="activity_data_binding"
    modulePackage="com.zry.jetpackdemo" rootNodeType="androidx.constraintlayout.widget.ConstraintLayout">
    <Variables name="user" declared="true" type="com.zry.jetpackdemo.databinding.User">
        <location endLine="9" endOffset="57" startLine="7" startOffset="8" />
    </Variables>
    <Targets>
        <Target tag="layout/activity_data_binding_0"
            view="androidx.constraintlayout.widget.ConstraintLayout">
            <Expressions />
            <location endLine="63" endOffset="55" startLine="13" startOffset="4" />
        </Target>
        <Target id="@+id/tv_name" tag="binding_1" view="TextView">
            <Expressions>
                <Expression attribute="android:text" text="user.name">
                    <Location endLine="23" endOffset="38" startLine="23" startOffset="12" />
                    <TwoWay>false</TwoWay>
                    <ValueLocation endLine="23" endOffset="36" startLine="23" startOffset="28" />
                </Expression>
            </Expressions>
            <location endLine="27" endOffset="55" startLine="18" startOffset="8" />
        </Target>
        <Target id="@+id/tv_age" tag="binding_2" view="TextView">
            <Expressions>
                <Expression attribute="android:text" text="user.age">
                    <Location endLine="34" endOffset="37" startLine="34" startOffset="12" />
                    <TwoWay>false</TwoWay>
                    <ValueLocation endLine="34" endOffset="35" startLine="34" startOffset="28" />
                </Expression>
            </Expressions>
            <location endLine="38" endOffset="63" startLine="29" startOffset="8" />
        </Target>
        <Target id="@+id/et_name" tag="binding_3" view="EditText">
            <Expressions>
                <Expression attribute="android:text" text="user.name">
                    <Location endLine="45" endOffset="39" startLine="45" startOffset="12" />
                    <TwoWay>true</TwoWay>
                    <ValueLocation endLine="45" endOffset="37" startLine="45" startOffset="29" />
                </Expression>
            </Expressions>
            <location endLine="49" endOffset="62" startLine="40" startOffset="8" />
        </Target>
        <Target id="@+id/et_age" tag="binding_4" view="EditText">
            <Expressions>
                <Expression attribute="android:text" text="user.age">
                    <Location endLine="57" endOffset="38" startLine="57" startOffset="12" />
                    <TwoWay>true</TwoWay>
                    <ValueLocation endLine="57" endOffset="36" startLine="57" startOffset="29" />
                </Expression>
            </Expressions>
            <location endLine="61" endOffset="63" startLine="51" startOffset="8" />
        </Target>
    </Targets>
</Layout>

可以看到每个Target节点都记录了布局中的控件的id以及tag,通过这些tag就可以读出布局,再来看一下拆过的第二个文件build/intermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_data_binding.xml

<?xml version="1.0" encoding="utf-8"?>

                                                       
                                                   

    

                 
                       
                                                          

           

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".databinding.DataBindingActivity" 
        android:tag="layout/activity_data_binding_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:id="@+id/tv_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="100dp"
            android:tag="binding_1"    
            android:textSize="24sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tv_age"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:tag="binding_2"   
            android:textSize="24sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tv_name" />

        <EditText
            android:id="@+id/et_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="100dp"
            android:tag="binding_3"     
            android:textSize="24sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tv_age" />

        <EditText
            android:id="@+id/et_age"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:inputType="number"
            android:tag="binding_4"    
            android:textSize="24sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/et_name" />

    </androidx.constraintlayout.widget.ConstraintLayout>

这个文件其实就是我们自己编写的布局文件,但每个控件都被加上了tag。

2、DataBindingUtil.setContentView

DataBinding源码很庞大,但使用代码很少,我们来看一下最关键的这个方法,点进去可以看到其实也调用了activity.setContentView(layoutId);

    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);
    }

设置布局以后,拿到decorViewcontentView,最后返回的是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);
        }
    }

可以看到bindToAddedViews方法是在遍历所有子View,并最终调用bind方法,跟进bind方法再来看一下

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

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

这里的sMapper是一个DataBinderMapper对象,其实现类是DataBinderMapperImpl,DataBinderMapperImpl是通过APT注解处理器生成的。这里的sMapper.getDataBinder()其实就是调用的MergedDataBinderMapper的getDataBinder()方法,而sMapper中的数据,其实就是DataBinderMapperImpl的构造器中调用其父类MergedDataBinderMapper的addMapper()方法添加的对象

public class DataBinderMapperImpl extends MergedDataBinderMapper {
  DataBinderMapperImpl() {
    addMapper(new com.zry.jetpackdemo.DataBinderMapperImpl());
  }
}

而MergedDataBinderMapper的getDataBinder()方法,遍历了sMapper,也就是拿到每个DataBinderMapperImpl去执行它的getDataBinder方法

    @Override
    public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view,
            int layoutId) {
        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;
    }

再跟进一下getDataBinder方法,DataBinderMapperImpl是APT注解处理器生成的类。如果是布局的顶层View,比如tag为layout/activity_data_binding_0,那么就会new一个ActivityDataBindingBindingImpl对象。

  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_ACTIVITYDATABINDING: {
          //根布局tag就是这个
          if ("layout/activity_data_binding_0".equals(tag)) {
            return new ActivityDataBindingBindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for activity_data_binding is invalid. Received: " + tag);
        }
      }
    }
    return null;
  }

ActivityDataBindingBindingImpl会进行一些View的绑定操作,将通过tag取出的View与
ActivityDataBindingBindingImpl中对应的View属性进行绑定。

    public ActivityDataBindingBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
        this(bindingComponent, root, mapBindings(bindingComponent, root, 5, sIncludes, sViewsWithIds));
    }

第三个参数为mapBindings方法的返回结果,继续跟进看一下,进入了ViewDataBinding类中

    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;
    }

这里创建了一个Object数组,数组的大小为传进来的第三个参数,也就是布局的有tag的控件个数,继续跟进一下mapBindings方法

    private static void mapBindings(DataBindingComponent bindingComponent, View view,
            Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
            boolean isRoot) {
        final int indexInIncludes;
        // 判断View是否已经存在绑定,如果已经绑定,则直接return
        final ViewDataBinding existingBinding = getBinding(view);
        if (existingBinding != null) {
            return;
        }
        // 获取View的tag标签
        Object objTag = view.getTag();
        final String tag = (objTag instanceof String) ? (String) objTag : null;
        boolean isBound = false;
        // 如果tag是根布局,并且是以layout开头的tag
        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);
                // 将根布局标签对应的View放在bindings数组中
                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;
                }
            }
        }

        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;
                if (indexInIncludes >= 0 && child.getTag() instanceof String) {
                    String childTag = (String) child.getTag();
                    if (childTag.endsWith("_0") &&
                            childTag.startsWith("layout") && childTag.indexOf('/') > 0) {
                        // This *could* be an include. Test against the expected includes.
                        int includeIndex = findIncludeIndex(childTag, minInclude,
                                includes, indexInIncludes);
                        if (includeIndex >= 0) {
                            isInclude = true;
                            minInclude = includeIndex + 1;
                            final int index = includes.indexes[indexInIncludes][includeIndex];
                            final int layoutId = includes.layoutIds[indexInIncludes][includeIndex];
                            int lastMatchingIndex = findLastMatching(viewGroup, i);
                            if (lastMatchingIndex == i) {
                                bindings[index] = DataBindingUtil.bind(bindingComponent, child,
                                        layoutId);
                            } else {
                                final int includeCount =  lastMatchingIndex - i + 1;
                                final View[] included = new View[includeCount];
                                for (int j = 0; j < includeCount; j++) {
                                    included[j] = viewGroup.getChildAt(i + j);
                                }
                                bindings[index] = DataBindingUtil.bind(bindingComponent, included,
                                        layoutId);
                                i += includeCount - 1;
                            }
                        }
                    }
                }
                if (!isInclude) {
                    mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
                }
            }
        }
    }

可以看出mapBindings方法其实就是去寻找tag是layout_开头的,tag是binding_开头的,拿到这些设置了tag的view,最后都保存到Object[]中,这里其实是比较消耗内存的地方。至此,DataBinding就通过拆解布局,设置tag,将所有的view保存到了一个数组中。

3、设置数据

DataBindingUtil.setContentView方法完成后,已经将所有布局交给DataBinding了。接下来再看一下DataBinding.setUser方法,也就是设置数据类。在生成的ActivityDataBindingBindingImpl实现类中可以找到这个方法

    public void setUser(@Nullable com.zry.jetpackdemo.databinding.User User) {
        this.mUser = User;
        synchronized(this) {
            mDirtyFlags |= 0x4L;
        }
        notifyPropertyChanged(BR.user);
        super.requestRebind();
    }

跟进一下requestRebind方法

    protected void requestRebind() {
        if (mContainingBinding != null) {
            mContainingBinding.requestRebind();
        } else {
            final LifecycleOwner owner = this.mLifecycleOwner;
            if (owner != null) {
                Lifecycle.State state = owner.getLifecycle().getCurrentState();
                if (!state.isAtLeast(Lifecycle.State.STARTED)) {
                    return; // wait until lifecycle owner is started
                }
            }
            synchronized (this) {
                if (mPendingRebind) {
                    return;
                }
                mPendingRebind = true;
            }
            if (USE_CHOREOGRAPHER) {
                mChoreographer.postFrameCallback(mFrameCallback);
            } else {
                mUIThreadHandler.post(mRebindRunnable);
            }
        }
    }

这个方法可以看到,DataBinding同样使用到了Lifecycle中的生命周期状态,最后会调用mChoreographer.postFrameCallback(mFrameCallback);mUIThreadHandler.post(mRebindRunnable);,其实mFrameCallback最后也会使用mRebindRunnablemRebindRunnable在ViewDataBinding的构造中可以找到,run方法内部执行代码如下:

synchronized(this) {
    ViewDataBinding.this.mPendingRebind = false;
}
ViewDataBinding.processReferenceQueue();
if (VERSION.SDK_INT >= 19 && !ViewDataBinding.this.mRoot.isAttachedToWindow()) {              ViewDataBinding.this.mRoot.removeOnAttachStateChangeListener(ViewDataBinding.ROOT_REATTACHED_LISTENER);
    ViewDataBinding.this.mRoot.addOnAttachStateChangeListener(ViewDataBinding.ROOT_REATTACHED_LISTENER);
} else {
    ViewDataBinding.this.executePendingBindings();
}

excutePendingBindings方法最终会执行子类ActivityDataBindingBindingImpl中的executeBindings()方法

    @Override
    protected void executeBindings() {
        ...
        // batch finished
        if ((dirtyFlags & 0xdL) != 0) {
            // api target 1
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etAge, userAgeGet);
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvAge, userAgeGet);
        }
        if ((dirtyFlags & 0x8L) != 0) {
            // api target 1
            androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.etAge, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, etAgeandroidTextAttrChanged);
            androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.etName, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, etNameandroidTextAttrChanged);
        }
        if ((dirtyFlags & 0xeL) != 0) {
            // api target 1

            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.etName, userNameGet);
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvName, userNameGet);
        }
    }
    ...
}

到这里我们发现,最终执行的就是setText()以及添加textWatcher的操作,setText就是M—>V一向,textWatcher就是V—>M一向。至此DataBinding实现实现双向绑定的大致流程就分析完了,当然里面还有很多细节没有看,DataBinding代码量还是非常多的,在看源码时,要抓住主线流程,不然就会迷失在源码的海洋里。

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值