【Jetpack】DataBinding(五):可观察数据和双向数据绑定

Quibbler 2020-12-30 760

可观察数据和双向数据绑定


        Jetpack中的库相互结合使用才能发挥更大用处。想到之前了解过的ViewModelLiveData,结合目前学习的DataBinding能否让界面自动随着数据变化而更新:

    UserModel userModel = new ViewModelProvider(this).get(UserModel.class);
    MutableLiveData<String> name = userModel.getUserName();
    name.observe(this, new Observer<String>() {
        @Override
        public void onChanged(String s) {
            dataBindingBinding.setName(s);
        }
    });

        通过上面的方法是可以的,但是还可以更简洁!来看看DataBinding中的可观察数据绑定及双向数据绑定。可以简化很多繁琐的数据获取同步操作:比如当一个View发生变化,同步改变Model中的数据,并且同步到另一个View中。



1、可观察数据

        可观察数据对象绑定到界面并且该数据对象的属性发生更改时,界面会自动更新。所有的可观察数据对象均实现Observable接口

    public interface Observable {
        /**
         * 添加一个回调函数来监听可观察对象的变化。
         */
        void addOnPropertyChangedCallback(OnPropertyChangedCallback callback);
        
        /**
         * 移除回调。
         */
        void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback);
        
        /**
         * 当可观察对象的属性发生变化时,可观察对象调用的回调函数。
         */
        abstract class OnPropertyChangedCallback {
            public abstract void onPropertyChanged(Observable sender, int propertyId);
        }
    }

        DataBinding库中已经预先有很多类实现Observable接口方便开发者直接使用:



1.1、可观察对象

        开发者不需要实现顶级可观察Observable接口,DataBinding库提供了用于实现监听器注册机制的BaseObservable抽象类,内部做了大量的封装处理。开发者只需要继承BaseObservable,给getter方法添加Bindable注释,然后在setter中调用notifyPropertyChanged()方法更新指定的id值,或者notifyChange()更新所有的值:

    private static class User extends BaseObservable {
        private String firstName;
        private String lastName;
        
        @Bindable
        public String getFirstName() {
            return this.firstName;
        }
        
        @Bindable
        public String getLastName() {
            return this.lastName;
        }
        
        public void setFirstName(String firstName) {
            this.firstName = firstName;
            notifyPropertyChanged(BR.firstName);
        }
        
        public void setLastName(String lastName) {
            this.lastName = lastName;
            notifyPropertyChanged(BR.lastName);
        }
    }

        BR类之前在DataBinding布局绑定类中有提到过,它是DataBinding自动在模块包中生成的类,包含了用于数据绑定的资源的ID。


1.2、可观察字段

        对于简单的基本数据类型,可以用ObservableField<T>包装起来。可以将1.1节中的Employee类简化,不再需要继承BaseObservable,也不需要手动调用notifyChange()更新值域:

    public class Employee {
        private ObservableField<String> name;
        
        public Employee(String name) {
            this.name = new ObservableField<String>(name);
        }
        
        public String getName() {
            return name.get();
        }
        
        public void setName(String name) {
            this.name.set(name);
        }
    }

        当然DataBinding提供了实现好的可观察基本数据类型:ObservableBooleanObservableByteObservableCharObservableDoubleObservableFloatObservableIntObservableLongObservableParcelableObservableShort。它们都是继承自BaseObservable。上面的类可以改成使用基本数据类型可观察对象作为成员变量:

    public class Employee {
        private ObservableInt age;
        
        public Employee(int age) {
            this.age = new ObservableInt(age);
        }
        
        public int getAge() {
            return age.get();
        }
        
        public void setAge(int age) {
            this.age.set(age);
        }
    }


1.3、可观察集合

        对于复杂类型建议使用ObservableField<T>包装起来。Android开发中最重要的数据结构之一就是结合,客户端中各种各样的数据集合。DataBinding也为开发者提供了现成的集合可观察对象类:

        ObservableArrayList可观察集合实现ObservableList接口;

    public class ObservableArrayList<T> extends ArrayList<T> implements ObservableList<T> {
        ...
    }

        ObservableArrayMap<K, V>可观察映射Map实现ObservableMap<K, V>接口。

    public class ObservableArrayMap<K, V> extends ArrayMap<K, V> implements ObservableMap<K, V> {
        ...
    }

        它们都将原理ArrayList、ArrayMap中的添加修改元素方法封装了一遍,所有修改、添加、移除元素都会回调通知View层更新显示。

