MVVM之DataBinding學(xué)習(xí)筆記

[TOC]

前言

...想了半分鐘嗓违,好像并沒有啥需要特別聲明的,能翻到這篇文章的人知残,相信也早已了解了DataBinding是個(gè)什么東西靠瞎,所以還是簡單粗暴點(diǎn)兒比庄,就當(dāng)給自己留個(gè)筆記...開擼吧求妹!

配置

  • 確保sdk的support包更新到了最新版
  • 在對應(yīng)module的build.gradle文件中進(jìn)行如下配置(需AS版本1.5以上)
android {
    ...
    dataBinding {
        enabled = true
    }
}

基本使用

數(shù)據(jù)綁定

Activity

先上layout代碼

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

    <!--如果本頁不需要綁定數(shù)據(jù),data標(biāo)簽可以省略-->
    <data class="DataBingMain">
        <import type="lxf.widget.util.AppUtils"/>
        <variable
            name="user"
            type="lxf.androiddemos.model.UserEntity"/>
    </data>

    <LinearLayout
        xmlns:tools="http://schemas.android.com/tools"
        tools:context="lxf.androiddemos.ui.DatabindingActivity"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="16dp"
        android:background="@mipmap/bg_robot"
        android:orientation="vertical"
        >
        <TextView
            style="@style/text_base"
            android:text="@{`name:`+user.name}"/>
        <TextView
            style="@style/text_base"
            android:text="@{user.sex}"/>
        <TextView
            style="@style/text_base"
            android:text="@{String.valueOf(user.age)}"/>
        <TextView
            style="@style/text_base"
            android:text="@{user.initType(user.type)}"/>
    </LinearLayout>
</layout>

  1. layout的編寫方式改變:新增layout和data標(biāo)簽佳窑,layout為根布局制恍,包含data和ui布局兩部分。data即要綁定的數(shù)據(jù)model神凑,ui布局即我們以前寫法中的根布局净神。
  2. data標(biāo)簽:
    • 使用DataBinding編寫布局,系統(tǒng)會自動生成一個(gè)繼承ViewDataBinding類溉委,而class屬性可以指定這個(gè)類的名字鹃唯,如果不指定,則會根據(jù)xml的名字自動生成瓣喊。
    • variable可以設(shè)置多個(gè)坡慌。
    • 通俗的講,name即變量名藻三,可以在下面直接引用洪橘,同時(shí)會在自動生成的ViewDataBinding類中自動生成setXXX和getXXX方法,用來綁定數(shù)據(jù)棵帽。
    • type即我們的數(shù)據(jù)model熄求。
    • 支持import,導(dǎo)入后可以在@{}中直接使用逗概,方式同java弟晚。
  3. @{}語法中支持大部分的java操作,當(dāng)然最好不要寫太復(fù)雜的語句逾苫,如果有這個(gè)需求指巡,可以在java類中寫一個(gè)方法,然后在此調(diào)用:
    • 運(yùn)算符: + - / * % () && || & | ^ >> >>> << == > < >= <=
    • 字符串拼接 + (注意字符串要用``括起來隶垮,esc下面那個(gè)鍵)
    • instanceof
    • 方法調(diào)用
    • res資源訪問
    • 數(shù)組訪問 []
    • 三目運(yùn)算 表達(dá)式1 ? 表達(dá)式2 : 表達(dá)式3
    • 聚合判斷 表達(dá)式1 ?? 表達(dá)式2 (表達(dá)式1為null藻雪,則返回表達(dá)式2)
    • 等等...

activity代碼
  通過DataBindingUtil.setContentView方法代替原來的setContentView。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //1.獲取ViewDataBinding對象
        DataBingMain dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_databinding);

        //2.獲取數(shù)據(jù)
        UserEntity user = new UserEntity();
        user.setName("lxf");
        user.setSex("man");
        user.setAge(25);
        user.setType(1);

        //3.綁定數(shù)據(jù)
        dataBinding.setUser(user);
        //dataBinding.setVariable(BR.user,user);
    }

model代碼

package lxf.androiddemos.model;

public class UserEntity {
    private String name;
    private String sex;
    private int age;
    private int type;

    public String initType(int type){
        String result;
        switch (type){
            case 1:
                result = "程序猿";
                break;
            case 2:
                result = "程序猿的天敵";
                break;
            default:
                result = "無業(yè)游民";
                break;
        }
        return result;
    }

    //setter   getter方法略
}

activity和model代碼很簡單狸吞,就不需要解釋了勉耀。

Fragment

看到這里應(yīng)該有個(gè)疑問:fragment中沒有setContentView方法指煎,該怎么辦?
  所幸DataBinding庫還提供了另外一個(gè)初始化布局的方法:DataBindingUtil.inflate()便斥。

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

xml布局的寫法同activity至壤。

列表綁定

在此已RecyclerView為例。

單布局

