Android DataBinding完全解析

前言

       2015年的Google IO大會上,Android 團隊發佈了一個數據綁定框架(Data Binding Library),官方原生支持 MVVM 模型。以後可以直接在 layout 佈局 xml 文件中綁定數據了,無需再 findViewById然後手工設置數據了。其語法和使用方式和 JSP 中的 EL 表達式非常類似。


配置:

android {
    ....
    dataBinding {
        enabled = true
    }
}
首先我們肯定是先要和佈局文件進行綁定

        ActivityLayoutDetailBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_layout_detail);

替換掉setContentView即可,ActivityLayoutDetailBinding這個類是自動生成的和你的佈局文件名字一樣,如果你想要去改變名字的話

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:bind="http://schemas.android.com/apk/res-auto">

    <!--這裏你也可以爲Binding類進行命名,有三種形式
        1、Custom:會在databinding包下
        2、.Custom:會在當前的包名下創建
        3、com.andly.Custom:會在指定的包名下進行創建-->
    <data class="Custom">
接下來就是在佈局文件裏面引用代碼中的對象

Part 1、先是聲明類和對象

  <data>
        <!--java.lang.*包是自動導入的不需要你再次的導入-->
        <import type="android.view.View" />
        <!--當出現了import相同的類名的時候需要爲其指定外號alias加以區分-->
        <import
            alias="CustomView"
            type="com.andly.administrator.andlydatabinding.myview.View" />

        <import type="com.andly.administrator.andlydatabinding.entry.User" />
        <import type="com.andly.administrator.andlydatabinding.event_handing.EventHandler" />
        <import type="java.util.List" />
        <import type="java.util.Map" />
        <import type="android.graphics.drawable.Drawable" />

        <variable
            name="image"
            type="Drawable" />

        <variable
            name="note"
            type="String" />

        <variable
            name="boo"
            type="boolean" />

        <variable
            name="view"
            type="View" />
        <!--這裏不能用<>來表示泛型,要使用< >-->
        <variable
            name="list"
            type="List<User>" />

        <variable
            name="strList"
            type="List<String>" />

        <variable
            name="map"
            type="Map<String,String>" />
Part 2、引用對象中的方法和值

      1、簡易的引用

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{note}"/>
       2、集合引用

        <!--使用Map這種Key Value的格式數據時
                格式1:"@{map['key']}"
                格式2: '@{map["key"]}'
                格式3: "@{map[`key`]}"  反引號
        -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{map[`one`]}" />
這時我們在代碼中調用binding的set方法便可以爲其設置內容了

        List<User> users = new ArrayList<>();
        User u = new User();
        u.setName("List User Name Data");
        users.add(u);
        binding.setList(users);

        Map map = new HashMap();
        map.put("one", "Map One Data");
        binding.setMap(map);
在引用的同時也可以加上簡單的邏輯運算

       <!--數據綁定將自動檢測null異常,如果你的表達式爲null,它將會給它賦值爲(null)
        如果爲int類型則默認爲0-->
        <!--之前都是寫三元運算符的形式,當然在數據綁定中也能夠使用,但更推薦下面那種-->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{boo?note:null}" />
        <!--?? :null合併運算符,當左邊爲null會顯示右邊-->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{note??null}"
            android:textColor="#00FF00"
            android:textSize="18sp" />
        <!--引用資源文件-->
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingLeft="@{boo?@dimen/large_padding:@dimen/small_padding}"
            android:src="@{image}" />
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text='@{String.valueOf(map[`one`])}'/>
等等,詳情請看源碼

然而上面引用的數值並不能隨着用戶的操作動態的改變,如果你想動態的改變需要使用Observable前綴的類,如:

        //使用綁定的集合
        /**
         * 如果要使用ObservableArrayMap類需要在layout裏面導入 <import type="android.databinding.ObservableMap" />
         */
        user = new ObservableArrayMap<>();
        user.put(Fields.FIRST_NAME, "Google");
        user.put(Fields.LAST_NAME, "Inc.");
        user.put(Fields.AGE, 17);
        binding.setObserableMap(user);
