(二)Android Jetpack 組件之數據綁定庫 (DataBinding)

1、概述

數據綁定庫(DataBinding)是一種支持庫,藉助該庫,您可以使用聲明性格式(而非程序化地)將佈局中的界面組件綁定到應用中的數據源。
主要特性簡單來說:

  • I、取代findVewById這種寫法,Butterknife(黃油刀)這個庫也可以不用了;
  • II、可以直接在佈局文件xml中綁定數據(定義變量、調用變量、控件值可直接賦值變量)(基本數據類型、Map、List、實體)變量;
  • III、數據綁定庫支持雙向數據綁定。此類綁定使用的表示法支持以下操作:接收對屬性的數據更改,同時監聽用戶對此屬性的更新。

使用數據綁定庫之前我們需要做2件事:

  • I、在app的build.gradle文件中開啓配置dataBinding;
  • II、創建data binding layout佈局。

這個支持庫在Android Studio3.6.1版本里面已經預製了,所以我們只需要在app的build.gradle文件中添加一行配置即可:

dataBinding {
        enabled = true
    }

爲什麼叫數據綁定?根據案例來具體感受吧。

2、如何創建data binding layout 的佈局?

爲什麼data binding layout佈局會不同,因爲我們要直接在佈局文件xml中進行數據綁定操作,所以改變佈局xml是必然的。
來看一張 常規佈局文件xml vs data binding layout 佈局xml,如下所示:
在這裏插入圖片描述
從此有了這個data binding layout佈局文件了。接下來我們需要在這個佈局文件中添加一些變量及變量賦值等操作。

舉個例子:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <!--字符串-->
        <variable
            name="userName"
            type="java.lang.String" />
        <!--整型包裝類-->
        <variable
            name="age"
            type="java.lang.Integer" />
        <!--整型-->
        <variable
            name="sex"
            type="int" />
        <!--點擊事件-->
        <variable
            name="onClickListener"
            type="android.view.View.OnClickListener" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp"
        tools:context=".NormalActivity">
        <!-- 調用 data 標籤裏面的變量 賦值-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{userName}" />
        <!-- 還可以進行類型轉換 -->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(age)}" />
        <!-- 還可以支持三元運算 -->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(sex==1?'男':'女')}" />
        <!-- 還可以綁定點擊事件 -->

        <Button
            android:id="@+id/na_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{onClickListener}"
            android:text="點我" />
    </LinearLayout>
</layout>

非常直觀,核心就是< data >標籤類的聲明變量,可以在< data >標籤外直接使用,還可以支持Map、List等等。佈局文件準備好了,我們接下來應該關注一下Activity裏面該如何去使用變量或點擊事件。

與上面佈局文件對應的Activity:


import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;

import com.hb.mvvm.databinding.ActivityNormalBinding;

public class NormalActivity extends AppCompatActivity implements View.OnClickListener {
    ActivityNormalBinding dataBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_normal);
        //DataBindingUtil.setContentView 接管了上面這句話,並得到了一個自動注入的 ActivityNormalBinding對象
        dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_normal);
        initData();
    }

    private void initData() {
        dataBinding.setUserName("小明");
        dataBinding.setAge(20);
        dataBinding.setSex(1);

        //先設置點擊事件監聽
        dataBinding.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.na_btn:
                //響應對應按鈕觸發的動作
                Toast.makeText(this,"觸發點擊事件",Toast.LENGTH_SHORT).show();
                break;
        }
    }
}

在例子中的onCreate方法中,可以看到DataBindingUtil.setContentView()這個方法接管了原來的setContentView(),返回了一個ActivityNormalBinding這個對象的實例,通過ActivityNormalBinding這個對象的實例我們就可以給佈局文件xml中聲明的變量賦值或事件了。

ActivityNormalBinding哪來的?

回答:自動生成的,爲什麼直接輸入的時候報錯提示找不到這個類呢?要想自動生成它必須滿足,佈局文件xml是data binding layout的佈局,然後在Activity中通過Alt+Enter進行導入,有時候也可能是編譯器的問題,點下[sync同步按鈕]可解決。
ActivityNormalBinding類的命名規律
R.layout.activity_normal + Binding =ActivityNormalBinding 就是這樣。