先看item布局:

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

    <data>
        <variable
            name="item"
            type="lxf.androiddemos.model.MainRecyclerItem"/>
    </data>

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:layout_marginTop="16dp"
        app:cardElevation="5dp"
        android:onClick="@{item.onItemClick}"
        >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:textSize="18sp"
            android:textColor="@color/text_green_bg"
            android:text="@{item.content}"/>
    </android.support.v7.widget.CardView>
</layout>

這里值得我們關(guān)注的有兩個(gè)地方枢纠,一個(gè)就是TextView上的數(shù)據(jù)綁定像街,一個(gè)是父布局上的onClick屬性,可以通過這種方式來設(shè)置item點(diǎn)擊事件晋渺,說白了其實(shí)就是調(diào)用MainRecyclerItem中的一個(gè)方法镰绎,我們可以通過getter方法很方便的知道當(dāng)前item的具體數(shù)據(jù),具體實(shí)現(xiàn)請往下看木西。
  model實(shí)體類

public class MainRecyclerItem {
    public static final String[] items = new String[]{"ViewDragHelper", "自定義Behavior", "二維碼", "DataBinding"};

    private String content;

    public void onItemClick(View view) {
        Intent intent = null;
        switch (getContent()) {
            case "ViewDragHelper":
                intent = new Intent(view.getContext(), ViewDragHelperActivity.class);
                break;
            case "自定義Behavior":
                intent = new Intent(view.getContext(), BehaviorActivity.class);
                break;
            case "二維碼":
                intent = new Intent(view.getContext(), ZxingActivity.class);
                break;
            case "DataBinding":
                intent = new Intent(view.getContext(), DatabindingActivity.class);
                break;
        }
        if (intent != null)
            view.getContext().startActivity(intent);
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

adapter

class RecyclerBindingViewHolder extends RecyclerView.ViewHolder {
    ViewDataBinding binding;

    private RecyclerBindingViewHolder(ViewDataBinding binding) {
        super(binding.getRoot());
        this.binding = binding;
    }

    static RecyclerBindingViewHolder createViewHolder(ViewGroup parent, int layoutId) {
        ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),layoutId,parent,false);
        return new RecyclerBindingViewHolder(binding);
    }
    
    
public abstract class BaseRecyclerBindingAdapter extends RecyclerView.Adapter<RecyclerBindingViewHolder> implements ChangeDataLinstener{
    protected List<Object> mData;

    public BaseRecyclerBindingAdapter(List<Object> list) {
        mData = (list != null) ? list : new ArrayList<>();
    }

    @Override
    public RecyclerBindingViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return RecyclerBindingViewHolder.createViewHolder(parent,getItemLayoutId(viewType));
    }

    @Override
    public void onBindViewHolder(RecyclerBindingViewHolder holder, int position) {
        //綁定數(shù)據(jù)
        holder.binding.setVariable(getItemVariableId(),mData.get(position));
        holder.binding.executePendingBindings();
    }

    @Override
    public int getItemCount() {
        return mData.size();
    }

    public void setmData(List<Object> mData) {
        this.mData = mData;
        notifyDataSetChanged();
    }
    
    //item布局id
    public abstract int getItemLayoutId(int viewType);
    
    //對應(yīng)item布局里面data標(biāo)簽中的name畴栖,會自動生成一個(gè)BR.xxx屬性,類似于R文件
    public abstract int getItemVariableId();

}

我們這里把a(bǔ)dapter寫成了一個(gè)抽象類八千,如果沒有什么很奇葩的要求吗讶,可以算一個(gè)通用adapter了,可以看到它沒有任何的findviewbyid和set數(shù)據(jù)恋捆,一切都在布局中封裝好了照皆,實(shí)現(xiàn)非常的簡潔。

多布局

如果我們想加一個(gè)頭部文件沸停,可以這樣:

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

    <data>
        <variable
            name="item"
            type="lxf.androiddemos.model.MainRecyclerHeader"/>
    </data>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:gravity="center"
        android:text="@{item.header}"
        android:background="@color/text_orange_bg">

    </TextView>
</layout>

注意和之前的item布局有個(gè)相通之處膜毁,就是data標(biāo)簽的name屬性值是一樣的,然后通過getItemViewType實(shí)現(xiàn)我們的不同布局即可。

datas = new ArrayList<>();
MainRecyclerHeader header = new MainRecyclerHeader();
header.setHeader("我是頭部文件");
datas.add(header);
for (int i = 0; i < MainRecyclerItem.items.length; i++) {
    MainRecyclerItem item = new MainRecyclerItem();
    item.setContent(MainRecyclerItem.items[i]);
    datas.add(item);
}


