Jetpack之DataBinding使用方式

1,200 阅读5分钟

一.基础入门

DataBinding 是谷歌官方发布的一个框架,顾名思义即为数据绑定,是 MVVM 模式在 Android 上的一种实现,用于降低布局和逻辑的耦合性,使代码逻辑更加清晰。MVVM 相对于 MVP,其实就是将 Presenter 层替换成了 ViewModel 层。DataBinding 能够省去我们一直以来的 findViewById() 步骤,大量减少 Activity 内的代码,数据能够单向或双向绑定到 layout 文件中,有助于防止内存泄漏,而且能自动进行空检测以避免空指针异常

启用 DataBinding 的方法是在对应 Model 的 build.gradle 文件里加入以下代码,同步后就能引入对 DataBinding 的支持

android { dataBinding { enabled = true } }

1.使用DataBinding页面布局变化

<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>
             <import type="com.liquid.jetpackapp.data.Goods" />
             <variable   
                name="goods"
                type="Goods" />
        </data>
         <!--view根部局-->
        <android.support.constraint.ConstraintLayout 
            android:layout_width="match_parent" 
            android:layout_height="match_parent" 
            tools:context=".MainActivity">
            ...
        </android.support.constraint.ConstraintLayout>
</layout>

和原始布局的区别在于多出了一个 layout 标签将原布局包裹了起来,data 标签用于声明要用到的变量以及变量类型,要实现 MVVM 的 ViewModel 就需要把数据(Model)与 UI(View)进行绑定,data 标签的作用就像一个桥梁搭建了 View 和 Model 之间的通道

2.创建model

public class Goods{
    @Bindable
    public String name;

    private String details;
    ...
}

3.在 data 标签里声明要使用到的变量名、类的全路径

<data>
   <import type="com.liquid.jetpackapp.data.Goods" />
     <variable   
        name="goods"
        type="Goods" />
</data>

4.这里声明了一个Goods类型的变量 goods,我们要做的就是使这个变量与两个 TextView 控件挂钩,通过设置goods的变量值同时使 TextView 显示相应的文本,完整的布局代码如下所示

<TextView
    android:id="@+id/tv_name"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    android:text="@{goods.name}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

<TextView
    android:id="@+id/tv_details"
    app:layout_constraintLeft_toRightOf="@+id/tv_name"
    app:layout_constraintTop_toTopOf="parent"
    android:text="@{goods.details}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

通过@{goods.name}使 TextView 引用到相关的变量,DataBinding 会将之映射到相应的 getter 方法

5.之后可以在 Activity 中通过 DataBindingUtil 设置布局文件,省略原先 Activity 的 setContentView() 方法,并为变量 goods 赋值

private Goods goods;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    goods = new Goods("goods","food",34);
    activityMainBinding.setUserInfo(goods);
}

6.由于 @{goods.name}在布局文件中并没有明确的值,所以在预览视图中什么都不会显示,不便于观察文本的大小和字体颜色等属性,此时可以为之设定默认值(文本内容或者是字体大小等属性都适用),默认值将只在预览视图中显示,且默认值不能包含引号

android:text="@{goods.name,default=food}"

此外,也可以通过 ActivityMainBinding 直接获取到指定 ID 的控件

activityMainBinding.tvName.setText("food");

7.Databinding 同样是支持在 Fragment 和 RecyclerView 中使用 。例如,可以看 Databinding 在 Fragment 中的使用

@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    FragmentBlankBinding fragmentBlankBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_blank,container, false);
    return fragmentBlankBinding.getRoot();
}

二.单向绑定

实现数据变化自动驱动 UI 刷新的方式有三种:BaseObservable、ObservableField、ObservableCollection

1.BaseObservable
一个纯净的 ViewModel 类被更新后,并不会让 UI 自动更新。而数据绑定后,我们自然会希望数据变更后 UI 会即时刷新,Observable 就是为此而生的概念

BaseObservable 提供了 notifyChange() 和 notifyPropertyChanged() 两个方法,前者会刷新所有的值域,后者则只更新对应 BR 的 flag,该 BR 的生成通过注

释 @Bindable 生成,可以通过 BR notify 特定属性关联的视图

public class Goods extends BaseObservable {
    //如果是 public 修饰符,则可以直接在成员变量上方加上 @Bindable 注解
    @Bindable
    public String name;     
    //如果是 private 修饰符,则在成员变量的 get 方法上添加 @Bindable 注解
    private String details;


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

    @Bindable
    public String getDetails() {
    return details;
    }

    public void setDetails(String details) {
        this.details = details;
    notifyChange();
    }
}

在 setName() 方法中更新的只是本字段,而 setDetails() 方法中更新的是所有字段,当更新值的时候,相应的UI显示就会随之更新

2.实现了 Observable 接口的类允许注册一个监听器,当可观察对象的属性更改时就会通知这个监听器,此时就需要用到 OnPropertyChangedCallback 当中 propertyId 就用于标识特定的字段

goods.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
    @Override
    public void onPropertyChanged(Observable sender, int propertyId) {
        switch (propertyId) {
            case BR.details:
                Log.e("MainActivity","details");
                break;
            case BR.name:
                Log.e(""MainActivity","name");
                break;
            case BR.price:
                Log.e(""MainActivity","price");
                break;
        }
    }
});

