databinding

183 阅读3分钟

学习

如果想要使UI可以刷新,需要使用类型为BaseObservable来更新此类中的属性展示到UI上。 例如:

public class MainData extends BaseObservable {

    private String name = "dataBinding";

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

}

layout文件

<layout
    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">
    <data>

        <variable
            name="viewModel"
            type="com.example.sourcestudy20221111.data.MainData" />
    </data>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/CView"
        android:text="@{viewModel.name}"
        >

    </TextView>

</layout>

测试demo

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        BingdingDemoBinding vd = DataBindingUtil.setContentView(this,R.layout.bingding_demo);
        vd.setLifecycleOwner(this);
        MainData viewmodel = new MainData();
        vd.setViewModel(viewmodel);
        System.out.println(vd.getViewModel().getName());
        vd.CView.setOnClickListener(v -> {
            vd.getViewModel().setName("hello");
            System.out.println("点击了");
            viewmodel.notifyPropertyChanged(BR.viewModel);//没有效果
        });
    }

如果使用生成的ViewDataBinding调用notifyPropertyChanged可以更新UI。但这里不包括导入的对象,在这个例子中是viewmodel,即使viewmodel.notifyPropertyChanged(BR.viewModel);也没有效果,它不在失效的统计范围。

实际更新UI的地方是自动生成的DataBindin源文件的executeBindings方法。

这个源文件的位置在:build/generated/ap_generated_sources/debug/out文件夹下后缀是Impl的那个,这个是实现类。

如果使用自动生成的ViewDataBinding调用notifyPropertyChanged传递任何BRid都没有效果!

public void notifyPropertyChanged(int fieldId) {
    synchronized (this) {
        if (mCallbacks == null) {
            return;
        }
    }
    mCallbacks.notifyCallbacks(this, fieldId, null);
}

即使是这里的viewmodelPOJO类最终都会回到ViewBinding中去处理UI更新,因为当调用setViewModel(自动生成的方法)时就会绑定到该ViewDataBinding,会间接拥有该binder的引用。

但是如果用vd.notifyPropertyChanged调用,并不会产生效果,因为顶层的ViewDataBinding的callback为null,所以会直接返回。viewmodel有callback是因为在setViewModel中调用了updateRegistration。

BR中无法允许重名变量存在,所以无法bind相同变量名。

ObservavaleField的工作原理

即使用ObservableField也需要添加@Bindable注解。

自动生成的

if (viewModel != null) {
    // read viewModel.age
    viewModelAge = viewModel.getAge();
}
@Bindable
    private ObservableField<String> age = new ObservableField<>("17");
    
    public void setAge(String age) {
        this.age.set(age);
    }

ObservavaleField.set会自动调用BaseObservalable的notifyChange来刷新UI,但是这种是全局刷新的!

双向绑定原理

自动生成的Impl类中由于这么一行代码

androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.CView2, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, CView2androidTextAttrChanged);
@BindingAdapter(value = {"android:beforeTextChanged", "android:onTextChanged",
        "android:afterTextChanged", "android:textAttrChanged"}, requireAll = false)
public static void setTextWatcher(TextView view, final BeforeTextChanged before,
        final OnTextChanged on, final AfterTextChanged after,
        final InverseBindingListener textAttrChanged) {
    final TextWatcher newValue;
    if (before == null && after == null && on == null && textAttrChanged == null) {
        newValue = null;
    } else {
        newValue = new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                if (before != null) {
                    before.beforeTextChanged(s, start, count, after);
                }
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if (on != null) {
                    on.onTextChanged(s, start, before, count);
                }
                if (textAttrChanged != null) {
                    textAttrChanged.onChange();
                }
            }

            @Override
            public void afterTextChanged(Editable s) {
                if (after != null) {
                    after.afterTextChanged(s);
                }
            }
        };
    }
    final TextWatcher oldValue = ListenerUtil.trackListener(view, newValue, R.id.textWatcher);
    if (oldValue != null) {
        view.removeTextChangedListener(oldValue);
    }
    if (newValue != null) {
        view.addTextChangedListener(newValue);
    }
}

我们通过代码可以发现,他是通过添加了text文字变化的Listener,这个添加方法是TextView自带的。

但是该静态方法上面的注解是什么用呢?

BindingAdapter

下面是官方给出的解释。

BindingAdapter 应用于用于操纵如何将带有表达式的值设置为视图的方法。最简单的例子是有一个公共静态方法,它接受视图和要设置的值:

  @BindingAdapter("android:bufferType")
   public static void setBufferType(TextView view, TextView.BufferType bufferType) {
       view.setText(view.getText(), bufferType);
   }
在上面的示例中,当在 TextView 上使用 android:bufferType 时,将调用 setBufferType 方法。
如果首先列出旧值,也可以采用先前设置的值:

  @BindingAdapter("android:onLayoutChange")
   public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
                                                View.OnLayoutChangeListener newValue) {
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
           if (oldValue != null) {
               view.removeOnLayoutChangeListener(oldValue);
           }
           if (newValue != null) {
               view.addOnLayoutChangeListener(newValue);
           }
       }
   }
当绑定适配器也可能带有多个属性时,只有当与绑定适配器关联的所有属性都具有与其关联的绑定表达式时,才会调用它。当属性之间存在不寻常的交互时,这很有用。例如:

  @BindingAdapter({"android:onClick", "android:clickable"})
   public static void setOnClick(View view, View.OnClickListener clickListener,
                                 boolean clickable) {
       view.setOnClickListener(clickListener);
       view.setClickable(clickable);
   }
参数的顺序必须与 BindingAdapter 中值中属性的顺序相匹配。
绑定适配器也可以选择将扩展 DataBindingComponent 的类作为第一个参数。如果是这样,它将被传递绑定期间传入的值,直接在 inflate 方法中或间接地使用来自DataBindingUtil.getDefaultComponent()的值。
如果绑定适配器是实例方法,则生成的 DataBindingComponent 将有一个 getter 来检索 BindingAdapter 类的实例以用于调用该方法。

云里雾里的!

到底是什么作用呢?

比如: @BindingAdapter("android:text") 当对layout的android:text属性进行了绑定,那么当需要更新UI时首先会调用被这个注解修饰的方法,那么他为什么会调用这个方法呢?

首先我们要明白一个问题:我们在更新UI时我们并没有调用setText方法,而是调用了notify方法,无论如何我们更新UI都是在自动生成的文件的executeBindings方法。

这里的更新UI方法是这样的

androidx.databinding.adapters.TextViewBindingAdapter.setText(this.CView2, viewModelAge);

单向绑定和双向绑定都会使用被BindingAdapter修饰的方法更新UI。