: R.layout.activity_main + Binding =ActivityMainBinding。

輸出結果:
在這裏插入圖片描述

3、單向數據綁定與雙向數據綁定

3.1、單向數據綁定

注意:單向綁定變量我們使用: @{變量名}

單向的意思就是在Activity中設置變量的值,會直接反應在xml的控件上。
單向數據綁定的時候我們不得不關注下這三個類,ObservableField、BaseObservable、ObservableCollection,直觀感覺是觀察者模式的類。

Observable開頭的類有什麼用處:

BaseObservable:需要結合@Bindable註解使用進行觀察數據變化,屬性所在類需要繼承它,通知屬性值變化需要手動調用 notifyPropertyChanged(BR.屬性名)或notify()。

ObservableField:聲明變量會自動創建get set方法,同時自動爲屬性添加數據變化監聽,可以添加泛型,同時DataBinding庫已經封裝了基本數據類型和List、Map等,ObservableInt、ObservableDouble。

ObservableCollection:集合,這裏和我們常用的 List 、Map一樣。只不過這裏的ObservableList、ObservableMap是封裝好的。當我們改變集合裏的數據時。xml也會改變。唯一要注意的是,在xml裏引用這些集合的時候<類型>,這些符號,會影響xml格式所以要轉義。用< 代表<;用&gt代表>(這些轉義符,同樣支持Mark Down);

說到底爲什麼會有這三個類型呢?
:個人理解是首先以前寫一個對象類需要自己手動寫get set方法,同時數據變化的時候很少去關注那些屬性值變化了,Observable打頭說明實現了觀察者模式,當數據變化我們可以及時感知到,提醒xml數據更新。同時爲了更多的支持不同類型,8大基本數據類型、引用類型、list、map都進行了Observable封裝,可以實時的觀察這些數據類型值的變化。

3.2、雙向數據綁定

注意:雙向綁定變量我們使用 :@={變量名},比單向綁定多了一個=號。

雙向的意思就是在Activity中設置變量的值,不僅會直接反應在xml的控件上;同時在xml的EditText等輸入型控件上輸入值可以直接傳遞給Activity中的變量並改變變量的值。

如:你在Activity設置了變量a的值爲’test’,你在EditText的android:text屬性上雙向綁定了變量a,則這個EditText上會顯示出這個“test”,不需關閉程序,這時候你繼續在EditText上刪除‘test’時,activity裏面的變量a的值也會變成空,當你再次繼續輸入“hellow”,這時在Activity裏面的變量a的值也會變成“hellow”。

3.3、具體案例

activity_normal.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <!--字符串-->
        <variable
            name="name"
            type="java.lang.String" />
        <!--整型包裝類-->
        <variable
            name="age"
            type="java.lang.Integer" />
        <!--整型-->
        <variable
            name="sex"
            type="int" />
        <!--點擊事件-->
        <variable
            name="onClickListener"
            type="android.view.View.OnClickListener" />
        <!-- 屬性數據變化監聽 雙向綁定-->
        <variable
            name="loginBean"
            type="com.hb.mvvm.entity.LoginBean" />
        <!-- 屬性數據變化監聽 雙向綁定-->
        <variable
            name="boy"
            type="com.hb.mvvm.entity.Boy" />

        <!-- Map類型用法,使用帶觀察者的列表對象ObservableMap 導包-->
        <import
            alias="Map"
            type="androidx.databinding.ObservableMap" />
        <!-- Map類型用法 聲明變量  , 因在xml中不能直接使用<>符號,所以:&lt;代表<,&gt;代表>-->
        <variable
            name="map"
            type="Map&lt;String,Object&gt;" />
        <!-- List類型用法,使用帶觀察者的列表對象ObservableList 導包-->
        <import
            alias="List"
            type="androidx.databinding.ObservableList" />
        <!-- List類型用法 聲明變量,因在xml中不能直接使用<>符號,所以:&lt;代表<,&gt;代表>-->
        <variable
            name="list"
            type="List&lt;String&gt;" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp"
        tools:context=".NormalActivity">
        <!-- 調用 data 標籤裏面的變量 賦值-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{name}" />
        <!-- 還可以進行類型轉換 -->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(age)}" />
        <!-- 還可以支持三元運算 -->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(sex==1?'男':'女')}" />
        <!-- 還可以綁定點擊事件 -->

        <Button
            android:id="@+id/na_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{onClickListener}"
            android:text="點我" />

        <!-- 通過 ObservableField 觀察值變化-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{boy.name}" />
        <!--雙向數據綁定 通過 ObservableField  觀察值變化-->
        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@={boy.name}" />

        <!--雙向數據綁定,改變EditText的值也會改變 LoginBean 屬性 userName的值-->
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="32dp"
            android:hint="請輸入賬號"
            android:text="@={loginBean.userName}" />

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="請輸入密碼"
            android:inputType="textPassword"
            android:text="@={loginBean.password}" />


        <Button
            android:id="@+id/na_btn_login"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{onClickListener}"
            android:text="登錄" />

        <!-- map變量使用-->
        <TextView
            android:layout_width="wrap_content"
            android:text='@{map["name"]}'
            android:layout_height="wrap_content" />

        <!-- list變量使用-->
        <TextView
            android:layout_width="wrap_content"
            android:text='@{list[0]}'
            android:layout_height="wrap_content" />

        <TextView
            android:layout_width="wrap_content"
            android:text='@{list.get(1)}'
            android:layout_height="wrap_content" />

    </LinearLayout>
