DataBinding即数据绑定,能够实现数据和UI的双向绑定
。数据改变时,驱动UI刷新;操做UI时,也能够同步给数据。一般在开发界面时,总有findViewById
的重复工做,DataBinding能够免去这些操做。同时,DataBinding还能够直接在xml中绑定数据,免去相似setText
的操做,让数据来驱动UI刷新。java
Jetpack笔记代码android
在app/build.gradle
中开启:git
android { dataBinding { enabled = true } } 复制代码
在布局文件中,将光标定位在根布局,alt+enter
,而后convert to data binding layout
:github
布局外层会多出一层layout标签:markdown
<layout> <!--数据描述--> <data> </data> <!--布局描述--> <ScrollView> </ScrollView> </layout> 复制代码
在数据描述内,能够导入类和声明变量:app
<data> <import type="com.holiday.jetpackstudy.model.User" /> <variable name="user" type="User" /> </data> 复制代码
在布局描述内,定义一个TextView并绑定数据:ide
<TextView android:id="@+id/tv_name" android:text="@{user.name}" /> 复制代码
在activity中,经过DataBindingUtil
获得binding对象:oop
void onCreate(Bundle savedInstanceState) { mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); } 复制代码
其中xml文件名决定了生成的binding的类名,xml文件名+Binding,如activity_main.xml
生成ActivityMainBinding.java
,而后就能够经过binding对象直接访问到view:布局
mBinding.tvName.setTextColor(xxx);
复制代码
经过binding对象设置数据,驱动UI刷新:post
mBinding.setUser(user);
复制代码
以DataBindingUtil.setContentView
做为入口跟进去,
//DataBindingUtil.java public static <T extends ViewDataBinding> T setContentView(Activity activity,int layoutId,DataBindingComponent bindingComponent) { //这里设置了布局文件 activity.setContentView(layoutId); return bindToAddedViews(bindingComponent, contentView, 0, layoutId); } //省略调用链:bindToAddedViews -> bind static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,int layoutId) { return (T) sMapper.getDataBinder(bindingComponent, root, layoutId); } 复制代码
来到MergedDataBinderMapper.java
,
//MergedDataBinderMapper.java @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; } } return null; } 复制代码
那么mMappers
的值是在何时设置的呢?发现只有一处进行add,
//MergedDataBinderMapper.java public void addMapper(DataBinderMapper mapper) { Class<? extends DataBinderMapper> mapperClass = mapper.getClass(); //若是不在mExistingMappers中,才添加进mMappers if (mExistingMappers.add(mapperClass)) { mMappers.add(mapper); final List<DataBinderMapper> dependencies = mapper.collectDependencies(); for(DataBinderMapper dependency : dependencies) { addMapper(dependency); } } } 复制代码
再来看看谁调了addMapper
,发现有一个生成类DataBinderMapperImpl
(data binding经过apt建立了一些类),
//DataBinderMapperImpl.java package androidx.databinding;//注意包名 public class DataBinderMapperImpl extends MergedDataBinderMapper { DataBinderMapperImpl() { //构造的时候把另外一个包下的生成类DataBinderMapperImpl添加进去 addMapper(new com.holiday.jetpackstudy.DataBinderMapperImpl()); } } 复制代码
接着看业务包名下的生成类DataBinderMapperImpl
,
//DataBinderMapperImpl.java package com.holiday.jetpackstudy;//注意包名 public class DataBinderMapperImpl extends DataBinderMapper { @Override public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) { int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId); switch(localizedLayoutId) { case LAYOUT_ACTIVITYMAIN: { if ("layout/activity_main_0".equals(tag)) { //返回了binding的具体实现类 return new ActivityMainBindingImpl(component, view); } } } } } 复制代码
这里出现了tag,须要知道的是,DataBinding将布局文件拆成了两个文件,activity_main.xml
描述布局,activity_main-layout.xml
描述数据,activity_main.xml
在app/build/intermediates/incremental/mergeDebugResources/stripped.dir/layout/
这个目录下,可见其被剔除了layout外壳和数据描述,同时根布局被加上了android:tag="layout/activity_main_0"
,
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:tag="layout/activity_main_0" > 复制代码
activity_main-layout.xml
在app/build/intermediates/data_binding_layout_info_type_merge/debug/mergeDebugResources/out/
目录下,里面能够看到TextView被设置了一个tag="binding_1"
,
<?xml version="1.0" encoding="utf-8" standalone="yes"?> <Layout directory="layout" isMerge="false" layout="activity_main" modulePackage="com.holiday.jetpackstudy"> <Variables name="user" declared="true" type="User"> </Variables> <Imports name="User" type="com.holiday.jetpackstudy.model.User"> </Imports> <Targets> <Target tag="layout/activity_main_0" view="ScrollView"> </Target> <Target id="@+id/tv_name" tag="binding_1" view="TextView"> <Expressions> <Expression attribute="android:text" text="user.name"> <TwoWay>false</TwoWay> </Expression> </Expressions> </Target> </Targets> </Layout> 复制代码
接下来跟进具体实现类ActivityMainBindingImpl
,
//ActivityMainBindingImpl.java public ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent,View root) { this(bindingComponent, root, mapBindings(bindingComponent, root, 2, sIncludes, sViewsWithIds)); //mapBindings会解析xml里data binding相关的tag,返回Object[] //如:if (isRoot && tag != null && tag.startsWith("layout")) //如:if (tag != null && tag.startsWith("binding_")) } private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) { //bindings存储了布局文件里含tag的view,如bindings[0]是根布局,bindings[1]是TextView //调用父类ActivityMainBinding的构造方法,为TextView赋值 super(bindingComponent, root, 0, (android.widget.TextView) bindings[1]); this.mboundView0 = (android.widget.ScrollView) bindings[0]; //这里把tag置空,就不会影响到开发者本身写的tag this.mboundView0.setTag(null); this.tvName.setTag(null); setRootTag(root); invalidateAll(); } //省略调用链:invalidateAll -> requestRebind -> mUIThreadHandler.post(mRebindRunnable); // -> executePendingBindings -> executeBindingsInternal -> executeBindings @Override protected void executeBindings() { long dirtyFlags = 0; synchronized(this) { dirtyFlags = mDirtyFlags; mDirtyFlags = 0; } java.lang.String userName = null; com.holiday.jetpackstudy.model.User user = mUser; if ((dirtyFlags & 0x3L) != 0) { //这里对数据进行了判空,避免了空指针 if (user != null) { userName = user.getName(); } } if ((dirtyFlags & 0x3L) != 0) { //这里把数据设置给了TextView androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvName, userName); } } 复制代码
最后补充一点,ActivityMainBinding
这个类的位置在app/build/generated/data_binding_base_class_source_out/debug/dataBindingGenBaseClassesDebug/out/$业务包名/databinding/
路径下,从这里能够找到binding能直接引用view的缘由:
//ActivityMainBinding.java public abstract class ActivityMainBinding extends ViewDataBinding { public final TextView tvName; protected ActivityMainBinding(Object _bindingComponent, View _root, int _localFieldCount,TextView tvName) { super(_bindingComponent, _root, _localFieldCount); this.tvName = tvName; } } 复制代码
本文使用 mdnice 排版