Android開發 之 DataBinding 實戰全解

修改對應模塊(Module)的 build.grade:

android {
    ....
    dataBinding {
        enabled = true
    }
}

佈局文件

使用DataBinding之後,xml的佈局文件就不再用於單純地展示UI元素,還需要定義UI元素用到的變量。所以,它的根節點不再是一個 ViewGroup,而是變成了layout,並且新增了一個節點data,用來定義綁定的數據。

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
    <!--添加要綁定的數據-->
    </data>
    
    <!--下面的佈局文件就是原先的根節點(Root Element)-->
    <LinearLayout>
    ....
    </LinearLayout>
</layout>

data節點的作用就像一個橋樑,Activity中將data需要數據傳入,XML定義data數據綁定的位置,搭建了 View 和 Model 之間的通路,也就是實現MVVM的ViewModel

 

下面說明下如何在XML定義data節點:我們先在 xml 佈局文件的data節點中聲明variable節點,這個節點將會定義變量的名稱、類型,爲UI元素提供數據(例如將String綁定到TextView的android:text屬性上)。

 <data>
        <!--java.lang.*包是自動導入的不需要你再次的導入-->
        <!--當出現了import相同的類名的時候需要爲其指定外號alias加以區分-->
        <import type="com.example.administrator.databindingdemo.Student"/>
        <import type="com.example.administrator.databindingdemo.MainActivity.Presenter"/>
        <variable
            name="student"
            type="Student"/>
        <variable
            name="presenter"
            type="Presenter"/>
        <variable
            name="visibility"
            type="Boolean"/>
</data>

 

其中Presenter類時用來綁定方法的類,我們稍後再說;Student類是我們定義的一個類,用來呈現數據,其定義如下:

public class Student extends BaseObservable{
    private String firstName;
    private String lastName;

    public void setFirstName(String firstName) {
        this.firstName = firstName;
        //提示該屬性刷新了
        notifyPropertyChanged(BR.firstName);
        //提示所有的屬性都刷新
        //notifyAll();
    }

    //註解來提示Binding生成這個字段
    @Bindable
    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
        notifyPropertyChanged(BR.lastName);
    }

    @Bindable
    public String getFirstName() {
        return firstName;
    }

    public Student(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return "Student{" +
                       "firstName='" + firstName + '\'' +
                       ", lastName='" + lastName + '}';
    }
}

Studnet類繼承了BaseObservable,這個是數據更新時所需要的,我們稍後在再來討論。

當我們設置DataBinding生效後,如果一個xml文件的被如上這般設置了Data節點,編譯器就會自動生成一個繼承自 ViewDataBinding 的類(build 目錄下)。其命名規則也是固定的:如果 xml 的文件名叫 activity_main.xml,那麼生成的類就是 ActivityMainBinding。

 

 

綁定數據變量

下面需要修改MainActivity的onCreate方法,用 DatabindingUtil.setContentView()來替換掉setContentView(),然後創建一個Student對象,通過mBinding.setStudent(mStudent)與 variable 進行綁定。

Student mStudent = new Student("guo","chengqian");
private ActivityMainBinding mBinding;
Boolean visiblity = true;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
        
    //綁定變量
    mBinding.setStudent(mStudent);
    mBinding.setPresenter(new Presenter());
    mBinding.setVisibility(visiblity);
}

ActivityMainBinding類中所有的set方法是根據variable名稱生成的。我們在XML文件中定義了三個變量,所以會生成三個set方法。

 

在XML中使用變量

當數據傳入ViewDataBinding與Variable綁定之後,xml的UI元素就可以直接使用了。

<TextView
            android:id="@+id/textView1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{student.firstName}"
            />
            
<TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:visibility="@{visibility?View.VISIBLE:View.GONE}"
            android:text="@{student.lastName}"
/>

 

事件方法綁定

僅僅是在UI上綁定數據還遠遠不夠,UI上的響應事件也需要被處理,因此需要要把響應事件的方法綁定到View上。

事件方法的綁定有兩種方法:第一種是方法引用,這種方法要求函數的參數個數,類型以及順序都會原生的事件方法相同;第二種方法是使用lambda表達式,這種方法更爲靈活,可以自定義參數。

我們來看下例子:
這是一個方法類的實現,之前在xml的data節點中已經聲明瞭變量,在Activity中也進行了綁定。

//方法類
public class Presenter{
    //方法引用 參數要求符合接口定義
    public void onTextChanged(CharSequence s, int start, int before, int count){
        mStudent.setFirstName(s.toString());
        //如果已經爲該類繼承了BaseObservable 就不需要再賦值了;
        //mBinding.setStudent(mStudent);
    }
    
