Android JetPack DataBinding原理分析

一.簡(jiǎn)介

???????DataBinding是谷歌發(fā)布的一個(gè)實(shí)現(xiàn)數(shù)據(jù)和UI綁定的框架,從字面意思來看即為數(shù)據(jù)綁定,是 MVVM 模式在Android上的一種實(shí)現(xiàn),用于降低布局和邏輯的耦合性,使代碼邏輯更加清晰。
???????相對(duì)于 MVP,MVVM將Presenter層替換成了ViewModel層,關(guān)于MVC、MVP、MVVM架構(gòu)比較,可以參考文章Android MVC、MVP、MVVM比較分析。
???????DataBinding 能夠省去我們一直以來的 findViewById() 步驟,大量減少 Activity 內(nèi)的代碼,數(shù)據(jù)能夠單向或雙向綁定到 layout 文件中。
???????接下來本文通過一個(gè)例子來學(xué)習(xí)一下DataBinding的用法及原理分析。

二.實(shí)例分析

a.加入dataBinding支持

???????使用DataBinding框架需要在對(duì)應(yīng)Model的build.gradle文件Android{}內(nèi)加入以下代碼,同步后就能引入對(duì) DataBinding的支持:

dataBinding {
    enabled = true
}

???????AndroidStudio高版本引入方式如下:

buildFeatures {
    dataBinding = true
}
b.xml布局文件轉(zhuǎn)化

???????將常規(guī)的布局文件轉(zhuǎn)化為對(duì)應(yīng)的數(shù)據(jù)綁定布局文件,在布局文件的根布局上輸入Alt+Enter后,選擇Convert to data binding layout會(huì)將常規(guī)布局轉(zhuǎn)換為以下布局文件形式:

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

    <data>
        //布局文件用到了View相關(guān)的方法,需要引入
        <import type="android.view.View" />
        <variable
            //任意取名字,來作為type對(duì)應(yīng)的引用,可以通過guide代替類取可變變量
            //在view中通過setGuide()建立引用關(guān)系
            name="guide"
            //定義可變變量的類
            type="com.hly.learn.ViewModel" />

    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/name"
            android:layout_width="100dp"
            android:layout_height="50dp"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="10dp"
            android:gravity="center"
            android:textColor="@android:color/holo_red_light"
            android:text="@{guide.rightImageName}" />

        <ImageView
            android:layout_width="200dp"
            android:layout_height="160dp"
            android:layout_below="@+id/name"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="10dp"
            android:scaleType="fitXY"
            android:src="@{guide.rightImageRes}" />

        <TextView
            android:layout_width="300dp"
            android:layout_height="wrap_content"
            android:layout_below="@+id/name"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="200dp"
            android:textColor="@color/colorAccent"
            android:text="@{guide.rightImageDescription}" />

        <TextView
            android:layout_width="100dp"
            android:layout_height="50dp"
            android:layout_below="@+id/name"
            android:layout_marginLeft="50dp"
            android:layout_marginTop="350dp"
            android:gravity="center"
            android:onClick="@{guide::upStep}"
            android:text="@string/up"
            android:textStyle="bold"
            android:textColor="@color/cardview_shadow_start_color"
            android:visibility="@{guide.step!=1?View.VISIBLE:View.INVISIBLE}" />

        <TextView
            android:layout_width="100dp"
            android:layout_height="50dp"
            android:layout_below="@+id/name"
            android:layout_marginLeft="230dp"
            android:layout_marginTop="350dp"
            android:gravity="center"
            android:onClick="@{guide::nextStep}"
            android:text="@string/next"
            android:textStyle="bold"
            android:visibility="@{guide.step!=3?View.VISIBLE:View.INVISIBLE}" />
    </RelativeLayout>
</layout>

??????通過以上轉(zhuǎn)化,已經(jīng)將常規(guī)布局轉(zhuǎn)化為數(shù)據(jù)綁定布局文件,那么下面就一步一步的來講解實(shí)現(xiàn)。

c.View邏輯實(shí)現(xiàn)

??????將之前常規(guī)的UI操作從view里面移到數(shù)據(jù)綁定布局xml文件中,可以大大減少view的代碼量及相關(guān)的UI操作邏輯。
??????使用前,動(dòng)態(tài)設(shè)置ImageView的顯示,在view里面的操作如下:

ImageView mImg = findViewById(R.id.img);
mImg.setImageResource(R.drawable.rse);