用来观察goods中属性的值的变化

3.ObservableField

继承于 Observable 类相对来说限制有点高,且也需要进行 notify 操作,因此为了简单起见可以选择使用 ObservableField。ObservableField 可以理解为官方对 BaseObservable 中字段的注解和刷新等操作的封装,官方原生提供了对基本数据类型的封装,例如 ObservableBoolean、ObservableByte、ObservableChar、

ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble 以及 ObservableParcelable ,也可通过 ObservableField 泛型来申明其他类型

public class Goods {

    private ObservableField<String> name;

    private ObservableFloat price;

    private ObservableField<String> details;

    public ObservableGoods(String name, float price, String details) {
        this.name = new ObservableField<>(name);
        this.price = new ObservableFloat(price);
        this.details = new ObservableField<>(details);
    }

    ```
}

对 ObservableGoods 属性值的改变都会立即触发 UI 刷新,概念上与继承BaseObservable区别不大

4.ObservableCollection

dataBinding 也提供了包装类用于替代原生的 List 和 Map,分别是 ObservableList 和 ObservableMap,当其包含的数据发生变化时,绑定的视图也会随之进行刷新

<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>
        <import type="androidx.databinding.ObservableList" />
        <import type="androidx.databinding.ObservableMap" />
        <variable
            name="list"
            type="ObservableList&lt;String&gt;" />

        <variable
            name="map"
            type="ObservableMap&lt;String,String&gt;" />

        <variable
            name="index"
            type="int" />

        <variable
            name="key"
            type="String" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/tv_list"
            app:layout_constraintTop_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            android:text="@{list[index],default=xx}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <TextView
            android:id="@+id/tv_map"
            app:layout_constraintTop_toBottomOf="@+id/tv_list"
            app:layout_constraintStart_toStartOf="parent"
            android:text="@{map[key],default=gg}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>


public class MainActivity extends AppCompatActivity {

        private ObservableList<String> list;
    private ObservableMap map;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
        list = new ObservableArrayList();
        list.add("Ye");
        list.add("leavesC");
        activityMainBinding.setList(list);
        activityMainBinding.setIndex(1);
        map = new ObservableArrayMap();
        map.put("name","lvdouwan");
        map.put("age","24");
        activityMainBinding.setMap(map);
        activityMainBinding.setKey("name");
    }
}

三、双向数据绑定 双向绑定的意思即为当数据改变时同时使视图刷新,而视图改变时也可以同时改变数据

比如当 EditText 的输入内容改变时,会同时同步到变量 goods,绑定变量的方式比单向绑定多了一个等号:android:text="@={goods.name}"

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <import type="com.liquid.jetpackapp.data.Goods"/>
        <variable
            name="goods"
            type="ObservableGoods" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">

        <TextView
            ···
            android:text="@{goods.name}" />

        <EditText
            ···
            android:text="@={goods.name}" />

    </LinearLayout>
</layout>


public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        Goods goods = new Goods("code", "hi", 23);
        activityMainBinding.setGoods(goods);
    }

}

四、事件绑定 严格意义上来说,事件绑定也是一种变量绑定,只不过设置的变量是回调接口而已 事件绑定可用于以下多种回调事件

android:onClick
android:onLongClick
android:afterTextChanged
android:onTextChanged

在 Activity 内部新建一个 GoodsHandler类来声明 onClick() 和 afterTextChanged() 事件相应的回调方法

public class GoodsHandler{
    public void changeGoodsName() {
        goods.setName("code " + new Random().nextInt(100));
        goods.setPrice(new Random().nextInt(100));
    }

    public void changeGoodsDetails() {
        goods.setDetails("hi " + new Random().nextInt(100));
        goods.setPrice(new Random().nextInt(100));
    }

    public void afterTextChanged(Editable s) {

    }

    public void afterUserPassword(Editable s){


        }
}


<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="goodsHandler"
            type="com.liquid.jetpackapp.MainActivity.GoodsHandler" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <Button
            android:id="@+id/btn_one"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            android:text="改变属性 name 和 price"
            android:onClick="@{()->goodsHandler.changeGoodsName()}"
            android:textAllCaps="false"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <Button
            android:id="@+id/btn_two"
            app:layout_constraintTop_toBottomOf="@+id/btn_one"
            app:layout_constraintLeft_toLeftOf="parent"
            android:text="改变属性 details 和 price"
            android:onClick="@{()->goodsHandler.changeGoodsDetails()}"
            android:textAllCaps="false"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>


public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
        activityMainBinding.setGoodsHandler(new GoodsHandler());
    }


        public class GoodsHandler{
        public void changeGoodsName() {
                goods.setName("code " + new Random().nextInt(100));
                goods.setPrice(new Random().nextInt(100));
        }

        public void changeGoodsDetails() {
                goods.setDetails("hi " + new Random().nextInt(100));
                goods.setPrice(new Random().nextInt(100));
        }

        public void afterTextChanged(Editable s) {

        }

        public void afterUserPassword(Editable s){


                }
        }


}