例:

    ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
    user.put("firstName", "Google");
    user.put("lastName", "Inc.");
    user.put("age", 17);

        在data binding layout布局中可以使用集合中的任意数据,集合数据变化时会回调通知更新界面,也不用担心发送IndexOutOfBoundException发生。

    <TextView
        android:text="@{String.valueOf(employee.mates)}" />
        
    <TextView
        android:text="@{(String)(employee.mates.get(`firstName`))}" />
        //甚至可以在布局中进行类型转换...这和直接在布局中写代码有什么区别...



2、双向数据绑定

        到这里我们一直在从Model =>View,数据驱动界面。但是实际上View层也会因用户输入、点击等原因发生改变。如何实时获取界面的变化,从单向的数据驱动界面变成反向或者是双向的数据流动

Model <==>View


2.1、定义双向绑定类

        定义的双向绑定数据类型必须实现Observable接口,这里直接继承BaseObservable抽象类即可。

    public class DataView extends BaseObservable {
        public Boolean is = Boolean.FALSE;
        
        @Bindable
        public Boolean getChecked() {
            return is;
        }
        
        public void setChecked(Boolean isChecked) {
            // Avoids infinite loops.
            if (is != isChecked) {
                is = isChecked;
                // Notify observers of a new value.
                notifyPropertyChanged(BR.checked);
            }
        }
        
    }

        getter和setter方法名分别为:getChecked和setChecked,所以布局中可以直接使用checked属性。定义双向数据绑定时,注意不要引入无限循环。通过比较方法中的新值和旧值,可以打破可能出现的无限循环。


2.2、@={} 双向绑定

        在绑定布局中绑定变量时使用@={variable}语法,没错和之前学习的绑定语法 @{} 只多了一个 = 符号。

    <CheckBox
        android:value="@={data.isChecked}" />

        如果需要双向绑定的对象类并没有实现Observable接口,那么绑定编译时会报错:Bindable must be on a member in an Observable class. DataViewModel is not Observable


2.3、支持双向特性属性

        Android View控件有很多支持双向绑定的属性,Google官网贴了一些:

ClassAttribute(s)Binding adapter
AdapterViewandroid:selectedItemPositionAdapterViewBindingAdapter
CalendarViewandroid:dateCalendarViewBindingAdapter
CompoundButtonandroid:checkedCompoundButtonBindingAdapter
DatePickerandroid:yearDatePickerBindingAdapter
NumberPickerandroid:valueNumberPickerBindingAdapter
RadioButtonandroid:checkedButtonRadioGroupBindingAdapter
RatingBarandroid:ratingRatingBarBindingAdapter
SeekBarandroid:progressSeekBarBindingAdapter
TabHostandroid:currentTabTabHostBindingAdapter
TextViewandroid:textTextViewBindingAdapter
TimePickerandroid:hourTimePickerBindingAdapter



3、示例

        好记性不如烂笔头,纸上谈兵不如实战。实现开篇的那个效果:

 View <==> Model <==>View


3.1、使用现成的ObservableInt

        对于简单的需求,这样的方法最简单直接。不建议把简单的事情搞复杂。直接定义基本可观察类型数据:

    <variable
        name="progress"
        type="androidx.databinding.ObservableInt" />

        data binding layout布局中直接使用即可:

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="@{String.valueOf(progress)}" />
    <ProgressBar
        style="@style/Widget.AppCompat.ProgressBar.Horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="15dp"
        android:progress="@{progress}" />
    <SeekBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:progress="@={progress}" />

        绑定布局的时候,拿到绑定对象设置绑定布局中定义的变量。

    dataBindingBinding.setProgress(new ObservableInt(50));


3.2、自定义可观察对象

        演示一下,封装简单的Integer类型,其实可以举一反三对于项目上更复杂类型同样可以做到封装成为可观察数据:

    public class DataViewModel extends BaseObservable {
        public int mProgress = 0;
        
        @Bindable
        public int getProgress() {
            return mProgress;
        }
        
        public void setProgress(int progress) {
            // Avoids infinite loops.
            if (mProgress != progress) {
                mProgress = progress;
                // Notify observers of a new value.
                notifyPropertyChanged(BR.progress);
            }
        }
        
    }

        在绑定布局中设置自定义的可观察对象值:

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="@{String.valueOf(data.progress)}" />
    <ProgressBar
        style="@style/Widget.AppCompat.ProgressBar.Horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="15dp"
        android:progress="@{data.progress}" />
    <SeekBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:progress="@={data.progress}" />

        就可以实现多个View状态同步的效果:




参考资料:

        Data Binding:Work with observable data objects

        Data Binding:Two-way data binding

        Two-way attributes

        使用可观察的数据对象


不忘初心的阿甘
最新回复 (0)
    • 安卓笔记本
      2
        登录 注册 QQ
返回
仅供学习交流,切勿用于商业用途。如有错误欢迎指出:fluent0418@gmail.com