JetPack DataBinding原理分析4

614 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情

      接下来看一下KeyboardLayoutBindingImpl.java这个类:

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.xxx.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() {
        ......
        //注册观察者
        updateRegistration(0, guideRightImageDescription);
        ......
        //回调后进行UI更新
        androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView3, guideRightImageDescriptionGet);
        ......
    }
}

      该类继承了KeyboardLayoutBinding类,在构造方法内,会调用父类的构造方法,将root view[即对应R.layout.keyboard_layout进行inflate生成的view]传给父类,在Fragment内部的onCreateView()中通过getRoot()返回对应layoutId创建的View,其他方法的逻辑执行会在接下来的数据与UI绑定时进行介绍。

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是个抽象类,继承了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;
    }
    .......
    .......

      在创建KeyboardLayoutBindingImpl后,构造方法会一步一步的向上传,最终将root view保存在ViewDataBinding中,然后通过getRoot()获取到view。

四.数据与UI绑定分析

      通过上述分析可以看到了DataBindingUtil.inflate创建KeyboardLayoutBinding的整个过程,那数据与UI任何绑定的呢?
在KeyboardLayoutBindingImpl的构造方法内,会调用 invalidateAll(),接下来看一下绑定流程:

a.ObservableField进行observe()

      DataBinding使用的是观察者模式,ObservableField数据注册观察者是在创建DataBinding的时候在构造方法中就执行了,先创建了对应ObservableField数量的WeakListener数组,然后执行流程如下:
------>invalidateAll()
------>requestRebind()
------>executePendingBindings()
------>executeBindingsInternal()
------> executeBindings()[Impl]
------>updateRegistration(localFieldId, Observable observable)
------>updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER)[创建WeakPropertyListener]
------>registerTo()[将上步中创建的WeakPropertyListener赋值给执行创建的WeakListener对应的数组值]
------>listener.setTarget(observable)
------>WeakPropertyListener.addListener(Observable)
------>Observable.addOnPropertyChangedCallback(this);
经过以上逻辑执行,Observable[ObservableField]注册了OnPropertyChanged callback,如果数据变化后,会回调OnPropertyChanged()方法,流程图如下:

image.png

以上就是数据与UI绑定过程,那数据变化后,是如何反馈到UI上呢?接下来看一下数据变化后UI更新流程:

b.ObservableField数据变化后UI更新

      ObservableFiled数据变化后,最终UI更新执行流程如下:
------>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数据变化后,最终会调用到Impl类里面的executeBindings()来更新UI,流程图如下:

image.png

以上就是数据变化后UI更新的整个流程。

五.BindingAdapter

      DataBinding提供了BindingAdapter这个注解用于支持自定义属性,或者是修改原有属性。注解值可以是已有的 xml 属性,例如 android:src、android:text等,也可以自定义属性然后在 xml 中使用。

      例如,对于一个TextView ,希望在某个变量值发生变化时,可以动态改变显示的文字,此时就可以通过 BindingAdapter来实现。

      需要先定义一个静态方法,为之添加 BindingAdapter 注解,注解值是为TextView控件自定义的属性名,而该静态方法的两个参数可以这样来理解:当TextView控件的 step属性值发生变化时,DataBinding 就会将TextView实例以及新的step值传递给setProperty() 方法,从而可以根据此动态来改变TextView的相关属性。

<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实现如下:

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

import androidx.databinding.BindingAdapter;
//可以单独写一个类,统一处理所有使用BindingAdapter注解的控件 
public class ViewBinding {
    @BindingAdapter(value = {"app:step"})
    public static void setProperty(TextView textView, int step) {
        Log.e("Seven", "step is: " + step);
        //可以根据step来设置textView的属性,例如改变文字,设置宽高等...
        textView.setText(xxx);
    }

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