BaseRecyclerBindingAdapter bindingAdapter = new BaseRecyclerBindingAdapter(datas) {
            @Override
            public int getItemLayoutId(int viewType) {
                return viewType;
            }

            @Override
            public int getItemVariableId() {
                return BR.item;//對應(yīng)item布局里面data標(biāo)簽中的name
            }

            @Override
            public int getItemViewType(int position) {
                if (position == 0)
                    return R.layout.header_recycler_main;
                else
                    return R.layout.item_recycler_main;
            }
        };

事件綁定

事件綁定說白了星立,其實(shí)就是一種特殊的變量綁定爽茴,或者說是一個(gè)方法的調(diào)用。

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

    <!--如果本頁不需要綁定數(shù)據(jù)绰垂,data標(biāo)簽可以省略-->
    <data class="DataBingMain">
        <variable
            name="user"
            type="lxf.androiddemos.model.UserEntity"/>
        <variable
            name="util"
            type="lxf.androiddemos.test.TestUtil"/>
    </data>

    <LinearLayout
        ...
        >
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{util.onBtnClick}"
            />
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onTextChanged="@{util.onTextChanged}"
            />
    </LinearLayout>
</layout>


public class TestUtil {

    public void onBtnClick(View view){
        Toast.makeText(view.getContext(),"onBtnClick",Toast.LENGTH_SHORT).show();
    }
    public void onTextChanged(CharSequence s, int start, int before, int count){
        System.out.println(s);
    }
}

需要注意的是通過onclick這種方式綁定的事件室奏,實(shí)現(xiàn)方法中一定要傳入view參數(shù)(類似于傳統(tǒng)的onClick方法),否則編譯會報(bào)錯(cuò)劲装。
  同樣的官方文檔提到胧沫,你也可以用這種方式來綁定一些比較偏門的監(jiān)聽,比如上面的onTextChanged占业,方法參數(shù)必須與傳統(tǒng)的onTextChanged參數(shù)一模一樣绒怨,否則編譯報(bào)錯(cuò),這種方式可以使你只監(jiān)聽onTextChanged一個(gè)方法谦疾,而非TextWatcher的三個(gè)方法南蹂,另外EditText本身是沒有android:onTextChanged這個(gè)屬性的,具體實(shí)現(xiàn)原理需要先理解一下什么是databinding的自定義屬性念恍,會在后文提到六剥。

進(jìn)階使用

數(shù)據(jù)更新

在很多情況下晚顷,我們需要動態(tài)去設(shè)置相關(guān)數(shù)據(jù),DataBinding為我們提供了兩種方式來實(shí)現(xiàn)它疗疟。
  方法一

  1. 實(shí)體類繼承BaseObservable该默,或者自己實(shí)現(xiàn)Observable
  2. 在需要刷新的屬性的get方法上添加@Bindable注解,此時(shí)會自動生成BR類策彤。(這里有個(gè)坑栓袖,很多時(shí)候BR文件不會自動生成,此時(shí)需要重啟AS...請讓我先默默地日一波dog)
  3. 在相應(yīng)的set方法里調(diào)用notifyPropertyChanged(BR.xxx)進(jìn)行刷新店诗。
package lxf.androiddemos.model;

import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.view.View;

import lxf.androiddemos.BR;

public class UserEntity extends BaseObservable{
    private String name;
    private String sex;
    private int age;
    private int type;

    ...

    public void addAge(View view) {
        setAge(getAge() + 1);
    }

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

    public void setAge(int age) {
        this.age = age;
//        notifyChange();//刷新所有可刷新數(shù)據(jù)
        notifyPropertyChanged(BR.age);
    }
}

方法二

  1. 實(shí)體類繼承BaseObservable裹刮,或者自己實(shí)現(xiàn)Observable
  2. 使用ObservableField<>,泛型可以填入自己需要的類型必搞,注意必須要初始化必指。對于基本數(shù)據(jù)類型也可以直接使用ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble和ObservableParcelable囊咏。
  3. 通過set和get方法為ObservableField設(shè)值和取值
package lxf.androiddemos.model;

import android.databinding.BaseObservable;
import android.databinding.ObservableField;
import android.view.View;

public class UserEntity extends BaseObservable{

    public ObservableField<String> address = new ObservableField<>();

    public void changeAddress(View view){
        address.set("change:" + address.get());
    }
}

自定義屬性綁定適配器和回調(diào)

自動尋找setter

DataBinding在遇到屬性綁定時(shí)恕洲,會自動去尋找該屬性的set方法,找到就會調(diào)用梅割,找不到就報(bào)錯(cuò)霜第。

<ImageView
    android:layout_width="50dp"
    android:layout_height="50dp"
    app:imageResource="@{R.mipmap.ic_launcher}"/>

比如上面這段代碼,我們知道ImageView中是沒有imageResource這個(gè)屬性的户辞,但是有setImageResource(int resId)方法泌类,因此這段代碼是可以正常運(yùn)行的。利用這種特性底燎,可以為一些自定義控件增加setter方法刃榨,使其支持DataBinding。

@BindingMethods

