文章目錄
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格式所以要轉義。用< 代表<;用>代表>(這些轉義符,同樣支持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中不能直接使用<和>符號,所以:<代表<,>代表>-->
<variable
name="map"
type="Map<String,Object>" />
<!-- List類型用法,使用帶觀察者的列表對象ObservableList 導包-->
<import
alias="List"
type="androidx.databinding.ObservableList" />
<!-- List類型用法 聲明變量,因在xml中不能直接使用<和>符號,所以:<代表<,>代表>-->
<variable
name="list"
type="List<String>" />
</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中雙向綁定變量通過 @={變量名} 來使用,