Android Data Binding——高級

上一篇文章Android Data Binding——進(jìn)階
介紹了Data Binding的語法等進(jìn)階功能介褥。這一篇我們來介紹一下Data Binding的數(shù)據(jù)對象逾礁。

文中的例子可前往DataBindingDemo查看苞尝。

任何POJO對象都可以用在data binding中畸肆,但是對象改變時候,要如何通知UI更新呢宙址?這是使用Data Binding最奧妙的地方轴脐。Ps:我們這邊只是介紹如何使用,沒有涉及到實現(xiàn)原理抡砂。

有三種不同的數(shù)據(jù)變化通知機(jī)制:observable objects, observable fields, and observable collections.

這些observable對象綁定到UI上大咱,當(dāng)對象的屬性更改時就會自動通知UI更新。

Observale Objects

一個繼承Observable接口的類注益,data binding會設(shè)置一個listener用于監(jiān)聽綁定的對象的屬性變化碴巾。

public interface Observable {

    /**
     * Adds a callback to listen for changes to the Observable.
     * @param callback The callback to start listening.
     */
    void addOnPropertyChangedCallback(OnPropertyChangedCallback callback);

    /**
     * Removes a callback from those listening for changes.
     * @param callback The callback that should stop listening.
     */
    void removeOnPropertyChangedCallback(OnPropertyChangedCallback callback);

    /**
     * The callback that is called by Observable when an observable property has changed.
     */
    abstract class OnPropertyChangedCallback {

        /**
         * Called by an Observable whenever an observable property changes.
         * @param sender The Observable that is changing.
         * @param propertyId The BR identifier of the property that has changed. The getter
         *                   for this property should be annotated with {@link Bindable}.
         */
        public abstract void onPropertyChanged(Observable sender, int propertyId);
    }
}

Observable接口有注冊/刪除監(jiān)聽的方法,但是數(shù)據(jù)變化時是否通知取決于開發(fā)者丑搔。為了簡化開發(fā)厦瓢,data binding提供了一個BaseObservable的基類,幫我們實現(xiàn)了監(jiān)聽的注冊和刪除啤月。這個類也實現(xiàn)了通知數(shù)據(jù)變化的方法煮仇,在getter中使用Bindable注解,在setter中調(diào)用notifyPropertyChanged通知數(shù)據(jù)變更谎仲。

public class ObservableUser extends BaseObservable {
    public String firstName;
    public String lastName;

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

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

    public void setFirstName(String firstName) {
        this.firstName = firstName;
        notifyPropertyChanged(BR.firstName);
    }

    @Bindable
    public String getLastName() {
        return lastName;
    }

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

Bindable注解會在編譯時在BR中生成一個entry浙垫,當(dāng)數(shù)據(jù)變化時調(diào)用notifyPropertyChanged通知這個entry數(shù)據(jù)發(fā)生了變化。

ObservableFields

創(chuàng)建Observable類還是比較麻煩的,data binding為我們提供了一個便捷的ObservableField類以及它的派生類:
ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable.

ObservableFields是包含了一個單一屬性的observable objects夹姥,可以通過聲明一個public final field來使用它:

public class ObservableFieldUser extends BaseObservable {
    public final ObservableField<String> firstName = new ObservableField<>();
    public final ObservableField<String> lastName = new ObservableField<>();

    public ObservableFieldUser(String firstName, String lastName) {
        this.firstName.set(firstName);
        this.lastName.set(lastName);
    }
}

然后可以用set/get來存取數(shù)據(jù):

user.firstName.set("bai");
String s = user.firstName.get();

Observable Collections

有些應(yīng)用希望使用更加靈活的結(jié)構(gòu)來管理數(shù)據(jù)杉武,Observable集合類允許使用key來訪問這些數(shù)據(jù)對象。

  • 如果key是String佃声,ObservableArrayMap會非常有用:
ObservableMap<String, String> userMap = new ObservableArrayMap<>();
userMap.put("firstName", "bai");
userMap.put("lastName", "li");
binding.setUserMap(userMap);

然后在布局文件中用String keys獲取map中的數(shù)據(jù):

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

    <data>
        <import type="android.databinding.ObservableMap" />
        <variable
            name="userMap"
            type="ObservableMap<String,String>" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{`obserable map : ` + userMap[`firstName`] + ` ` + userMap[`lastName`]}" />
    </LinearLayout>
</layout>
  • 如果key是integer,ObservableArrayList會非常有用:
ObservableList<User> useList = new ObservableArrayList<>();
useList.add(new User("bai", "li"));
binding.setUserList(useList);

然后在布局文件中使用下標(biāo)獲取list中的數(shù)據(jù):

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