    //方法引用,參數要求符合接口定義;
    public void onClick(View view){
        Toast.makeText(MainActivity.this,"onClick!",Toast.LENGTH_SHORT).show();
    }
    
    //自定義方法
    public void onClickListenerBinding(Student student){
        Toast.makeText(MainActivity.this, student.getLastName(),Toast.LENGTH_SHORT).show();
    }
}

 

首先在EidtText控件中使用,將onTextChanged方法綁定。

<EditText
    android:id="@+id/first_name_ed_txt"
    android:onTextChanged="@{presenter.onTextChanged}"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:hint="First Name"/>

這樣一旦輸入的內容改變,變量student中對應的firstName屬性也會跟着改變。

同樣,我們將onClick方法綁定到一個TextView上

<TextView
    android:id="@+id/textView1"
    android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{student.firstName}"
            android:onClick="@{presenter.onClick}"
            />

 

但是這種方法不夠靈活,綁定的方法必須和View所要求的接口參數一致。爲了更爲靈活的綁定方法,還可以使用Lambda表達式。

   <TextView
        android:id="@+id/textView2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="@{visibility?View.VISIBLE:View.GONE}"
        android:text="@{student.lastName}"
        android:onClick="@{()-> presenter.onClickListenerBinding(student)}"/>

lambda表達式實際上也是創建了匿名函數,然後再這個匿名函數中調用了Presenter中的方法,只不過省去了匿名函數的申明,只保留最爲核心的邏輯部分。

此外,lambd表達式支持傳入Context參數,比如Presenter中定義這樣的函數

public void onStudentLongClick(Student student, Context context){
    Toast.makeText(LambdaActivity.this, "LongClick: "+student.toString(), Toast.LENGTH_SHORT).show();
}

Button中可以這樣調用,Context參數會被自動生成:

<Button
    ......
    android:longClickable="true"
    android:onClick="@{() -> presenter.onStudentLongClick(student,context)}"
    ......
            />

 

DataBinding所支持的表達式

爲了擴展更爲強大的功能,DataBinding在使用變量時支持一下表達式:

  • 運算符:Mathematical + - / * %
  • 字符串連接:String concatenation +
  • 邏輯運算:Logical && ||
  • 位操作: Binary & | ^
  • Unary + - ! ~
  • 移位操作:Shift >> >>> <<
  • 比較操作:Comparison == > < >= <=
  • 類型判斷:instanceof
  • Grouping ()
  • Literals - character, String, numeric, null
  • 類型轉化:Cast
  • Method calls
  • Field access
  • Array access []
  • Ternary operator ?:

但不支持的表達式:

  • this
  • super
  • new
  • Explicit generic invocation

Null Coalescing 運算符

DataBinding對於引用變量是否爲空,也有特殊的支持,使用符號??

android:text="@{student.firstName ??student.lastName}"

就等價於

android:text="@{student.firstName != null ? student.firstName : student.lastName}"

 

 在include文件裏使用BataBinding

爲了讓Xml佈局文件更爲簡潔易懂,對於重複使用的佈局可以單獨建立佈局文件,然後使用include導入。DataBinding也考慮到了這種使用情況。可以使用bind:variable_name導入數據變量。

<!--當你使用include的時候,你可以使用命名空間和屬性中的變量名將數據傳送到另一個佈局中去-->  
<include  
    layout="@layout/include_show_name"  
    bind:presenter="@{presenter}"
    bind:student="@{student}"
    bind:visibility="@{visibility}" /> 

需要注意的是:

  1. 一定要使用app命名空間xmlns:app="http://schemas.android.com/apk/res-auto"
  2. 必須在ViewGroup的根佈局中導入佈局文件,如果在非根節點的ViewGroup中使用include會導致錯誤;

 

 在SubView中的使用

在SubView的使用方式和在Include中使用是類似的。

<ViewStub
    bind:visibility="@{visibility}"
    android:id="@+id/view_stub"
    android:layout_width="368dp"
    android:layout_height="wrap_content"
    android:layout="@layout/viewstub"
/>

然後在Activity中通過DataBindingUtil獲得SubView對象,然後填充View;

//viewstub inflate;
View inflate = mBinding.viewStub.getViewStub().inflate();

 

雙向綁定

上面提的內容都是單向綁定,也就是把後臺的數據綁定到前臺顯示出來。DataBinding如果只有如此功能,也不足爲奇,最重要的功能點還是還在於雙向綁定,前臺顯示的數據改變也會自動影響後臺的數據。

首先我們新建一個數據對象類