當(dāng)xml屬性名稱和源碼中set方法名稱不一致時(shí)双仍,可以通過這種方式來進(jìn)行綁定枢希。先看一個(gè)官方的實(shí)現(xiàn):

@BindingMethods({
        ...
        @BindingMethod(type = TextView.class, attribute = "android:inputType", method = "setRawInputType"),
        ...
})

這段代碼的意思就是將TextView的android:inputType屬性綁定到setRawInputType方法,其實(shí)也可以通俗的認(rèn)為是為原本的setter方法起了一個(gè)別名朱沃。

@BindingAdapter

很多時(shí)候苞轿,源碼中并沒有提供set方法,比如ImageView逗物,我們希望通過設(shè)置url來達(dá)到加載圖片的目的搬卒,我們可以通過@BindAdapter來實(shí)現(xiàn)。

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

    <!--如果本頁不需要綁定數(shù)據(jù)翎卓,data標(biāo)簽可以省略-->
    <data class="DataBingMain">
        <import type="lxf.androiddemos.R"/>
        <variable
            name="user"
            type="lxf.androiddemos.model.UserEntity"/>
    </data>

    <LinearLayout
        ...
        >
        
        <ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            app:url="@{user.img}"
            app:placeHolder="@{R.mipmap.ic_launcher}"/>
    </LinearLayout>
</layout>


public class UserEntity extends BaseObservable{

    private String img;

    @BindingAdapter(value = {"url","placeHolder"},requireAll = false)
    public static void setImage(ImageView imageView ,String url,int placeHolder){
        Glide.with(imageView.getContext()).load(url).placeholder(placeHolder).into(imageView);
    }
}

這里有幾點(diǎn)需要注意:

  • xml文件中一定不要忘記各種類的import(除java.lang包外均需導(dǎo)入)契邀,否則你一定會碰到databinding程序包不存在這個(gè)錯(cuò)誤。
  • 設(shè)置@BindAdapter注解的方法需要是static的失暴,否則編譯也會報(bào)錯(cuò)坯门。
  • 你可以把這個(gè)方法設(shè)置在一個(gè)專門的工具類中椭迎,不是說必須要在這個(gè)model實(shí)體類里。
  • @BindAdapter包含value和requireAll兩個(gè)屬性田盈,value是一個(gè)String[]畜号,包含你自定義的屬性。requireAll意思是是否需要設(shè)置你在value中聲明的全部屬性允瞧,默認(rèn)為true简软。如果設(shè)定為false,那么沒賦值的自定義屬性會傳默認(rèn)值述暂。

到這里痹升,我們來回頭看一下之前在事件綁定中留下的那個(gè)坑——onTextChanged,其實(shí)這是官方提前為我們封裝好的:

@BindingAdapter(value = {"android:beforeTextChanged", "android:onTextChanged",
            "android:afterTextChanged", "android:textAttrChanged"}, requireAll = false)
    public static void setTextWatcher(TextView view, final BeforeTextChanged before,
            final OnTextChanged on, final AfterTextChanged after,
            final InverseBindingListener textAttrChanged) {
        final TextWatcher newValue;
        if (before == null && after == null && on == null && textAttrChanged == null) {
            newValue = null;
        } else {
            newValue = new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                    if (before != null) {
                        before.beforeTextChanged(s, start, count, after);
                    }
                }

                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                    if (on != null) {
                        on.onTextChanged(s, start, before, count);
                    }
                    if (textAttrChanged != null) {
                        textAttrChanged.onChange();
                    }
                }

                @Override
                public void afterTextChanged(Editable s) {
                    if (after != null) {
                        after.afterTextChanged(s);
                    }
                }
            };
        }
        final TextWatcher oldValue = ListenerUtil.trackListener(view, newValue, R.id.textWatcher);
        if (oldValue != null) {
            view.removeTextChangedListener(oldValue);
        }
        if (newValue != null) {
            view.addTextChangedListener(newValue);
        }
    }

可以看到畦韭,當(dāng)on!=null時(shí)疼蛾,會調(diào)用傳統(tǒng)的onTextChanged方法。

@BindingConversion

方法注釋艺配,當(dāng)自定義的屬性和setter方法中需要的參數(shù)類型不符時(shí)進(jìn)行轉(zhuǎn)換察郁。

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@{`#de325e`}"
            />

上面這種寫法,直接編譯是會報(bào)錯(cuò)的转唉,因?yàn)閟etBackground接收的是一個(gè)Drawable對象皮钠,而我們傳入的是個(gè)string,所以我們此處可以用@BindingConversion來轉(zhuǎn)換一下(PS:我知道傳統(tǒng)寫法是可以直接傳字符串顏色值的赠法,我只是舉個(gè)簡單例子)麦轰。

    @BindingConversion
    public static Drawable colorToDrawable(String color){
        return new ColorDrawable(Color.parseColor(color));
    }