    <data>
        <import type="com.dragonjiang.databindingdemo.model.User" />
        <import type="android.databinding.ObservableList" />
        <variable
            name="userList"
            type="ObservableList<User>" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{`obserable list : ` + userList[0].toString()}" />
    </LinearLayout>
</layout>

生成綁定

自動生成的Binding類都繼承了ViewDataBinding類艺智,它們是連接layout的variables和Views的橋梁。

Creating

binding在View inflate之后創(chuàng)建圾亏。inflate方法會將Veiw綁定到binding上十拣,對于不同的Veiw有不同的創(chuàng)建方法:

MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);

如果布局使用不同的機(jī)制inflate,可以單獨綁定:

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

有時候綁定不能提前確定志鹃,例如ListView的Item layout夭问,這時候可以使用DataBindingUtil類:

ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
    parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);

有ID的View

我們在之前的例子里面都沒有給View聲明一個id,因為用不到曹铃。但是如果有些情況下缰趋,我要調(diào)用到布局里面的特定的View,還是需要一個id陕见。data binding提供了一個比findViewById更快的機(jī)制:

<TextView
    android:id="@+id/tv_name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{user.firstName + ` ` + user.lastName}" />

data binding會在binding類中自動生成對應(yīng)的屬性:

public final TextView tvName;

可以直接使用:

binding.tvName.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(LayoutDetailsActivity.this, binding.tvName.getText(), Toast.LENGTH_SHORT).show();
    }
});

ViewStubs

ViewStub不同于正常的View秘血,它一開始是不可見的,在需要時才加載出特定的布局评甜。所以data binding提供了一個ViewStubProxy類來代替ViewStub,開發(fā)者可以通過這個類來操作ViewStub灰粮。

ViewStub需要在inflate時候創(chuàng)建一個binding,故需要設(shè)置監(jiān)聽ViewStub.OnInflateLister

public class ViewStubActivity extends AppCompatActivity {