??????使用databinding后,可以在布局文件中將布局變量賦值以@{}形式給ImageView的src屬性。當(dāng)rightImageRes變化時(shí),ImageView會(huì)動(dòng)態(tài)加載對(duì)應(yīng)的資源文件。

<ImageView
     android:layout_width="200dp"
     android:layout_height="160dp"
     android:layout_below="@+id/name"
     android:layout_centerHorizontal="true"
     android:layout_marginTop="10dp"
     android:scaleType="fitXY"
     android:src="@{guide.rightImageRes}" />

??????使用數(shù)據(jù)綁定布局文件后,view對(duì)應(yīng)的類變?yōu)椋?/p>

package com.hly.learn.fragments;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.hly.learn.ViewModel;
import com.hly.learn.databinding.KeyboardLayoutBinding;

import com.hly.learn.R;

import androidx.databinding.DataBindingUtil;

public class KeyboardFragment extends BaseFragment {

    private ViewModel mViewModel;

    @Override
    public int getLayoutId() {
        return R.layout.keyboard_layout;
    }

    @Override
    public void initData(View view) {

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        //-------------------分析1------------------------------------
        KeyboardLayoutBinding binding = DataBindingUtil.inflate(inflater, R.layout.keyboard_layout, container, false);
        //獲取布局對(duì)應(yīng)的view
        //--------------------分析2-----------------------------------
        View view = binding.getRoot();
        //-------------------分析3----------------------------------
        mViewModel = new ViewModel(mContext);
        binding.setGuide(mViewModel);
        //-------------------分析4----------------------------------
        view.viewpager.setAdapter(xx);
        return view;
    }
}

??????通過以上可以發(fā)現(xiàn),主要的分析點(diǎn)如下:
??????分析1:通過DataBindingUtil來替代inflater來解析數(shù)據(jù)綁定布局文件,KeyboardLayoutBinding是在編譯時(shí)根據(jù)keyboard_layout生成的(后面會(huì)有詳細(xì)分析);如果為Activity,通過setContentView()方法來解析生成KeyboardLayoutBinding;
??????分析2:通過getRoot()獲取布局文件對(duì)應(yīng)的View;
??????分析3:創(chuàng)建ViewModel,并通過setGuide()(名字不是固定的)將View與ViewModel里面的變量建立關(guān)聯(lián);
??????分析4:直接通過view.idName來獲取對(duì)應(yīng)的view,然后進(jìn)行操作;
??????使用了數(shù)據(jù)綁定布局后,view里面除了加載布局后,沒有任何相關(guān)的findViewById操作,確實(shí)減少了不少UI處理邏輯。

d.VM實(shí)現(xiàn)

??????數(shù)據(jù)綁定框架提供了可觀察的變量,像ObservableInt,ObservableBoolean來替代原始數(shù)據(jù)類型int,boolean,使其具備可觀察能力。提供了ObservableField來替代引用數(shù)據(jù)類型,使其具備可觀察能力。具體的代碼實(shí)現(xiàn)如下:

package com.hly.learn;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;

import androidx.databinding.ObservableField;
import androidx.databinding.ObservableInt;

public class ViewModel {

    private Context mContext;
    public ViewModel(Context context) {
        step.set(1);
        mContext = context;
        updateImageInfo(step.get());
    }
    //定義用到的可變變量類型
    public ObservableInt step = new ObservableInt();
    public ObservableField<Drawable> rightImageRes = new ObservableField<>();
    public ObservableField<String> rightImageName = new ObservableField<>();
    public ObservableField<String> rightImageDescription = new ObservableField<>();

    public void nextStep(View view) {
        step.set(step.get() + 1);
        updateImageInfo(step.get());
    }

    public void upStep(View view) {
        step.set(step.get() - 1);
        updateImageInfo(step.get());
    }
    //更新ObservableField對(duì)應(yīng)的變量值
    private void updateImageInfo(int step) {
        rightImageRes.set(ModalData.getDrawable(mContext, step));
        rightImageName.set(ModalData.getImageName(mContext, step));
        rightImageDescription.set(ModalData.getImageDes(mContext, step));
    }
}

??????DataBinding框架用在MVVM模式下,View是加載布局,ViewModel來處理布局對(duì)應(yīng)的交互,Model是來加載數(shù)據(jù)。ViewModel從Model里面去數(shù)據(jù),供UI顯示。
??????最后加入Model模塊:

e.Model實(shí)現(xiàn)
package com.hly.learn;

import android.content.Context;
import android.graphics.drawable.Drawable;

public class ModalData {

