您好,登錄后才能下訂單哦!
在Android Studio 2.1 Preview 3之后,官方開始支持雙向綁定了。
可惜目前Google并沒有在Data Binding指南里面加入這個教程,并且在整個互聯(lián)網(wǎng)之中只有這篇文章介紹了如何使用反向綁定。
在閱讀一下文章之前,我假設(shè)你已經(jīng)知道如何正向綁定。
回顧一下Data Binding
在正向綁定中,我們在Layout里面的綁定表達(dá)式是這樣的:
<layout ...> <data> <variable type="com.example.myapp.User" name="user"/> </data> <RelativeLayout ...> <TextView android:text="@{user.name}" .../> </RelativeLayout> </layout>
當(dāng)user.name的數(shù)據(jù)改動時,我們的TextView都會同步改變文字。
雙向綁定
現(xiàn)在假設(shè)一種情況,當(dāng)你更換成EditText時,如果你的用戶名User.name已經(jīng)綁定到EditText中,當(dāng)用戶輸入文字的時候,你原來的user.name數(shù)據(jù)并沒有同步改動,因此我們需要修改成:
<layout ...> <data> <variable type="com.example.myapp.User" name="user"/> </data> <RelativeLayout ...> <EditText android:text="@={user.name}" .../> </RelativeLayout> </layout>
看出微小的差別了嗎?對,就是"@{}"改成了"@={}",是不是很簡單?
隱式引用屬性
同樣你也可以在別的View上引用屬性:
<layout ...> <data> <import type="android.view.View"/> </data> <RelativeLayout ...> <CheckBox android:id="@+id/seeAds" .../> <ImageView android:visibility="@{seeAds.checked ? View.VISIBLE : View.GONE}" .../> </RelativeLayout> </layout>
當(dāng)CheckBox的狀態(tài)發(fā)生改變的時候,ImageView也會同時發(fā)生改變。在復(fù)雜情況下,這個特性沒什么卵用,因為邏輯部分我們是不建議寫在XML中。
如何開啟雙向綁定
開啟雙向綁定,需要在項目的build.gradle中設(shè)置:
classpath 'com.android.tools.build:gradle:2.1.0-alpha3'
同樣,你需要在你Module的build.gradle中設(shè)置:
android { ... dataBinding.enabled = true }
貌似還有點問題
我們剛才的例子里面只顯示了系統(tǒng)自帶的應(yīng)用,那么如果是自定義控件,或者是我們更細(xì)顆粒度的Observable呢?等下就揭曉如何自定義自己的雙向綁定,我們來看看目前Android支持的控件:
自定義雙向綁定
設(shè)想一下我們使用了下拉刷新SwipeRefreshLayout
控件,這個時候我們希望在加載數(shù)據(jù)的時候能控制refreshing的狀態(tài),所以我們加入了ObservableBoolean的變量swipeRefreshViewRefreshing
來正向綁定數(shù)據(jù),并且能夠在用戶手動下拉刷新的時候同步更新swipeRefreshViewRefreshing數(shù)據(jù):
// SwipeRefreshLayout.java public class SwipeRefreshLayout extends View { private boolean isRefreshing; public void setRefreshing() {/* ... */} public boolean isRefreshing() {/* ... */} public void setOnRefreshListener(OnRefreshListener listener) { /* ... */ } public interface OnRefreshListener { void onRefresh(); } }
接下來我們需要告訴框架,我們需要將SwipeRefreshLayout
的isRefreshing的值反向綁定到swipeRefreshViewRefreshing
:
@InverseBindingMethods({ @InverseBindingMethod( type = android.support.v4.widget.SwipeRefreshLayout.class, attribute = "refreshing", event = "refreshingAttrChanged", method = "isRefreshing")})
這是一種簡單的定義,其中event和method都不是必須的,因為系統(tǒng)會自動生成,寫出來是為了更好地了解如何綁定的,可以參考官方文檔InverseBindingMethod。
當(dāng)然你也可以使用另外一種寫法,并且如果你的值并不是直接對應(yīng)Observable的值的時候,就可以在這里進(jìn)行轉(zhuǎn)換:
@InverseBindingAdapter(attribute = "refreshing", event = "refreshingAttrChanged") public static boolean isRefreshing(SwipeRefreshLayout view) { return view.isRefreshing(); }
上面的event同樣也不是必須的。以上的定義都是為了讓我們能夠在布局文件中使用"@={}"這個雙向綁定的特性。接下來你需要告訴框架如何處理refreshingAttrChanged事件,就像處理一般的監(jiān)聽事件一樣:
@BindingAdapter("refreshingAttrChanged") public static void setOnRefreshListener(final SwipeRefreshLayout view, final InverseBindingListener refreshingAttrChanged) { if (refreshingAttrChanged == null) { view.setOnRefreshListener(null); } else { view.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh() { colorChange.onChange(); } }); } }
一般情況下,我們都需要設(shè)置正常的OnRefreshListener,所以我們可以合并寫成:
@BindingAdapter(value = {"onRefreshListener", "refreshingAttrChanged"}, requireAll = false) public static void setOnRefreshListener(final SwipeRefreshLayout view, final OnRefreshListener listener, final InverseBindingListener refreshingAttrChanged) { OnRefreshListener newValue = new OnRefreshListener() { @Override public void onRefresh() { if (listener != null) { listener.onRefresh(); } if (refreshingAttrChanged != null) { refreshingAttrChanged.onChange(); } } }; OnRefreshListener oldValue = ListenerUtil.trackListener(view, newValue, R.id.onRefreshListener); if (oldValue != null) { view.setOnRefreshListener(null); } view.setOnRefreshListener(newValue); }
現(xiàn)在我們終于可以使用雙向綁定的技術(shù)啦。但是要注意,需要設(shè)置requireAll = false,否則系統(tǒng)將識別不了refreshingAttrChanged屬性,前文提到的文章例子里并沒有設(shè)置這個。
在ViewModel中,我們的數(shù)據(jù)是這樣的:
// MyViewModel.java public final ObservableBoolean swipeRefreshViewRefreshing = new ObservableBoolean(false); public void load() { swipeRefreshViewRefreshing.set(true); // 網(wǎng)絡(luò)請求 .... swipeRefreshViewRefreshing.set(false); } public SwipeRefreshLayout.OnRefreshListener onRefreshListener() { return new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { // Do something you need } }; }
在布局文件中是這樣設(shè)置的:
<android.support.v4.widget.SwipeRefreshLayout android:id="@+id/swipe_refresh_view" android:layout_width="match_parent" android:layout_height="match_parent" app:onRefreshListener="@{viewModel.onRefreshListener}" app:refreshing="@={viewModel.swipeRefreshViewRefreshing}"> ... </android.support.v4.widget.SwipeRefreshLayout>
最后我們還有一個小問題,就是雙向綁定有可能會出現(xiàn)死循環(huán),因為當(dāng)你通過Listener反向設(shè)置數(shù)據(jù)時,數(shù)據(jù)也會再次發(fā)送事件給View。所以我們需要在設(shè)置一下避免死循環(huán):
@BindingAdapter("refreshing") public static void setRefreshing(SwipeRefreshLayout view, boolean refreshing) { if (refreshing != view.isRefreshing()) { view.setRefreshing(refreshing); } }
這樣就沒問題啦。以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。