public class FormModel extends BaseObservable {
    private String name;
    private String password;
    
    @Bindable //該註解表示數據綁定
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        //通知name屬性發生改變
        notifyPropertyChanged(BR.name);
    }
    
    //註解標誌該屬性是綁定的
    @Bindable
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
        //通知有屬性發生改變
        notifyPropertyChanged(BR.password);
    }

    public FormModel(String name, String password) {
        this.name = name;
        this.password = password;

    }
}

該類繼承了BaseObservable,在getter方法之前使用@Bindable,標記該屬性就是需要綁定的,會在BR類中生成對應的ID;在setter方法中notifyPropertyChanged(BR.ID),通知對應ID的屬性發生了改變。

然後在XML中使用“@={variable}”表示數據綁定,如下。

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

    <data>
        <variable
            name="model"
            type="com.example.administrator.databindingdemo.FormModel"/>
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="com.example.administrator.databindingdemo.TowWayBindingActivity">
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Name"
            android:text="@={model.name}"/>
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="pwd"
            android:text="@={model.password}"/>
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{model.name}"/>
    </LinearLayout>
</layout>

最後在Activity中傳入變量。

public class TowWayBindingActivity extends AppCompatActivity {

    ActivityTowWayBindingBinding mBinding;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.
            setContentView(this,R.layout.activity_tow_way_binding);
        FormModel model = new FormModel("user", "12345");
        mBinding.setModel(model);
        
        //屬性改變監聽器
        model.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
            @Override
            public void onPropertyChanged(Observable sender, int propertyId) {
                if(BR.name == propertyId)
                    Toast.makeText(TowWayBindingActivity.this, "姓名改變",
                        Toast.LENGTH_SHORT).show();
            }
        });
    }
}

這是運行程序就會發現,在前端輸入姓名時,其他綁定name屬性的地方也會改變,說明後臺數據也響應改變了。

 

表達式鏈與隱式更新

當多個View的屬性相互關聯式時,可以通過表達式鏈實現隱式更新,比如:

<CheckBox android:id="@+id/check_box"/>
<ImageView android:visibility="check_box.checked?View.VISIBILE:View.GONE"/>

 

添加動畫

數據綁定必然涉及到UI的變化,默認情況下往往都是很生硬的UI變化,沒有任何過度。DataBinding也提供辦法來插入動畫。

實現方法也很簡單,就是設置一個重新綁定時的回調函數addOnRebindCallback,在函數中設置當前View的UI變化時開啓一定延時。

public class AnimationActivity extends AppCompatActivity {
    ActivityAnimationBinding mBinding;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this,R.layout.activity_animation);
        mBinding.setPresenter(new AnimatorPresenter());
        //設置重綁定回調函數
        mBinding.addOnRebindCallback(new OnRebindCallback() {
            @Override
            public boolean onPreBind(ViewDataBinding binding) {
                ViewGroup viewGroup = (ViewGroup) binding.getRoot();
                //開啓延時位移動畫          
                TransitionManager.beginDelayedTransition(viewGroup);
                return true;
            }
        });
    }
    public class AnimatorPresenter{
        public void onCheckedChanged(boolean isChecked){
            mBinding.setShowImage(isChecked);
        }
    }
}

在XML中正常使用DataBinding

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

    <data>
        <import type="android.view.View"/>
        <variable
            name="presenter"
            type="com.example.administrator.databindingdemo.AnimationActivity.AnimatorPresenter"/>
        <variable
            name="showImage"
            type="Boolean"/>
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="com.example.administrator.databindingdemo.AnimationActivity">
        <ImageView
            android:contentDescription="Animation Show"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:src="@drawable/default_avatar"
            android:visibility="@{showImage?View.VISIBLE:View.GONE}"/>

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="showImage"
            android:onCheckedChanged = "@{(v,isChecked)->presenter.onCheckedChanged(isChecked)}"/>
    </LinearLayout>
</layout>

這樣圖片在顯示或者出現的過程中都會添加一個CheckBox位移勻速漸變的動畫效果。

需要說明的是,雖然佈局文件中CheckBox原本並沒有onCheckedChanged屬性,但是CheckBox類有對應的屬性,並且設有setOnCheckedChanged方法來設置該屬性,所以也是可以進行綁定的。

按照慣例,給出Demo源碼:
https://github.com/sunrongxin7666
歡迎大家指正批評

需要說明的是,由於篇幅的限制,還有動態綁定和在RecyclerView中使用沒有介紹,如果感興趣請讀者參考一下兩篇文章,基於以上的內容,相信讀者不會很難理解。
Android DataBinding完全解析
精通 Android Data Binding

 

 

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