    public static Drawable getDrawable(Context context, int index) {
        if (index == 1) {
            return context.getResources().getDrawable(R.drawable.ic_chuancai);
        } else if (index == 2) {
            return context.getResources().getDrawable(R.drawable.ic_lucai);
        } else {
            return context.getResources().getDrawable(R.drawable.ic_xiangcai);
        }
    }

    public static String getImageName(Context context, int index) {
        if (index == 1) {
            return context.getResources().getString(R.string.chuancai);
        } else if (index == 2) {
            return context.getResources().getString(R.string.lucai);
        } else {
            return context.getResources().getString(R.string.xiangcai);
        }
    }

    public static String getImageDes(Context context, int index) {
        if (index == 1) {
            return context.getResources().getString(R.string.chuancaides);
        } else if (index == 2) {
            return context.getResources().getString(R.string.lucaides);
        } else {
            return context.getResources().getString(R.string.xiangcaides);
        }
    }
}
f.事件綁定

??????事件綁定也是一種變量綁定,只不過設(shè)置的變量是回調(diào)接口而已。
??????點(diǎn)擊TextView響應(yīng),在view里面的操作如下:

TextView tv = view.findViewById(R.id.tv);
tv.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
          upStep();
      }
});

??????使用databinding后,可以在布局文件中將TextView的onClick屬性加入執(zhí)行方法即可,當(dāng)TextView點(diǎn)擊后,會(huì)執(zhí)行相應(yīng)的方法。

<TextView
    android:layout_width="100dp"
    android:layout_height="50dp"
    android:onClick="@{guide::upStep}"
    android:text="@string/up"
    android:visibility="@{guide.step!=1?View.VISIBLE:View.INVISIBLE}"

三.DataBinding原理分析

??????DataBinding實(shí)現(xiàn)了數(shù)據(jù)與UI綁定,那是如何綁定的呢?數(shù)據(jù)變化后是如何對(duì)應(yīng)UI進(jìn)行更新的呢?一起看一下實(shí)現(xiàn)過程。
??????使用AndroidStudio進(jìn)行開發(fā)時(shí),對(duì)應(yīng)用編譯后,會(huì)在對(duì)應(yīng)目錄下生成額外的文件:
??????在build/generated/ap_generated_sources/debug/out目錄下會(huì)生成對(duì)應(yīng)的文件:DataBinderMapperImpl.java(兩個(gè)),KeyboardLayoutBindingImpl.java文件;
??????在build/generated/data_binding_base_class_source_out/debug/out目錄下會(huì)生成對(duì)應(yīng)的文件:KeyboardLayoutBinding.java;
??????在build/intermediates/incremental/mergeDebugResources/stripped.dir/layout目錄下會(huì)生成對(duì)應(yīng)的布局文件:keyboard_layout.xml;
??????接下來會(huì)一一進(jìn)行分析。
??????上面我們看到,在KeyBoardFragment內(nèi)部的onCreateView()內(nèi)部執(zhí)行的邏輯,再一起看一下:

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    KeyboardLayoutBinding binding = DataBindingUtil.inflate(inflater, R.layout.keyboard_layout, container, false);
    View view = binding.getRoot();
    mViewModel = new ViewModel(mContext);
    binding.setGuide(mViewModel);
    return view;
}
a.DataBindingUtil.java

??????在Fragement的onCreateView()內(nèi)部通過DataBindingUtil.inflate()來創(chuàng)建KeyboardLayoutBinding對(duì)象binding,先看一下DataBindingUtil的inflate()實(shí)現(xiàn):

private static DataBinderMapper sMapper = new DataBinderMapperImpl();

public static <T extends ViewDataBinding> T inflate(@NonNull LayoutInflater inflater,
            int layoutId, @Nullable ViewGroup parent, boolean attachToParent) {
    return inflate(inflater, layoutId, parent, attachToParent, sDefaultComponent);
}

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

??????inflate()內(nèi)部會(huì)通過inflater,inflate()來加載對(duì)應(yīng)layoutId的view,由于attachToParent為false,所以在inflate()內(nèi)部直接執(zhí)行bind()方法,在bind()內(nèi)部會(huì)通過sMapper.getDataBinder()來返回ViewDataBinding的實(shí)現(xiàn)類對(duì)象,sMapper是DataBinderMapperImpl對(duì)象,接下來看一下DataBinderMapperImpl實(shí)現(xiàn)。

b.DataBinderMapperImpl.java

