目录
- 一、布局加载
- 二、view映射
上一章说明了DataBinding生存的类之间关系,现在这里来看看布局是如何加载的以及view是如何映射的。
一、布局加载
这里把之前的代码重新贴下方便说明,代码如下:
class MainActivity : AppCompatActivity() { | |
private val viewModel: SimpleViewModel by viewModels() | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main) | |
binding.lifecycleOwner = this | |
binding.viewModel = viewModel | |
} | |
} |
其中布局加载就这一行:DataBindingUtil.setContentView(this, R.layout.activity_main),所以进入到DataBindingUtil中,代码如下:
public static <T extends ViewDataBinding> T setContentView( Activity activity, | |
int layoutId) { | |
return setContentView(activity, layoutId, sDefaultComponent); | |
} |
就是简单的调用转发而已,继续下一步,如下:
public static <T extends ViewDataBinding> T setContentView( Activity activity, | |
int layoutId, DataBindingComponent bindingComponent) { | |
activity.setContentView(layoutId); | |
View decorView = activity.getWindow().getDecorView(); | |
ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content); | |
return bindToAddedViews(bindingComponent, contentView,, layoutId); | |
} |
activity.setContentView(layoutId),这和我们不用DataBinding写的一样啊,所以Databinding在这里就帮我们加载了布局。
接下来,看DataBinding是如何实现view映射的。
二、view映射
然后拿到decorView 并找到contentView ,最后调用bindToAddedViews,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 ==) { | |
final View childView = parent.getChildAt(endChildren -); | |
return bind(component, childView, layoutId); | |
} else { | |
final View[] children = new View[childrenAdded]; | |
for (int i =; i < childrenAdded; i++) { | |
children[i] = parent.getChildAt(i + startChildren); | |
} | |
return bind(component, children, layoutId); | |
} | |
} |
在我们的场景里面,endChildren 应该为1,childrenAdded 也为1,所以走了第一个分支,继续调用bind函数,如下:
private static DataBinderMapper sMapper = new DataBinderMapperImpl(); | |
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root, int layoutId) { | |
return (T) sMapper.getDataBinder(bindingComponent, root, layoutId); | |
} |
调用了sMapper的getDataBinder函数,这里的sMapper类型为DataBinderMapperImpl,还记得上一章说过有两个DataBinderMapperImpl吗?为了便于说明,这里再把之前的类图贴下:
额,这就尴尬了,所以这里的Mapper到底是哪个呢?之前说过左边的是android提供的,右边的是我们自己包下面的;其实这里的sMapper属于左边这个行列,也就是androidx这个包下面的。那他们有什么区别呢?你可以认为左边的提供了一个简单的代理功能,其实它就是简单对右边的Mapper类进行包装而已。
这里需要说明下sMapper对象的初始化过程,我们知道类加载会触发类变量(静态变量)的初始化,这个时候sMapper就会被初始化,这个时候会调用DataBinderMapperImpl(左边那个mapper)的构建函数,代码如下:
package androidx.databinding;//位于androidx包下面 | |
public class DataBinderMapperImpl extends MergedDataBinderMapper {DataBinderMapperImpl() { | |
//这个DataBinderMapperImpl就是我们自己包下面的了 | |
addMapper(new com.zfang.databindingstudy.DataBinderMapperImpl()); | |
} | |
} |
正如前面所说,androidx下面的mapper类包装了项目中的mapper类,addMapper代码如下:
public void addMapper(DataBinderMapper mapper) { | |
Class<? extends DataBinderMapper> mapperClass = mapper.getClass(); | |
if (mExistingMappers.add(mapperClass)) { | |
mMappers.add(mapper); | |
final List<DataBinderMapper> dependencies = mapper.collectDependencies(); | |
for(DataBinderMapper dependency : dependencies) { | |
addMapper(dependency); | |
} | |
} | |
} |
这里会把项目中的mapper(即DataBinderMapperImpl)加入到mMappers这个CopyOnWriteArrayList中,后面会用到。
此时可以继续看看getDataBinder的实现了(其实现位于MergedDataBinderMapper中),代码如下:
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; | |
} |
这里就是从mMappers中把mapper拿出来,再根据传递进来的参数view、layoutId找到相应的ViewDataBinding对象;这里的mMappers就是刚刚提到的那个CopyOnWriteArrayList,所以会调用到我们的DataBinderMapperImpl,其中的getDataBinder实现如下:
private static final int LAYOUT_ACTIVITYMAIN =;private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(); | |
static { INTERNAL_LAYOUT_ID_LOOKUP.put(com.zfang.databindingstudy.R.layout.activity_main, LAYOUT_ACTIVITYMAIN); | |
} | |
public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) { int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId); | |
if(localizedLayoutId >) { | |
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_".equals(tag)) { | |
return new ActivityMainBindingImpl(component, view); | |
} | |
throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag); | |
} | |
} | |
} | |
return null; | |
} |
这里有个SparseIntArray ,它定义了我们的布局与一个整数的映射关系,上面的代码首先拿到view的tag,这里返回的tag为layout/activity_main_0(回忆下:上一章说过DataBinding会生存两个xml,其中一个加了tag,那里说的tag正是和这里对应上了,其作用就体现在这里),所以会返回ActivityMainBindingImpl,这正是需要的ViewDataBinding类。
继续进入ActivityMainBindingImpl的构建函数中,代码如下:
public ActivityMainBindingImpl( androidx.databinding.DataBindingComponent bindingComponent, View root) { | |
this(bindingComponent, root, mapBindings(bindingComponent, root,, sIncludes, sViewsWithIds)); | |
} | |
private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) { | |
super(bindingComponent, root, | |
, (android.widget.TextView) bindings[] | |
, (android.widget.TextView) bindings[] | |
); | |
this.first.setTag(null); | |
this.mboundView = (androidx.constraintlayout.widget.ConstraintLayout) bindings[0]; | |
this.mboundView.setTag(null); | |
this.second.setTag(null); | |
setRootTag(root); | |
// listeners | |
invalidateAll(); | |
} |
先调用了第一个构造函数,然后进入第二个。第二个构造函数又调用了父类的相应构造函数,代码如下:
protected ActivityMainBinding(Object _bindingComponent, View _root, int _localFieldCount, TextView first, TextView second) { | |
super(_bindingComponent, _root, _localFieldCount); | |
this.first = first; | |
this.second = second; | |
} |
没错,上面的bindings数组中的bindings[1]、bindings[2]正是对应到了我们这个场景中的first和second两个view。现在的问题是bindings数组中的值是怎么来的呢?
我们继续看看ActivityMainBindingImpl类中第一个构建数据中调用的函数mapBindings,看来在mapBindings中会填充bindings数组,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; | |
} |
这里 根据numBindings新建了一个数组,继续:
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; | |
} | |
Object objTag = view.getTag(); | |
final String tag = (objTag instanceof String) ? (String) objTag : null; | |
boolean isBound = false; | |
//第一次进来isRoot为true,tag为根据布局所以是以layout开头,因此这进入第一个if | |
if (isRoot && tag != null && tag.startsWith("layout")) { | |
final int underscoreIndex = tag.lastIndexOf('_'); | |
if (underscoreIndex > && isNumeric(tag, underscoreIndex + 1)) { | |
final int index = parseTagInt(tag, underscoreIndex +); | |
if (bindings[index] == null) { | |
bindings[index] = view;//放入bindings数组,这里的view代表根布局 | |
} | |
//处理包含布局中有include标签的情况 | |
indexInIncludes = includes == null ? - : index; | |
isBound = true; | |
} else { | |
indexInIncludes = -; | |
} | |
} else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) { | |
//如何不是根布局,对应到我们的场景则会走到这里,我们的两个TextView的 | |
//tag刚是以binding开头的,其实只要写了绑定表达式就会到这里。 | |
int tagIndex = parseTagInt(tag, BINDING_NUMBER_START); | |
if (bindings[tagIndex] == null) { | |
bindings[tagIndex] = view; | |
} | |
isBound = true; | |
indexInIncludes = includes == null ? - : tagIndex; | |
} else { | |
// Not a bound view | |
indexInIncludes = -; | |
} | |
if (!isBound) { | |
final int id = view.getId(); | |
if (id >) { | |
int index; | |
if (viewsWithIds != null && (index = viewsWithIds.get(id, -)) >= 0 && | |
bindings[index] == null) { | |
bindings[index] = view; | |
} | |
} | |
} | |
//如果是ViewGroup则递归处理找到相应的view | |
if (view instanceof ViewGroup) { | |
final ViewGroup viewGroup = (ViewGroup) view; | |
final int count = viewGroup.getChildCount(); | |
int minInclude =; | |
for (int i =; i < count; i++) { | |
final View child = viewGroup.getChildAt(i); | |
boolean isInclude = false; | |
//处理include标签 | |
if (indexInIncludes >= && child.getTag() instanceof String) { | |
String childTag = (String) child.getTag(); | |
if (childTag.endsWith("_") && | |
childTag.startsWith("layout") && childTag.indexOf('/') >) { | |
// This *could* be an include. Test against the expected includes. | |
int includeIndex = findIncludeIndex(childTag, minInclude, | |
includes, indexInIncludes); | |
if (includeIndex >=) { | |
isInclude = true; | |
minInclude = includeIndex +; | |
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 +; | |
final View[] included = new View[includeCount]; | |
for (int j =; j < includeCount; j++) { | |
included[j] = viewGroup.getChildAt(i + j); | |
} | |
bindings[index] = DataBindingUtil.bind(bindingComponent, included, | |
layoutId); | |
i += includeCount -; | |
} | |
} | |
} | |
} | |
//非include | |
if (!isInclude) { | |
mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false); | |
} | |
} | |
} | |
} |
这里就是实现view数组映射的关键,主要功能就是填充了bindings数组,思路就是找到包含绑定表达式的控件,然后把它们记录下来放到一个数组中,方便在相应控件的数据变化的时候能够通知到控件, 这里其实就是找到如下布局中的两个TextView然后加入到bindings中。
<androidx.constraintlayout.widget.ConstraintLayout 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" | |
android:layout_width="match_parent" | |
android:layout_height="match_parent" | |
android:tag="layout/activity_main_" | |
tools:context=".MainActivity"> | |
<TextView | |
android:id="@+id/first" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:layout_marginVertical="dp" | |
android:tag="binding_" | |
android:textColor="#" | |
android:textSize="sp" | |
app:layout_constraintBottom_toTopOf="@id/second" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toTopOf="parent" | |
app:layout_constraintVertical_chainStyle="packed" /> | |
<TextView | |
android:id="@+id/second" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content" | |
android:tag="binding_" | |
android:textColor="#" | |
android:textSize="sp" | |
app:layout_constraintBottom_toBottomOf="parent" | |
app:layout_constraintEnd_toEndOf="parent" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toBottomOf="@id/first" /> | |
</androidx.constraintlayout.widget.ConstraintLayout> |
上面那段代码的逻辑就是找到ConstraintLayout(android:tag="layout/activity_main_0") 以及两个TextView(tag分别为binding_1和binding_2),总共三个控件。ConstraintLayout就是根布局,两个TextView就是我们需要操作的View。
好了,布局view映射完成,简单总结下:首先就是DataBinding会帮我们调用setContentView,所以我们不用调用这个方法;其次DataBinding会帮我们找到包含有数据绑定表达式的View其后帮我们存起来,方便在数据变化的时候操作我们的View。
下一章继续分析数据是如何与控件进行绑定的。
如果你对DataBinding生存的类关系有疑问,可以返回上一章DataBinding原理----类关系进行参考。