databinding用來實現 vm層和v層的雙向綁定關系;
主要作用
- 取代繁瑣的findViewByid();
- 綁定VM層和V層的關聯關系,實現雙向交互;
雙向交互指的是:
VM通知V做出改變,例如:修改文字顏色。
V通知VM做出改變,例如:click事件,textChanged事件。
簡單分析
編譯時 生成新的xml布局文件
根據xml布局文件生成了 編譯后的布局文件,把所有設置id的view都添加一個 字符串tag;tag的名稱 是例如:layout/布局名字+數字
編譯時 生成對應的BindingImpl類
每個編譯后的布局文件 都生成 一個對應的BindingImpl類,持有布局文件中的view對象,id信息和設置bean對象;BindingImpl類的名稱 是例如:布局文件名稱+BindingImpl
public class ItemKDPageBindingImpl extends ItemKDPageBinding {
static {
sIncludes = null;
sViewsWithIds = new android.util.SparseIntArray();
sViewsWithIds.put(R.id.item_knowledge_list_detail_authorImg, 5);
}
//...
public ItemKDPageBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
this(bindingComponent, root, mapBindings(bindingComponent, root, 6, sIncludes, sViewsWithIds));
}
//設置bean數據對象
public void setKnowledgeBean(@Nullable com.brucej.module_knowledge.beans.KnowledgeBean KnowledgeBean) {
this.mKnowledgeBean = KnowledgeBean;
synchronized(this) {
mDirtyFlags |= 0x1L;
}
notifyPropertyChanged(BR.knowledgeBean);//通知 觀察者,我的屬性改變了
super.requestRebind();//觸發 view重新賦值
}
}
//持有view屬性,和bean數據對象;
public abstract class ItemKDPageBinding extends ViewDataBinding {
@NonNull
public final TextView itemKnowledgeListDetailTypeTv;
@Bindable
protected KnowledgeListBean.DatasBean mDataBean;
//...
protected void requestRebind() {
//...
if (USE_CHOREOGRAPHER) {
mChoreographer.postFrameCallback(mFrameCallback);
} else {
mUIThreadHandler.post(mRebindRunnable);
}
}
}
private final Runnable mRebindRunnable = new Runnable() {
@Override
public void run() {
//...
//最后調用
executePendingBindings();
}
};
/**
* @hide
*/
protected abstract void executeBindings();
}
//ViewDataBinding
public abstract class ViewDataBinding extends BaseObservable {
private static void mapBindings(DataBindingComponent bindingComponent, View view, Object[] bindings, ViewDataBinding.IncludedLayouts includes, SparseIntArray viewsWithIds, boolean isRoot) {
ViewDataBinding existingBinding = getBinding(view);
if (existingBinding == null) {
//...偽代碼
if(不是ViewGroup){
//判斷tag,存放到bindings數組
bindings[id] = view;
}else{
//遍歷子view,判斷tag,存放到bindings數組
}
}
}
}
編譯時 DataBinderMapperImpl類
生成一個DataBinderMapperImpl類,DataBinderMapperImpl類持有所有的已經生成的 BindingImpl類;
DataBindUtil持有一個 sMapper對象,這個對象就是編譯時期生成的DataBinderMapperImpl;
public class DataBinderMapperImpl extends DataBinderMapper {
static {
INTERNAL_LAYOUT_ID_LOOKUP.put(com.brucej.module_knowledge.R.layout.activity_knowledge_detail, LAYOUT_ACTIVITYKNOWLEDGEDETAIL);
INTERNAL_LAYOUT_ID_LOOKUP.put(com.brucej.module_knowledge.R.layout.fragment_kd_page, LAYOUT_FRAGMENTKDPAGE);
INTERNAL_LAYOUT_ID_LOOKUP.put(com.brucej.module_knowledge.R.layout.fragment_knowledge, LAYOUT_FRAGMENTKNOWLEDGE);
INTERNAL_LAYOUT_ID_LOOKUP.put(com.brucej.module_knowledge.R.layout.item_k_d_page, LAYOUT_ITEMKDPAGE);
INTERNAL_LAYOUT_ID_LOOKUP.put(com.brucej.module_knowledge.R.layout.item_knowledge, LAYOUT_ITEMKNOWLEDGE);
}
@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_ACTIVITYKNOWLEDGEDETAIL: {
if ("layout/activity_knowledge_detail_0".equals(tag)) {
return new ActivityKnowledgeDetailBindingImpl(component, view);
}
throw new IllegalArgumentException("The tag for activity_knowledge_detail is invalid. Received: " + tag);
}
//...
}
}
return null;
}
}
調用setContentView或者inflate方法 實現view注入
DataBindingUtil.setContentView(activity, layoutId);
DataBindingUtil.inflate(inflater, layoutId, container, false);
調用setContentView或者inflate 傳入layoutId 加載布局時,調用的還是原生的加載方法,生成布局view;
//DataBindingUtil
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);
}
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;
final int startChildren = useChildren ? parent.getChildCount() : 0;
final View view = inflater.inflate(layoutId, parent, attachToParent);
if (useChildren) {
return bindToAddedViews(bindingComponent, parent, startChildren, layoutId);
} else {
return bind(bindingComponent, view, layoutId);
}
}
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
int layoutId) {
return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
}
- 接著調用bind方法,使用DataBinderMapperImpl sMapper對象 ,根據layoutId和view的tag信息 找到 BindingImpl類,生成對象并返回;
- 生成對象時,在BindingImpl類的構造方法中,根據傳入的view和自身的id 對其view屬性進行賦值。
調用binding對象的setXX方法 設置數據對象
見 ItemKDPageBindingImpl 的setKnowledgeBean方法。
調用binding 對象的賦值方法,
然后調用requestRebind方法然后保證切換到主線程 (使用Handler或者Choreographer.getInstance() )
最后調用executeBindings 對view進行賦值和綁定監聽。
編譯時,
根據xml布局文件生成了 兩個xml文件;例如:
app/build/../layout/activity_main-layout.xml;DataBinding所需要的 bean和布局信息
app/build/../layout/activity_main.xml; activity加載的所需要的布局文件,
而且所有存在id的view都添加一個 字符串tag(tag的規則:layout/布局名字+數字)
根據布局文件,生成對應的BindingImpl類(類名規則:布局文件名稱+BindingImpl);
BindingImpl類 持有布局文件中的所有view對象,持有一個map(SparseIntArray類型) 存放所有的 view的id和數字;
BindingImpl類 存在一個set設置bean對象的方法;
BindingImpl類 存在一個requestRebind方法,用來更新View顯示;
同時生成一個DataBinderMapperImpl類,存放所有 生成的BindingImpl類,xxBindingImpl類;
BindingImpl類 有一個getDataBinder(...,View view, int layoutId) 用來獲取xxBindingImpl的對象;
#
DataBindUtil類 持有一個 sMapper對象,這個對象就是編譯時生成的DataBinderMapperImpl 對象;
調用setContentView或者inflate 傳入layoutId 加載布局時,調用的還是原始的加載方法,生成布局view;
接著調用bind方法,調用sMapper對象的getDataBinder(...,View view, int layoutId)方法 獲取一個BindingImpl對象;
根據layoutId,調用構造方法 xxBindingImpl(...,View root) ,生成并返回一個 xxBindingImpl對象;
xxBindingImpl的構造方法中,會根據root對象,獲取其子view;
xxBindingImpl的構造方法中,調用invalidateAll() -> requestRebind()
最終保證切換到主線程 (Handler或者下一幀回調 Choreographer.getInstance()),
調用executeBindings 對view進行賦值
補充:
如果Bean類的 set方法中,調用了notifyPropertyChanged()方法,
那么 在生成的xxBindingImpl類中的 executeBindings中,
會創建觀察者監聽 bean對象或者bean的屬性 的變化,然后更新相應的view對象;