??????前面說到,DataBinderMapperImpl.java是編譯生成的文件,繼承了MergedDataBinderMapper類,內(nèi)部就一個(gè)構(gòu)造方法內(nèi)執(zhí)行了addMapper()方法:

package androidx.databinding;

public class DataBinderMapperImpl extends MergedDataBinderMapper {
  DataBinderMapperImpl() {
    addMapper(new com.hly.learn.DataBinderMapperImpl());
  }
}

??????在創(chuàng)建DataBinderMapperImpl對(duì)象的時(shí)候,在構(gòu)造方法內(nèi)執(zhí)行addMapper()將創(chuàng)建com.hly.learn.DataBinderMapperImpl()對(duì)象作為參數(shù)傳入,后續(xù)通過sMapper.getDataBinder()來獲取對(duì)應(yīng)的ViewDataBinding的實(shí)現(xiàn)類時(shí),會(huì)最終調(diào)用到com.hly.learn.DataBinderMapperImpl.java中的getDataBinder()方法,接下來看一下com.hly.learn.DataBinderMapperImpl.java這個(gè)類的邏輯實(shí)現(xiàn):

c.DataBinderMapperImpl.java

??????此DataBinderMapperImpl.java不同于b步的DataBinderMapperImpl.java,注意區(qū)分,該類是主要的實(shí)現(xiàn)類:

public class DataBinderMapperImpl extends DataBinderMapper {
    private static final int LAYOUT_KEYBOARDLAYOUT = 1;
    private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(1);
    static {
        INTERNAL_LAYOUT_ID_LOOKUP.put(com.hly.learn.R.layout.keyboard_layout, LAYOUT_KEYBOARDLAYOUT);
    }

  @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_KEYBOARDLAYOUT: {
          if ("layout/keyboard_layout_0".equals(tag)) {
            return new KeyboardLayoutBindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for keyboard_layout is invalid. Received: " + tag);
        }
      }
    }
    return null;
  }
  ......
}

??????DataBinderMapperImpl實(shí)現(xiàn)了DataBinderMapper類,定義了靜態(tài)變量LAYOUT_KEYBOARDLAYOUT和靜態(tài)數(shù)組INTERNAL_LAYOUT_ID_LOOKUP(可能有多個(gè)使用databinding的layout),在靜態(tài)代碼塊內(nèi)將R.layout.keyboard_layout與LAYOUT_KEYBOARDLAYOUT建立映射關(guān)系存入INTERNAL_LAYOUT_ID_LOOKUP內(nèi),在執(zhí)行g(shù)etDataBinder時(shí),通過創(chuàng)建時(shí)傳入的layoutId從INTERNAL_LAYOUT_ID_LOOKUP找到對(duì)應(yīng)的localizedLayoutId,然后根據(jù)view的tag關(guān)系來創(chuàng)建KeyboardLayoutBindingImpl對(duì)象,該tag體現(xiàn)在對(duì)應(yīng)生成的布局文件:

<?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent" android:tag="layout/keyboard_layout_0" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">

        <TextView
            android:id="@+id/name"
            .....
            android:tag="binding_1"  />

        <ImageView
            .......
            android:tag="binding_2" />

        <TextView
            .......
            android:tag="binding_3" />

        <TextView
            .......
            android:tag="binding_4"           
            ....../>

        <TextView
            ........
            android:tag="binding_5"             
            ......./>

        <TextView
            .........
            android:tag="binding_6"  />

    </RelativeLayout>

??????簡(jiǎn)單總結(jié)一下:在onCreateView()內(nèi)部執(zhí)行DataBindingUtil.inflate(),通過一步一步的調(diào)用后,最終返回的是KeyboardLayoutBindingImpl對(duì)象,該類是KeyboardLayoutBinding的實(shí)現(xiàn)類。
??????接下來看一下KeyboardLayoutBindingImpl.java這個(gè)類:

d.KeyboardLayoutBindingImpl.java
public class KeyboardLayoutBindingImpl extends KeyboardLayoutBinding  {
    private KeyboardLayoutBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 5
            , (android.widget.TextView) bindings[1]
            );
        this.mboundView0 = (android.widget.RelativeLayout) bindings[0];
        this.mboundView0.setTag(null);
        this.mboundView2 = (android.widget.ImageView) bindings[2];
        this.mboundView2.setTag(null);
        this.mboundView3 = (android.widget.TextView) bindings[3];
        this.mboundView3.setTag(null);
        this.mboundView4 = (android.widget.TextView) bindings[4];
        this.mboundView4.setTag(null);
        this.mboundView5 = (android.widget.TextView) bindings[5];
        this.mboundView5.setTag(null);
        this.mboundView6 = (android.widget.TextView) bindings[6];
        this.mboundView6.setTag(null);
        this.name.setTag(null);
        setRootTag(root);
        // listeners
        invalidateAll();
    }

    @Override
    public void invalidateAll() {
        synchronized(this) {
                mDirtyFlags = 0x40L;
        }
        requestRebind();
    }

    public void setGuide(@Nullable com.hly.learn.viewmodel.ViewModel Guide) {
        this.mGuide = Guide;
        synchronized(this) {
            mDirtyFlags |= 0x20L;
        }
        notifyPropertyChanged(BR.guide);
        super.requestRebind();
    }

    @Override
    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        switch (localFieldId) {
            case 0 :
                return onChangeGuideRightImageDescription((androidx.databinding.ObservableField<java.lang.String>) object, fieldId);
       ......
    }

    @Override
    protected void executeBindings() {
        ......
        //注冊(cè)觀察者
        updateRegistration(0, guideRightImageDescription);
        ......
        //回調(diào)后進(jìn)行UI更新
        androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView3, guideRightImageDescriptionGet);
        ......
    }
}

??????該類繼承了KeyboardLayoutBinding類,在構(gòu)造方法內(nèi),會(huì)調(diào)用父類的構(gòu)造方法,將root view[即對(duì)應(yīng)R.layout.keyboard_layout進(jìn)行inflate生成的view]傳給父類,在Fragment內(nèi)部的onCreateView()中通過getRoot()返回對(duì)應(yīng)layoutId創(chuàng)建的View,其他方法的邏輯執(zhí)行會(huì)在接下來的數(shù)據(jù)與UI綁定時(shí)進(jìn)行介紹。

e.KeyboardLayoutBinding.java
public abstract class KeyboardLayoutBinding extends ViewDataBinding {
  @NonNull
  public final TextView name;

  @Bindable
  protected ViewModel mGuide;

  protected KeyboardLayoutBinding(Object _bindingComponent, View _root, int _localFieldCount,
      TextView name) {
    super(_bindingComponent, _root, _localFieldCount);
    this.name = name;
  }

  public abstract void setGuide(@Nullable ViewModel guide);

  @Nullable
  public ViewModel getGuide() {
    return mGuide;
  }
  .......
  .......
}

??????KeyboardLayoutBinding是個(gè)抽象類,繼承了ViewDataBinding。

f.ViewDataBinding.java
public abstract class ViewDataBinding extends BaseObservable implements ViewBinding {
    .......
    .......
    private final View mRoot;
    .......
    protected ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount) {
        mBindingComponent = bindingComponent;
        mLocalFieldObservers = new WeakListener[localFieldCount];
        this.mRoot = root;
        if (Looper.myLooper() == null) {
            throw new IllegalStateException("DataBinding must be created in view's UI Thread");
        }
       .......
    }

    public View getRoot() {
        return mRoot;
    }
    .......
    .......

??????在創(chuàng)建KeyboardLayoutBindingImpl后,構(gòu)造方法會(huì)一步一步的向上傳,最終將root view保存在ViewDataBinding中,然后通過getRoot()獲取到view。

四.數(shù)據(jù)與UI綁定分析

??????通過上述分析可以看到了DataBindingUtil.inflate創(chuàng)建KeyboardLayoutBinding的整個(gè)過程,那數(shù)據(jù)與UI任何綁定的呢?
??????在KeyboardLayoutBindingImpl的構(gòu)造方法內(nèi),會(huì)調(diào)用 invalidateAll(),接下來看一下綁定流程:

a.ObservableField進(jìn)行observe()

??????DataBinding使用的是觀察者模式,ObservableField數(shù)據(jù)注冊(cè)觀察者是在創(chuàng)建DataBinding的時(shí)候在構(gòu)造方法中就執(zhí)行了,先創(chuàng)建了對(duì)應(yīng)ObservableField數(shù)量的WeakListener數(shù)組,然后執(zhí)行流程如下:
??????------>invalidateAll()
????????????------>requestRebind()
??????????????????------>executePendingBindings()
????????????????????????------>executeBindingsInternal()
??????????????????????????????------> executeBindings()[Impl]
????????????????????????????????????------>updateRegistration(localFieldId, Observable observable)
??????????????????????????????????????????------>updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER)[創(chuàng)建WeakPropertyListener]
????????????????????????????????????????????????------>registerTo()[將上步中創(chuàng)建的WeakPropertyListener賦值給執(zhí)行創(chuàng)建的WeakListener對(duì)應(yīng)的數(shù)組值]
??????????????????????????????????????????????????????------>listener.setTarget(observable)
????????????????????????????????????????????????????????????------>WeakPropertyListener.addListener(Observable)
??????????????????????????????????????????????????????????????????------>Observable.addOnPropertyChangedCallback(this);
??????經(jīng)過以上邏輯執(zhí)行,Observable[ObservableField]注冊(cè)了OnPropertyChanged callback,如果數(shù)據(jù)變化后,會(huì)回調(diào)OnPropertyChanged()方法,流程圖如下:


image.png

??????以上就是數(shù)據(jù)與UI綁定過程,那數(shù)據(jù)變化后,是如何反饋到UI上呢?接下來看一下數(shù)據(jù)變化后UI更新流程:

b.ObservableField數(shù)據(jù)變化后UI更新

??????ObservableFiled數(shù)據(jù)變化后,最終UI更新執(zhí)行流程如下:
??????------>set(value)
????????????------>notifyChange()
??????????????????------>mCallbacks.notifyCallbacks(this, 0, null)[mCallbacks是PropertyChangeRegistry,通過上述addOnPropertyChangedCallback()加入mCallbacks列表]
????????????????????????------>mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2)
??????????????????????????????------>callback.onPropertyChanged(sender, arg)
????????????????????????????????????------>WeakPropertyListener.onPropertyChanged()
??????????????????????????????????????????------>handleFieldChange()
????????????????????????????????????????????????------>onFieldChange()------>requestRebind()----->......
??????????????????????????????????????????????????????------>executeBindings()[Impl]
??????在ObservableField數(shù)據(jù)變化后,最終會(huì)調(diào)用到Impl類里面的executeBindings()來更新UI,流程圖如下:


image.png

??????以上就是數(shù)據(jù)變化后UI更新的整個(gè)流程。

五.BindingAdapter

??????DataBinding提供了BindingAdapter這個(gè)注解用于支持自定義屬性,或者是修改原有屬性。注解值可以是已有的 xml 屬性,例如 android:src、android:text等,也可以自定義屬性然后在 xml 中使用。
??????例如,對(duì)于一個(gè)TextView ,希望在某個(gè)變量值發(fā)生變化時(shí),可以動(dòng)態(tài)改變顯示的文字,此時(shí)就可以通過 BindingAdapter來實(shí)現(xiàn)。
??????需要先定義一個(gè)靜態(tài)方法,為之添加 BindingAdapter 注解,注解值是為TextView控件自定義的屬性名,而該靜態(tài)方法的兩個(gè)參數(shù)可以這樣來理解:當(dāng)TextView控件的 step屬性值發(fā)生變化時(shí),DataBinding 就會(huì)將TextView實(shí)例以及新的step值傳遞給setProperty() 方法,從而可以根據(jù)此動(dòng)態(tài)來改變TextView的相關(guān)屬性。

<TextView
     android:layout_width="200dp"
     android:layout_height="50dp"
     android:layout_below="@+id/name"
     android:layout_centerHorizontal="true"
     android:layout_marginTop="400dp"
     android:gravity="center"
     android:textStyle="bold"
     app:step="@{guide.step}"/>

??????BindingAdapter實(shí)現(xiàn)如下:

package com.hly.learn;

import android.util.Log;
import android.widget.TextView;

import androidx.databinding.BindingAdapter;
//可以單獨(dú)寫一個(gè)類,統(tǒng)一處理所有使用BindingAdapter注解的控件 
public class ViewBinding {
    @BindingAdapter(value = {"app:step"})
    public static void setProperty(TextView textView, int step) {
        Log.e("Seven", "step is: " + step);
        //可以根據(jù)step來設(shè)置textView的屬性,例如改變文字,設(shè)置寬高等...
        textView.setText(xxx);
    }

    @BindingAdapter(value = {"app:url"})
    public static void updateImg(ImageView imageView, String url) {
        Glide.with(imageView.getContext()).load(url).into(imageView);
    }
}

六.效果圖

1.jpg

2.jpg

??????以上是結(jié)合實(shí)例及源碼對(duì)DataBinding及ObservableField進(jìn)行UI綁定及更新進(jìn)行了分析,詳細(xì)過程還需閱讀ViewDataBinding、BaseObservable、PropertyChangeRegistry、CallbackRegistry等類文件來進(jìn)一步分析。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容