對象的話需要繼承BaseObservable並設置你索要觀察的字段
public class DataUser extends BaseObservable {
    //繼承BaseObservable類,想對誰進行監聽則需要在get方法上面添加@Bindable註解,在set方法裏面使用notifyPropertyChanged
    //注意到的是在這個方法裏面要傳入BR.name參數,這是Binding類爲該字段生成唯一變量進行綁定
    private String name;
    private int age;
    private String info;
    @Bindable
    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    public String getInfo() {
        return info;
    }
    public void setInfo(String info) {
        this.info = info;
    }
}

當你去執行小的工作的時候,想去節省時間或者減少配置可以使用ObservableField或者其兄弟

    //它們內部包含Observable對象,使用時要去創建public final類型的
    //爲其賦值和取值操作:user.firstName.set("Google");  int age = user.age.get();
    public final ObservableField<String> name = new ObservableField<>();
    public final ObservableInt age = new ObservableInt();
    public final ObservableField<String> info = new ObservableField<>();

Part 3、添加事件處理方法

        <!--
            綁定事件的格式有兩種:  
            1、方法引用:直接用handle.Click或者handle::Click  推薦後者
        -->
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{user.name}"
            android:onClick="@{handle::Click}"/>
       <!--
            2、監聽綁定:使用()組,括號裏面所填的是你爲參數起的名字,這樣你就可以在後面的括號進行引用
               如果你監聽的事件需要返回值,那麼你的方法也要返回一個相同類型
        -->
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{()->handle.eventHandler(user)}"
            android:text="傳入佈局文件中的數據" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{(thisView)->handle.eventHandlerView(thisView,user)}"
            android:text="傳入此View" />
        <!--如果你需要爲一個點擊事件設置一個斷言,那麼使用void作爲一個標誌,表示什麼也不做-->
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{(v)->handle.isVisible(v)?handle.doSomething():void}"
            android:text="判斷是否爲visible" />

        <!--對於一些控件有自己專門的單擊事件,需要創建下面的屬性進行避免
           SearchView    android:onSearchClick
           ZoomControls  android:onZoomIn
           ZoomControls  android:onZoomOut-->
        <SearchView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{(v)->handle.searchClick(v)}"
            android:onSearchClick="@{(v)->handle.onSearchClick(v)}">

        </SearchView>
實現監聽方法,保證參數個數、類型、返回值都要保證和你使用set時監聽一樣,不然就出報錯。

    public void checkChanged(View view, boolean isCheck) {
        System.out.println("checkChanged:" + view + "    " + isCheck);
    }

    public boolean longClick(View view) {
        System.out.println("longClick:" + view);
        return true;
    }
Part 4、在佈局中使用include

如果你需要用到從xml傳過來的數據需要去使用bind:user屬性,這裏的user是你定義的實體類名

        <!--當你使用include的時候,你可以使用命名空間和屬性中的變量名
        來將數據傳送到另一個佈局中去,值得注意的是當include的父節點爲merge時將不支持-->
        <include
            layout="@layout/detail_include"
            bind:user="@{user}" />

然後只需要在include佈局裏面聲明之後便可以直接使用了。

Part 5、在佈局中使用ViewStub

        /**
         * 爲ViewStub設置監聽,當顯示的時候爲它綁定數據,因爲當不顯示的ViewStub會在視圖中消失
         */
        vs = (ViewStub) findViewById(R.id.viewstub);
        vs.setOnInflateListener(new ViewStub.OnInflateListener() {
            @Override
            public void onInflate(ViewStub stub, View inflated) {
                ViewstubBinding viewstubBinding = ViewstubBinding.bind(inflated);
                Info info = new Info();
                info.setInfo("Andly Info");
                viewstubBinding.setInfo(info);
                Drawable d = getResources().getDrawable(R.mipmap.ic_launcher);
                viewstubBinding.setDrawable(d);
            }
        });

    }

    public void toggleViewStub(View view) {
        vs.inflate();
    }