</layout>

NormalActivity.class


import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.databinding.Observable;
import androidx.databinding.ObservableArrayList;
import androidx.databinding.ObservableArrayMap;
import androidx.databinding.ObservableList;
import androidx.databinding.ObservableMap;

import com.hb.mvvm.databinding.ActivityNormalBinding;
import com.hb.mvvm.entity.Boy;
import com.hb.mvvm.entity.LoginBean;

public class NormalActivity extends AppCompatActivity implements View.OnClickListener {
    ActivityNormalBinding dataBinding;
    LoginBean loginBean;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_normal);
        //DataBindingUtil.setContentView 接管了上面這句話,並得到了一個自動注入的 ActivityNormalBinding對象
        dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_normal);
        initData();
    }

    Boy boy;

    private void initData() {
        dataBinding.setName("小明");
        dataBinding.setAge(20);
        dataBinding.setSex(1);

        //先設置點擊事件監聽
        dataBinding.setOnClickListener(this);
        //傳遞對象雙向綁定數據
        loginBean = new LoginBean();
        dataBinding.setLoginBean(loginBean);

        boy = new Boy();
        dataBinding.setBoy(boy);

        //通過ObservableField通用類、ObservableInt、ObservableDouble等基本數據類型,監聽屬性變化
        boy.name.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
            @Override
            public void onPropertyChanged(Observable sender, int propertyId) {
                //sender: The Observable that is changing.
                //propertyId: The BR identifier of the property that has changed. The getter for this property should be annotated with Bindable.
                Log.d(getPackageName(), "監聽到屬性發生變化:" + boy.name + " ," + propertyId);
            }
        });

        //繼承BaseObservable 觀察屬性變化
        loginBean.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
            @Override
            public void onPropertyChanged(Observable sender, int propertyId) {
                if (propertyId == BR.userName) {
                    Log.d(getPackageName(), "監聽到屬性發生變化:" + loginBean.getUserName());
                } else if (propertyId == BR.password) {
                    Log.d(getPackageName(), "監聽到屬性發生變化:" + loginBean.getPassword());
                }
            }
        });

        //Map使用
        ObservableMap<String, Object> map = new ObservableArrayMap<>();
        map.put("name", "james");
        dataBinding.setMap(map);
        //List使用
        ObservableList<String> mList = new ObservableArrayList<>();
        mList.add("Lucy");
        mList.add("LilySi");
        dataBinding.setList(mList);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.na_btn:
                //響應對應按鈕觸發的動作
                Toast.makeText(this, "觸發點擊事件", Toast.LENGTH_SHORT).show();
                break;
            case R.id.na_btn_login:
                //獲取xml綁定的數據
                String userName = loginBean.getUserName();
                String password = loginBean.getPassword();
                Log.d(getPackageName(), userName + "," + password);
                //響應對應按鈕觸發的動作
                Toast.makeText(this, userName + "正在登錄。。。", Toast.LENGTH_SHORT).show();
                break;
            default:
                break;
        }
    }
}