    private ActivityViewStubBinding mBinding;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub);
        mBinding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
            @Override
            public void onInflate(ViewStub stub, View inflated) {
                IncludeBinding binding = DataBindingUtil.bind(inflated);
                binding.setUser(new User("bai", "li"));
            }
        });
    }

    public void onClick(View view) {
        if (!mBinding.viewStub.isInflated()) {
            mBinding.viewStub.getViewStub().inflate();
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data></data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onClick"
            android:text="inflate view_stub" />

        <ViewStub
            android:id="@+id/view_stub"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout="@layout/include" />
    </LinearLayout>
</layout>

高級綁定

有些情況下忍坷,例如RecyclerView.Adapter中我們無法事先知道binding類粘舟。需要在onBindViewHodler(VH, int)中給binding賦值。
在這種情況下佩研,RecyclerView布局內(nèi)都設(shè)置了一個item變量柑肴,可以通過getBinding方法返回一個ViewDataBinding類:

public void onBindViewHolder(VH holder, int position) {
    holder.binding.setModel(mDataList.get(position));
    holder.binding.executePendingBindings();
}

注意到上面executePendingBindings()表示立即綁定。如果沒有指定立即執(zhí)行旬薯,在數(shù)據(jù)變化時晰骑,binding會在下一幀開始前觸發(fā)。

屬性設(shè)置

當(dāng)綁定的數(shù)據(jù)變化時绊序,自動生成的binding類會尋找對應(yīng)屬性的setter方法些侍。data binding框架設(shè)置了幾種自定義賦值的機(jī)制。

自動Setter

對于一個屬性政模,data binding 嘗試找到對應(yīng)的setter方法岗宣,例如我們自定義了一個UserView類,實現(xiàn)一個setUser方法:

public class UserView extends AppCompatTextView {
    public UserView(Context context) {
        super(context);
    }

    public UserView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public UserView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void setUser(User user) {
        this.setText(user.toString());
    }
}

在布局文件中使用:

<com.dragonjiang.databindingdemo.ui.UserView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:user="@{user}" />

data binding自動為我們找到了setUser(User user)的方法淋样。

重命名Setter

有的屬性的名稱與它的setter不匹配耗式,對于這類屬性,可以使用注解BindingMethods將屬性與setter關(guān)聯(lián)起來。例如下面這個例子將andorid:tintsetImageTintList關(guān)聯(lián)起來:

@BindingMethods({
       @BindingMethod(type = "android.widget.ImageView",
                      attribute = "android:tint",
                      method = "setImageTintList"),
})

自定義Setter(Binding Adapter)

有些屬性需要自定義屬性設(shè)置邏輯刊咳,例如沒有android:paddingLeft屬性對應(yīng)的setter方法彪见。但是有setPadding(left, top, right, bottom)。一個用BindingAdapter注解的靜態(tài)方法允許開發(fā)者自定義setter:

@android.databinding.BindingAdapter("android:paddingLeft")
    public static void setPaddingLeft(View view, int padding) {
        view.setPadding(padding,
                view.getPaddingTop(),
                view.getPaddingRight(),
                view.getPaddingBottom());
    }

BindingAdapter的方法還可以獲取舊的值娱挨。只需將舊的值放前面余指,新的值放后面:

@android.databinding.BindingAdapter("android:paddingLeft")
    public static void setPaddingLeft(View view, int oldPadding, int padding) {
        if (oldPadding != padding) {
            view.setPadding(padding,
                    view.getPaddingTop(),
                    view.getPaddingRight(),
                    view.getPaddingBottom());
        }
    }

BindingAdapter很強(qiáng)大,尤其對自定義屬性跷坝。比如可以用來異步加載圖片:

@android.databinding.BindingAdapter({"imageUrl", "error"})
    public static void loadImage(ImageView view, String url, Drawable error) {
        Glide.with(view.getContext()).load(url).error(error).into(view);
    }
<ImageView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:error="@{@drawable/ic_launcher}"
    app:imageUrl="@{user.avatar}" />

當(dāng)imageUrlerror屬性被使用時酵镜,就會匹配調(diào)用BindindAdapter的loadImage方法。

轉(zhuǎn)換器

對象轉(zhuǎn)換

如果binding表達(dá)式返回一個對象柴钻,data binding會尋找對應(yīng)的setter(自動setter淮韭、重命名setter、自定義setter)贴届,然后將返回的對象強(qiáng)制轉(zhuǎn)換成setter需要的類型靠粪。
這是一個使用ObservableMap的例子:

<TextView
   android:text='@{userMap["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

userMap返回一個對象,這個對象會被自動轉(zhuǎn)換為setText(CharSequence)需要的類型毫蚓。如果類型轉(zhuǎn)換有問題占键,開發(fā)者需要受到進(jìn)行類型轉(zhuǎn)換。

自定義轉(zhuǎn)換

有時候需要對一些特定的類型直接做轉(zhuǎn)換元潘,例如設(shè)置背景:

<com.dragonjiang.databindingdemo.ui.UserView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@{user.isAdult ? @color/colorAccent : @color/colorPrimary}"
    app:user="@{user}" />

這里background需要Drawable類型畔乙,而color是int類型,此時需要一個BindingConversation將int轉(zhuǎn)為ColorDrawable:

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   return new ColorDrawable(color);
}

注意:轉(zhuǎn)換只能在setter時生效柬批,所以不允許混合類型

<View
   <!--這是不允許的-->
   android:background="@{isError ? @drawable/error : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市袖订,隨后出現(xiàn)的幾起案子氮帐,更是在濱河造成了極大的恐慌,老刑警劉巖洛姑,帶你破解...
    沈念sama閱讀 210,835評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件上沐,死亡現(xiàn)場離奇詭異,居然都是意外死亡楞艾,警方通過查閱死者的電腦和手機(jī)参咙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,900評論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來硫眯,“玉大人蕴侧,你說我怎么就攤上這事×饺耄” “怎么了净宵?”我有些...
    開封第一講書人閱讀 156,481評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我择葡,道長紧武,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,303評論 1 282
  • 正文 為了忘掉前任敏储,我火速辦了婚禮阻星,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘已添。我一直安慰自己妥箕,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,375評論 5 384
  • 文/花漫 我一把揭開白布酝碳。 她就那樣靜靜地躺著矾踱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪疏哗。 梳的紋絲不亂的頭發(fā)上呛讲,一...
    開封第一講書人閱讀 49,729評論 1 289
  • 那天,我揣著相機(jī)與錄音返奉,去河邊找鬼贝搁。 笑死,一個胖子當(dāng)著我的面吹牛芽偏,可吹牛的內(nèi)容都是我干的雷逆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,877評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼污尉,長吁一口氣:“原來是場噩夢啊……” “哼膀哲!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起被碗,我...
    開封第一講書人閱讀 37,633評論 0 266
  • 序言:老撾萬榮一對情侶失蹤某宪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后锐朴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兴喂,經(jīng)...
    沈念sama閱讀 44,088評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,443評論 2 326
  • 正文 我和宋清朗相戀三年焚志,在試婚紗的時候發(fā)現(xiàn)自己被綠了衣迷。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,563評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡酱酬,死狀恐怖壶谒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情膳沽,我是刑警寧澤佃迄,帶...
    沈念sama閱讀 34,251評論 4 328
  • 正文 年R本政府宣布泼差,位于F島的核電站,受9級特大地震影響呵俏,放射性物質(zhì)發(fā)生泄漏堆缘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,827評論 3 312
  • 文/蒙蒙 一普碎、第九天 我趴在偏房一處隱蔽的房頂上張望吼肥。 院中可真熱鬧,春花似錦麻车、人聲如沸缀皱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,712評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽啤斗。三九已至,卻和暖如春赁咙,著一層夾襖步出監(jiān)牢的瞬間钮莲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,943評論 1 264
  • 我被黑心中介騙來泰國打工彼水, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留崔拥,地道東北人。 一個月前我還...
    沈念sama閱讀 46,240評論 2 360
  • 正文 我出身青樓凤覆,卻偏偏與公主長得像链瓦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子盯桦,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,435評論 2 348

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