Part 6、在佈局中使用RecycleView控件

          1、添加RecycleView控件

        <!--
            這裏使用到了自定義屬性,因爲RecycleView裏面有setAdapter方法,所以這裏可以直接用app:adapter
        -->
        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:adapter="@{adapter}" />
           2、爲RecycleView定義適配器

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ViewDataBinding viewDataBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), layoutId, parent, false);
        return new ViewHolder(viewDataBinding);
    }
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.binding.setVariable(variable, list.get(position));
        holder.binding.executePendingBindings();
        //當然這裏你也可以爲其設置點擊如:
        //holder.binding.getRoot.setOnclickListener()
    }
    @Override
    public int getItemCount() {
        return list.size();
    }

    class ViewHolder extends RecyclerView.ViewHolder {
        ViewDataBinding binding;

        public ViewHolder(ViewDataBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }
    }
               3、爲RecycleView設置Adapter

        //這裏注意的是一定要是BR.dataInfo不能是其它的常數
        MyAdapter adapter = new MyAdapter(list, R.layout.rv_item, BR.dataInfo);
        binding.setAdapter(adapter);
        binding.rv.setLayoutManager(new LinearLayoutManager(this));
這樣就大功告成,然而在很多情況我們都需要去對每個Item進行處理,如顯示網絡圖片等等,這裏我們就需要使用數據綁定自定義屬性的功能,看代碼

        <ImageView
            android:layout_width="150dp"
            android:layout_height="90dp"
            app:imageError="@{@drawable/android}"
            app:imagePath="@{dataInfo.imageUrl}" />
    //當你在一個方法只需要一個參數的時候可以使用@BindingAdapter("imageUrlStr"),加上之後就可以在佈局文件中直接使用imageUrlStr
    //運行之後就會調用loadImage方法
    @BindingAdapter("imageUrlStr")
    public static void loadImage(ImageView iv, String url) {
        Glide.with(iv.getContext()).load(url).into(iv);//這裏使用Glide庫
    }

    //上面是爲loadImage傳入一個參數,當傳入兩個或多個參數的時候應使用@BindingAdapter({"imagePath", "imageError"})
    //這個的ImageView自定義了兩個屬性一個是imagePath傳入的是url,一個是imageError爲Drawable
    @BindingAdapter({"imagePath", "imageError"})
    public static void downloadImage(ImageView iv, String url, Drawable error) {
        Glide.with(iv.getContext()).load(url).error(error).into(iv);
    }

上面的方法使用的是靜態方法,如果你不想使用靜態方法你需要重寫一個數據綁定組件類去實現DataBindingComponent

public class MyComponent implements android.databinding.DataBindingComponent {
    private Utils utils;
    @Override
    public Utils getUtils() {
        if (utils == null) {
            utils = new Utils();
        }
        return utils;
    }
}
然後你需要在Activity爲其進行設置

        //第一種方式
        DataBindingUtil.setDefaultComponent(new MyComponent());
        //第二種方式
        ActivityMyListViewBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_my_list_view,new MyComponent());
        //第三種方式
        DataBindingUtil.bind(root,new MyComponent());

最後有個不起眼的小功能,就是當使用數據綁定的時候在預覽界面不能看到顯示的內容,這時你可以爲你的控件設置默認顯示內容

android:text="@{placeName,default=PLACEHOLDER}"

注意:

不允許使用混合類型

        <!--值得注意的是
            android:background="@{boo?@color/red:@drawable/background}"
            這麼寫將會發生錯誤,因爲在BindingConversion默認實現爲:
            @BindingConversion
            public static ColorDrawable convertColorToDrawable(int color) {
                    return new ColorDrawable(color);
                }
        -->
        <ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_marginTop="20dp"
            android:background="@{boo?@color/red:@color/green}" />

源碼下載:http://download.csdn.net/detail/weiwozhiyi/9644657

發佈了59 篇原創文章 · 獲贊 10 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章