Boy.class


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

/**
 * Created by Administrator
 * on 2020/3/25
 */
public class Boy {
    // 自動創建get set 方法
    public final ObservableField<String> name = new ObservableField<>();
    // 自動創建get set 方法
    public final ObservableInt age = new ObservableInt();
    // 自動創建get set 方法
    public final ObservableInt sex = new ObservableInt();
    public Boy() {

    }
    public Boy(String name, int age, int sex) {
        this.name.set(name);
        this.age.set(age);
        this.sex.set(sex);
    }
}

LoginBean.class


import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;

import com.hb.mvvm.BR;

/** 單向綁定數據並監聽set方法
 * Created by Administrator
 * on 2020/3/26
 */
public class LoginBean extends BaseObservable {
    private String userName;
    private String password;
    public LoginBean() {
    }
    public LoginBean(String userName, String password) {
        this.userName = userName;
        this.password = password;
    }
    //userName@Bindable標誌,然後會生成BR.name
    @Bindable
    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
        //只刷新userName字段
        notifyPropertyChanged(BR.userName);
    }

    @Bindable
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
        //刷新password所有字段
        notifyPropertyChanged(BR.password);
    }
}

輸出結果:
在這裏插入圖片描述

4、在RecyclerView.Adapter的應用

獲取根節點使用iewDataBinding.getRoot() 方法,載入佈局使用DataBindingUtil.inflate。同時對應的xml都需要使用data binding layout 佈局。

舉個例子:

User.class

package com.hb.mvvm.entity;

import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;

public class User extends BaseObservable {

    private String name;
    private int age;
    private int sex;

    @Bindable
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        //只刷name字段
        notifyPropertyChanged(com.hb.mvvm.BR.name);
    }

    @Bindable
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
        //只刷name字段
        notifyPropertyChanged(com.hb.mvvm.BR.age);
    }

    @Bindable
    public int getSex() {
        return sex;
    }

    public void setSex(int sex) {
        this.sex = sex;
        //只刷name字段
        notifyPropertyChanged(com.hb.mvvm.BR.sex);
    }

    public User(String name, int age, int sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
}

item_list.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <import
            alias="User"
            type="com.hb.mvvm.entity.User" />

        <variable
            name="user"
            type="User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="10dp">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="@{user.name}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="10dp"
            android:text="@{String.valueOf(user.age)}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="10dp"
            android:text="@{String.valueOf(user.sex==1?'男':'女')}" />
    </LinearLayout>
</layout>

ListAdapter.class

import android.view.LayoutInflater;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
import androidx.databinding.ViewDataBinding;
import androidx.recyclerview.widget.RecyclerView;

import com.hb.mvvm.R;
import com.hb.mvvm.databinding.ItemListBinding;
import com.hb.mvvm.entity.User;

import java.util.List;

/**
 * Created by Administrator
 * on 2020/3/25
 */
public class ListAdapter extends RecyclerView.Adapter<ListAdapter.MyViewHolder> {
    private List<User> mList;

    public ListAdapter(List<User> mList) {
        this.mList = mList;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        ItemListBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_list, parent, false);
        return new MyViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        ItemListBinding binding = (ItemListBinding) holder.viewDataBinding;
        binding.setUser(mList.get(position));
    }

    @Override
    public int getItemCount() {
        return null != mList ? mList.size() : 0;
    }

    public class MyViewHolder extends RecyclerView.ViewHolder {
        ViewDataBinding viewDataBinding;

        public MyViewHolder(ViewDataBinding viewDataBinding) {
            super(viewDataBinding.getRoot());
            this.viewDataBinding = viewDataBinding;
        }
    }
}

5、總結

很顯然onCreate方法中 setContentView(R.layout.activity_normal);這個語句沒有了;
不再需要findViewById()方法了;
在data binding layout中聲明變量使用 < variable > 標籤,可以聲明各種類型,包括實體變量;
在data binding layout中使用變量直接通過 @{變量名} 來使用,還可以進行拆箱、類型轉換,三元運算等等操作;
在data binding layout中雙向綁定變量通過 @={變量名} 來使用,

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章