DataBinding在碰到這種參數(shù)類型不對的問題時(shí),會自動去檢索看看有沒有相關(guān)的@BindingConversion方法砖织,如果有的話則會調(diào)用款侵,需要注意,這個(gè)方法也需要是static的侧纯。

接口回調(diào)

model的回調(diào)

當(dāng)屬性值變化時(shí)的回調(diào)新锈。

user.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
            @Override
            public void onPropertyChanged(Observable sender, int propertyId) {
                if (propertyId == BR.age){
                    Toast.makeText(getApplicationContext(),"age刷新了",Toast.LENGTH_SHORT).show();
                }
            }
        });

ViewDatabinding的回調(diào)

當(dāng)ViewDataBinding執(zhí)行executePendingBindings()尺寸必須再次評估時(shí)的回調(diào)∶荆可以設(shè)置一些view的展示動畫等壕鹉。

dataBinding.addOnRebindCallback(new OnRebindCallback() {
            @Override
            public boolean onPreBind(ViewDataBinding binding) {
                return super.onPreBind(binding);
            }

            @Override
            public void onCanceled(ViewDataBinding binding) {
                super.onCanceled(binding);
            }

            @Override
            public void onBound(ViewDataBinding binding) {
                super.onBound(binding);
            }
        });

雙向綁定

基本數(shù)據(jù)

雙向綁定意思不僅數(shù)據(jù)綁定UI,同時(shí)UI更新時(shí)可以刷新數(shù)據(jù)聋涨,語法為@={},舉個(gè)例子:

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onTextChanged="@{util.onTextChanged}"
            android:text="@={user.address}"
            />

這樣在程序運(yùn)行后晾浴,editText會自動顯示user.address的初始值,改變editText牍白,則user.address也會同步改變脊凰,可以想象,如果我們將user.address綁定另一個(gè)TextView,則TextView的內(nèi)容會跟隨editText的變化而變化狸涌。

隱式屬性監(jiān)聽

       <ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:visibility="@{checkbox.checked?View.VISIBLE:View.GONE}"
            app:placeHolder="@{R.mipmap.ic_launcher}"
            app:url="@{user.img}" />
        <CheckBox
            android:id="@+id/checkbox"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

可以看ImageView的visibility屬性切省,通過CheckBox的checked屬性來控制自身的顯示和隱藏,這是官方給出的一種支持帕胆,同時(shí)官方還支持下面這些屬性:

  • AbsListView android:selectedItemPosition
  • CalendarView android:date
  • CompoundButton android:checked
  • DatePicker android:year, android:month, android:day (yes, these are synthetic, but we had a listener, so we thought you’d want to use them)
  • NumberPicker android:value
  • RadioGroup android:checkedButton
  • RatingBar android:rating
  • SeekBar android:progress
  • TabHost android:currentTab (you probably don’t care, but we had the listener)
  • TextView android:text
  • TimePicker android:hour, android:minute (again, synthetic, but we had the listener)

自定義雙向綁定

雙向綁定其實(shí)就是正向綁定+反向綁定朝捆,前面講的全部是正向綁定,截下來我們來看看怎么定義反向綁定懒豹。

綁定方法(@InverseBindingMethods)

首先先來了解幾個(gè)名詞:

  • @InverseBindingMethods:其實(shí)就是元素為@InverseBindingMethod的一個(gè)數(shù)組芙盘,用來注解
@Target(ElementType.TYPE)
public @interface InverseBindingMethods {
    InverseBindingMethod[] value();
}
  • @InverseBindingMethod:反向綁定方法脸秽,用來確定怎么去監(jiān)聽view屬性的變化和回調(diào)哪一個(gè)getter方法儒老。包含以下4個(gè)屬性:

    • type:包含attribute的view類型。
    • attribute:支持雙向綁定的屬性(string格式)记餐。
    • event:可以省略驮樊,用來通知DataBinding系統(tǒng)attribute已經(jīng)改變,默認(rèn)為attribute + "AttrChanged"片酝。(UI通知數(shù)據(jù))
    • method:可以省略囚衔,用來從view獲取數(shù)據(jù)的方法,不設(shè)定的話會自動尋找"is" 或 "get" + attribute方法钠怯。(數(shù)據(jù)刷新)

event調(diào)用時(shí)機(jī)需要通過@BindingAdapter進(jìn)行設(shè)置佳魔。

  • InverseBindingListener:反向綁定監(jiān)聽器曙聂,當(dāng)使用雙向綁定時(shí)晦炊,會在你的layout自動生成的binding類中自動生成一個(gè)InverseBindingListener的實(shí)現(xiàn)(拗口嗎?好像有一點(diǎn)點(diǎn)宁脊。断国。不理解的可以去看看源碼)。

看完這幾個(gè)名詞是不是已經(jīng)凌亂了榆苞?(話說我當(dāng)時(shí)也差點(diǎn)哭了稳衬。。)坐漏,我們來看個(gè)官方例子消化一下:

@InverseBindingMethods({
        @InverseBindingMethod(type = CompoundButton.class, attribute = "android:checked"),
})//1.這里需要雙向綁定的是checked屬性薄疚,event和method都省略了。
public class CompoundButtonBindingAdapter {
    ...
    //2.設(shè)置什么時(shí)候調(diào)用event
    @BindingAdapter(value = {"android:onCheckedChanged", "android:checkedAttrChanged"},
            requireAll = false)
    public static void setListeners(CompoundButton view, final OnCheckedChangeListener listener,
            final InverseBindingListener attrChange) {
        if (attrChange == null) {
            view.setOnCheckedChangeListener(listener);
        } else {
            view.setOnCheckedChangeListener(new OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                    if (listener != null) {
                        listener.onCheckedChanged(buttonView, isChecked);
                    }
                    attrChange.onChange();
                }
            });
        }
    }
}

    //3.我們在layout中使用雙向綁定
        <CheckBox
            android:id="@+id/checkbox"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:checked="@={user.checked}"/>
            
    //4.layout的binding類中自動生成的InverseBindingListener實(shí)現(xiàn)赊琳。
    // Inverse Binding Event Handlers
    private android.databinding.InverseBindingListener checkboxandroidCheck = new android.databinding.InverseBindingListener() {
        @Override
        public void onChange() {//這段邏輯其實(shí)就是用來更新user實(shí)體類中的checked字段的
            // Inverse of user.checked.get()
            //         is user.checked.set((java.lang.Boolean) callbackArg_0)
            boolean callbackArg_0 = checkbox.isChecked();//其實(shí)就是method
            // localize variables for thread safety
            // user.checked != null
            boolean checkedUserObjectnul = false;
            // user.checked
            android.databinding.ObservableField<java.lang.Boolean> checkedUser = null;
            // user
            lxf.androiddemos.model.UserEntity user = mUser;
            // user.checked.get()
            java.lang.Boolean CheckedUser1 = null;
            // user != null
            boolean userObjectnull = false;

            userObjectnull = (user) != (null);
            if (userObjectnull) {
              checkedUser = user.checked;

                checkedUserObjectnul = (checkedUser) != (null);
                if (checkedUserObjectnul) {
                checkedUser.set((java.lang.Boolean) (callbackArg_0));
                }
            }
        }
    };

整個(gè)反向綁定的流程下來其實(shí)就是:

  1. 定義需要反向綁定的屬性(checked)街夭,并配置event(checkedAttrChanged)和method(isChecked)。
  2. 系統(tǒng)會自動根據(jù)event找到對應(yīng)的方法(setLinstener)躏筏,配置好調(diào)用時(shí)機(jī)板丽。
  3. 開發(fā)者在layout中使用雙向綁定。
  4. 自動在binding類中生成一個(gè)InverseBindingListener的實(shí)現(xiàn)趁尼。

綁定適配器(@InverseBindingAdapter)

下面再來看個(gè)新名詞...( ╯□╰ ):

  • @InverseBindingAdapter:反向綁定適配器埃碱,用來注解方法猖辫。只包含attribute和event兩個(gè)屬性,含義同上:
    • attribute:支持雙向綁定的屬性(string格式)砚殿。
    • event:可以省略啃憎,用來通知DataBinding系統(tǒng)attribute已經(jīng)改變,默認(rèn)為attribute + "AttrChanged"似炎。需要通過@BindingAdapter進(jìn)行設(shè)置調(diào)用時(shí)機(jī)荧飞。

@InverseBindingAdapter注解的方法本身就相當(dāng)于獲取數(shù)據(jù)的getter方法(類似于@BindingAdapter注解的方法本身就相當(dāng)于setter方法)。

官方案例(雙向綁定android:text):

//1.這一步相當(dāng)于做了兩個(gè)操作:確定綁定的屬性和event名党;指定getter方法
@InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
    public static String getTextString(TextView view) {
        return view.getText().toString();
    }
//2.根據(jù)event找到對應(yīng)方法叹阔,配置event的調(diào)用時(shí)機(jī)。 
@BindingAdapter(value = {"android:beforeTextChanged", "android:onTextChanged",
            "android:afterTextChanged", "android:textAttrChanged"}, requireAll = false)
    public static void setTextWatcher(TextView view, final BeforeTextChanged before,
            final OnTextChanged on, final AfterTextChanged after,
            final InverseBindingListener textAttrChanged) {
        final TextWatcher newValue;
        if (before == null && after == null && on == null && textAttrChanged == null) {
            newValue = null;
        } else {
            newValue = new TextWatcher() {
                ...
                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                    if (on != null) {
                        on.onTextChanged(s, start, before, count);
                    }
                    if (textAttrChanged != null) {
                        textAttrChanged.onChange();
                    }
                }
                ....
            };
        }
       ...
    }
    //3.使用雙向綁定  
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onTextChanged="@{util.onTextChanged}"
            android:text="@={user.address}" />
    //4.binding類中自動生成InverseBindingListener的實(shí)現(xiàn)传睹。
private android.databinding.InverseBindingListener mboundView10androidT = new android.databinding.InverseBindingListener() {
        @Override
        public void onChange() {
            // Inverse of user.address.get()
            //         is user.address.set((java.lang.String) callbackArg_0)
            java.lang.String callbackArg_0 = android.databinding.adapters.TextViewBindingAdapter.getTextString(mboundView10);//getter方法
            // localize variables for thread safety
            // user.address != null
            boolean addressUserObjectnul = false;
            // user
            lxf.androiddemos.model.UserEntity user = mUser;
            // user.address
            android.databinding.ObservableField<java.lang.String> addressUser = null;
            // user.address.get()
            java.lang.String AddressUser1 = null;
            // user != null
            boolean userObjectnull = false;
            
            userObjectnull = (user) != (null);
            if (userObjectnull) {
                addressUser = user.address;
                addressUserObjectnul = (addressUser) != (null);
                if (addressUserObjectnul) {
                     addressUser.set((java.lang.String) (callbackArg_0));
                }
            }
        }
    };

一大堆的代碼看下來耳幢,其實(shí)綁定方法和綁定適配器兩種方法的最終效果是一樣的,實(shí)現(xiàn)過程也是大同小異欧啤,這里就不贅述了睛藻,和上面的綁定方法基本一致。

比葫蘆畫瓢

我們來自定義實(shí)現(xiàn)這樣一個(gè)效果邢隧,點(diǎn)擊改變自定義view的顏色店印,同時(shí)將色值在另一個(gè)TextView中展示出來(雖然沒什么卵用,僅僅當(dāng)個(gè)案例吧)倒慧,效果圖如下(請自覺忽略其他的東西按摘。。):

效果圖

實(shí)現(xiàn)過程:

//1.自定義ColorPicker纫谅,并為color屬性添加getter和setter方法
public class ColorPicker extends View {
    ...
    private String mColor;

    public String getColor() {
        return mColor;
    }

    public void setColor(String mColor) {
        this.mColor = mColor;
        paint.setColor(Color.parseColor(mColor));
        invalidate();
    }

    ...
}
//2.自定義反向綁定
@InverseBindingMethods({
        @InverseBindingMethod(type = ColorPicker.class,attribute = "color")
})
public class ColorPickerAdapter {

    @BindingAdapter(value = {"colorAttrChanged"},requireAll = false)
    public static void setListener(ColorPicker picker, final InverseBindingListener attrChange){
        if (attrChange!=null){
            picker.setOnColorChangeListener(new ColorPicker.OnColorChangeListener() {
                @Override
                public void onColorChange(ColorPicker picker, String color) {
                    //...

                    attrChange.onChange();
                }
            });
        }
    }
}
//3.在layout中使用雙向綁定
<lxf.androiddemos.test.ColorPicker
     android:layout_width="100dp"
     android:layout_height="100dp"
     app:color="@={user.color}" />

上面給出了關(guān)鍵代碼炫贤,剛接觸DataBinding的萌新如果理解不了可以去文末下載Demo看看,只是一個(gè)很簡單的案例付秕,應(yīng)該沒什么問題兰珍。
  接下來我們用@InverseBindingAdapter來實(shí)現(xiàn)同樣的效果:

public class ColorPickerAdapter {

    @InverseBindingAdapter(attribute = "color")
    public static String getColor(ColorPicker picker){
        return picker.getColor();
    }

    @BindingAdapter(value = {"colorAttrChanged"},requireAll = false)
    public static void setListener(ColorPicker picker, final InverseBindingListener attrChange){
        if (attrChange!=null){
            picker.setOnColorChangeListener(new ColorPicker.OnColorChangeListener() {
                @Override
                public void onColorChange(ColorPicker picker, String color) {
                    //...

                    attrChange.onChange();
                }
            });
        }
    }
}

另外關(guān)于一些情況下雙向綁定存在的死循環(huán)問題,只要在setter方法中判斷一下新老值不同即可询吴。

依賴注入

DataBindingComponent掠河,一般用于一個(gè)@BindingAdapter方法需要有多種實(shí)現(xiàn)時(shí)(比如說測試。猛计。)唠摹,我們來看一下前面那個(gè)修改年齡age的例子:

//原來的方式
 @BindingAdapter(value = {"url","placeHolder"},requireAll = false)
   public static void setImage(ImageView imageView , String url, int placeHolder){
       ImgLoadUtil.load(imageView,url,placeHolder);
   }

//運(yùn)用DataBindingComponent
//1.如果需要多種實(shí)現(xiàn),可以先建一個(gè)抽象的adapter有滑,注意方法為非靜態(tài)的
public abstract class AppAdapter {

   @BindingAdapter(value = {"url","placeHolder"},requireAll = false)
   public abstract void setImage(ImageView imageView , String url, int placeHolder);
}
//2.添加抽象adapter的實(shí)現(xiàn)跃闹,這里我們只寫了一個(gè)
public class ImgAdapter extends AppAdapter {
   @Override
   public void setImage(ImageView imageView, String url, int placeHolder) {
       ImgLoadUtil.load(imageView,url,placeHolder);
   }
}
public class Img2Adapter extends AppAdapter {
   @Override
   public void setImage(ImageView imageView, String url, int placeHolder) {
       ...
   }
}
//3.添加DataBindingComponent的實(shí)現(xiàn)(非靜態(tài)的@BindingAdapter注解方法會自動在DataBindingComponent中生成相應(yīng)的getter方法)。
public class MyComponent implements android.databinding.DataBindingComponent {
   @Override
   public AppAdapter getAppAdapter() {
       return new ImgAdapter();
   }
}
public class My2Component implements android.databinding.DataBindingComponent {
   @Override
   public AppAdapter getAppAdapter() {
       return new Img2Adapter();
   }
}
//4.Activity中調(diào)用
       //DataBingMain dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_databinding);
       //DataBindingUtil.setDefaultComponent(new MyComponent());
       DataBingMain dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_databinding,new MyComponent());

最終效果是一模一樣的。

遇到的坑

  • Error:(8, 36) 錯(cuò)誤: 程序包lxf.androiddemos.databinding不存在

遇到這種情況一般都是xml中的問題望艺,比如data標(biāo)簽中引入的包名不對苛秕,或者是布局里面使用了什么錯(cuò)誤的屬性,等等找默。艇劫。。數(shù)據(jù)量大的時(shí)候惩激,這種錯(cuò)誤一般比較難找店煞,簡直就是日了dog。

  • 需要更新數(shù)據(jù)時(shí)风钻,為getter方法設(shè)置@Bindable顷蟀,很多時(shí)候BR文件不會生成,需要重啟AS骡技,默默地再日一波dog鸣个。
  • 最好不要使用clean project,否則R文件和BR文件會被清掉布朦,R文件會自動重新生成囤萤,至于BR文件...那只dog,麻煩你再過來一下是趴。

更新(2018-1-5)

最近打開一個(gè)歷史項(xiàng)目涛舍,報(bào)了一堆BR文件找不到的異常,最終再錯(cuò)誤日志的最后看到這么一個(gè)錯(cuò)誤:

databinding.png

查了好久最后發(fā)現(xiàn)是因?yàn)樵趚ml文件里@{}代碼塊中使用了中文唆途,貌似這個(gè)問題只有在window系統(tǒng)下會出現(xiàn)富雅,記錄一下,緬懷我失去的四五個(gè)小時(shí)窘哈。

    <cn.izis.util.ZYTextView
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:gravity="center"
                android:text="@{`題目講解:` + model.firstChess}"
                android:textColor="@color/title"
                android:textSize="18sp" />

    //正確寫法
    <cn.izis.util.ZYTextView
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:gravity="center"
                android:text="@{@string/courseTeach + model.firstChess}"
                android:textColor="@color/title"
                android:textSize="18sp" />

Demo:此Demo會持續(xù)更新自己的學(xué)習(xí)歷程吹榴,歡迎star
官方文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市滚婉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌帅刀,老刑警劉巖让腹,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異扣溺,居然都是意外死亡骇窍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門锥余,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腹纳,“玉大人,你說我怎么就攤上這事〕盎校” “怎么了足画?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長佃牛。 經(jīng)常有香客問我淹辞,道長,這世上最難降的妖魔是什么俘侠? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任象缀,我火速辦了婚禮,結(jié)果婚禮上爷速,老公的妹妹穿的比我還像新娘央星。我一直安慰自己,他們只是感情好惫东,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布等曼。 她就那樣靜靜地躺著,像睡著了一般凿蒜。 火紅的嫁衣襯著肌膚如雪禁谦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天废封,我揣著相機(jī)與錄音州泊,去河邊找鬼。 笑死漂洋,一個(gè)胖子當(dāng)著我的面吹牛遥皂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播刽漂,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼演训,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了贝咙?” 一聲冷哼從身側(cè)響起样悟,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎庭猩,沒想到半個(gè)月后窟她,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蔼水,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年震糖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趴腋。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吊说,死狀恐怖论咏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情颁井,我是刑警寧澤厅贪,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站蚤蔓,受9級特大地震影響卦溢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜秀又,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一单寂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吐辙,春花似錦宣决、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至贤惯,卻和暖如春洼专,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背孵构。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工屁商, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人颈墅。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓蜡镶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親恤筛。 傳聞我的和親對象是個(gè)殘疾皇子官还,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容