關於 Data Binding
Data Binding 解決了 Android UI 編程的一個痛點,官方原生支持 MVVM 模型可以讓我們在不改變既有代碼框架的前提下,非常容易地使用這些新特性。
Data Binding 框架如果能夠推廣開來,也許 RoboGuice、ButterKnife 這樣的依賴注入框架會慢慢失去市場,因為在 Java 代碼中直接使用 View 變量的情況會越來越少。
android {
dataBinding {
enabled true
}
}
- DataBinding 出現以前,我們在實現 UI 界面時,不可避免的編寫大量的毫無營養的代碼:比如 View.findViewById(),比如各種更新 View 屬性的 setter:setText(),setVisibility(),setEnabled() 或者 setOnClickListener() 等等。
- 這些“垃圾代碼”數量越多,越容易滋生 bug。
- 使用 DataBinding,我們可以避免書寫這些“垃圾代碼”。
- 使用 Data Binding 會增加編譯出的 apk 文件的類數量和方法數量。
- 新建一個空的工程,統計打開 build.gradle 中 Data Binding 開關前后的 apk 文件中類數量和方法數量,類增加了 120+,方法數增加了 9k+(開啟混淆后該數量減少為 3k+)。
- 如果工程對方法數量很敏感的話,請慎重使用 Data Binding。
基礎功能的完整案例
【編寫布局文件】
使用 Data Binding 之后,xml 的布局文件就不再用於單純地展示 UI 元素,還需要定義 UI 元素用到的變量。所以,它的根節點不再是一個 ViewGroup,而是變成了 layout,並且新增了一個節點 data。
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data> … </data> //新增的data節點
<LinearLayout> ... </LinearLayout> //原先的根節點
</layout>
要實現 MVVM 的 ViewModel 就需要把數據(Model)與 UI(View) 進行綁定,data 節點的作用就像一個橋梁,搭建了 View 和 Model 之間的通路。
定義一個 POJO 類:
public class User {
private String firstName;//就是一個簡單的Java Bean
private String lastName;
...
}
稍后,我們會在 xml 布局文件的 data 節點中聲明一個 User 類型的 variable,這個變量會為 UI 元素提供數據,然后在 Java 代碼中把『后台』數據與這個 variable 進行綁定。
回到布局文件,在 data 節點中聲明一個 User 類型的變量 user。
<data>
<variable name="user" type="com.bqt.basic.User" />
</data>
其中 type 屬性就是我們剛剛定義的 User 類。
當然,data 節點也支持 import,並且 import 並沒有要求一定要放在使用前,所以上面的代碼可以這樣寫:
<data>
<import type="com.bqt.basic.User" />
<variable name="user" type="User" />
</data>
注意:
- java.lang.* 包中的類會被自動導入,可以直接使用。
- 基本類型的type可以使用包裝類或直接使用原型,比如,整數可以使用【type="int"】或【type="Integer"】
我們 build 工程后會自動在 build 目錄下生成一個繼承自 ViewDataBinding 的類,這個類將被放置在databinding包下。比如,如果我們的包名是com.bqt.databinding,那么它將被放置在com.bqt.databinding.databinding包下。


protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//用 DatabindingUtil.setContentView() 來替換掉 setContentView()
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
...
}
【Java代碼中綁定variable】
在創建**Binding類的實例后,我們便可以使用它來綁定variable,例如:
User user = new User("Test", "User");
binding.setUser(user);
注意,所有的 set/get 方法都是根據 variable 名稱生成的,例如,上面布局中定義了一個 name="user" 的變量:
那么就會生成對應的 set/get 方法:
public void setUser(com.bqt.databinding.model.User User) { ... }
public com.bqt.databinding.model.User getUser() { ... }
【布局中使用variable】
數據與variable 綁定之后,通過 @{} 可以直接把 Java 中定義的屬性值賦值給 xml 中 UI 元素的某個屬性。
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}" />
至此,一個簡單的數據綁定就完成了。
Observable Binding
任何Plain old Java object(POJO)可用於Data Binding,但修改POJO不會導致UI更新。比如在上面我們的案例中,我們通過 binding.setUser(user) 將數據 User 和 View 綁定在了一起,我們本想着,在 User 改變之后, View 會自動更新,但實際上是沒有這種效果的。
Data Binding的真正能力是當數據變化時,可以通知給你的Data對象。有三種不同的數據變化通知機制:Observable對象、ObservableFields以及Observable集合。當這些可觀察Data對象綁定到UI,Data對象屬性的更改后,UI也將自動更新。
Observable和BaseObservable
實現 android.databinding.Observable 接口的類可以允許添加一個監聽器到 Bound 對象以便監聽對象上的所有屬性的變化。Observable 接口有一個機制可以添加和刪除監聽器,但通知與否由開發人員管理。
為了使開發更容易,一個 BaseObservable 的基類為實現監聽器注冊機制而創建。Data實現類依然負責通知當屬性改變時。這是通過指定一個@Bindable注解給getter以及setter內通知來完成的。
- ObservableField<T>
- ObservableParcelable<T extends Parcelable>
- ObservableBoolean, ObservableByte, ObservableChar, ObservableDouble, ObservableFloat, ObservableInt, ObservableLong, ObservableShort
- [擴充的方法] synchronized void notifyChange():Notifies listeners that all properties of this instance have changed.
- [擴充的方法] void notifyPropertyChanged(int fieldId):Notifies listeners that a specific property has changed.
- [實現的方法] synchronized void addOnPropertyChangedCallback(Observable.OnPropertyChangedCallback callback):Adds a callback to listen for changes to the Observable.
- [實現的方法] synchronized void removeOnPropertyChangedCallback(Observable.OnPropertyChangedCallback callback):Removes a callback from those listening for changes.
/**
* Notifies listeners that a specific property has changed. The getter for the property
* that changes should be marked with @Bindable to generate a field in BR to be used as fieldId.
* @param fieldId The generated BR id for the Bindable field.
*/
public void notifyPropertyChanged(int fieldId) {
synchronized (this) {
if (mCallbacks == null) return;//private transient PropertyChangeRegistry mCallbacks;
}
mCallbacks.notifyCallbacks(this, fieldId, null);//通過【PropertyChangeRegistry】來實現通知監聽器
}
public class ObservableUser extends BaseObservable {
private String name;
@Bindable//給getter方法添加注解。The getter for an observable property should be annotated with Bindable.
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);//通知某個屬性改變了。Notifies listeners that a specific property has changed.
}
}
BR 是編譯階段生成的一個類,功能與 R.java 類似。
在編譯期間,通過@Bindable注解標記的getter方法返回的字段會在BR類文件中生成一個同名的Entry,如下:

package com.bqt.databinding;
public class BR {
...
public static final int name = 17;
...
}
Observable field
除此之外,還有一種更細粒度的綁定方式,可以具體到成員變量,這種方式無需繼承 BaseObservable,一個簡單的 POJO 就可以實現。
系統為我們提供了所有的原始類型所對應的 Observable 類,例如ObservableInt、ObservableFloat、ObservableBoolean等等,還有一個 ObservableField 對應着 引用類型。這種方式非常適合那些幾乎沒有幾個屬性的 POJO 。
public class android.databinding.ObservableInt extends BaseObservable implements Parcelable Serializable
ObservableInt:An observable class that holds a primitive int.
ObservableField:An object wrapper to make it observable.
Observable field classes may be used instead of creating an Observable object.
Fields of this type should be declared final because bindings only detect檢測 changes in the field's value, not of the field itself.
This class is parcelable可擴展的 and serializable but callbacks are ignored when the object is parcelled / serialized. Unless you add custom callbacks, this will not be an issue because data binding framework always re-registers callbacks when the view is bound.
要使用它需要在data對象中創建public final字段,如:
public class PlainUser {
public final ObservableField<String> name = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
要訪問該值,使用set和get方法:
PlainUser plainUser = new PlainUser();
plainUser.name.set("包青天,ObservableField");
plainUser.age.set(27);
int age = plainUser.age.get();
Observable集合
一些app使用更多的動態結構來保存數據,Observable集合允許鍵控訪問這些data對象。
【ObservableArrayMap】
public class android.databinding.ObservableArrayMap<K, V> extends ArrayMap<K, V> implements ObservableMap<K, V>
ObservableArrayMap用於鍵是引用類型,如String。
ObservableArrayMap<String, Object> mapUser = new ObservableArrayMap<>();
mapUser.put("name", "ObservableArrayMap");
mapUser.put("age", 27);
在layout文件中,通過String鍵可以訪問map。
android:text='@{mapUser["name"]}'
android:text='@{String.valueOf(1 + (Integer)mapUser["age"])}'
注意,在XML布局的某個屬性值中使用泛型時,要用【<】來代替【<】
public class android.databinding.ObservableArrayList<T> extends ArrayList<T> implements ObservableList<T>
ObservableArrayList<Object> listUser = new ObservableArrayList<>();
listUser.add("ObservableArrayList");
listUser.add(17);
listUser.set(0, "包青天,ObservableArrayList");
listUser.set(1, 27);
在layout文件中,通過索引可以訪問list:
<import type="android.databinding.ObservableList"/>
<variable name="listUser" type="ObservableList<Object>"/>
android:text='@{listUser[0]}'
android:text='@{String.valueOf(1 + (Integer)listUser[1])}'
雙向綁定
在布局中引用variable時,將之前的【@{}】改成了【@={}】即可實現雙向綁定,比如通過【@={}】將一個變量和EditText的內容綁定在一起,當用戶更改EditText中的內容時,和它綁定的變量也會同步改變。
android:text="@{user.name}"//通過【 @{user.name} 】方式綁定時,當用戶更改EditText中的內容時,和它綁定的變量【不會】同步改變
android:text="@={user2.name}"//通過【 @={user.name} 】方式綁定時,當用戶更改EditText中的內容時,和它綁定的變量【會】同步改變
常用表達式
常用表達式跟Java表達式很像,以下這些是一樣的:
- 數學 + - / * %
- 字符串連接 +
- 邏輯 && ||
- 二進制 & | ^
- 一元運算 + - ! ~
- 移位 >> >>> <<
- 比較 == > < >= <=
- instanceof
- 分組 ()
- null
- Cast
- 方法調用
- 數據訪問 []
- 三元運算 ? :
缺少的操作:
- this
- super
- new
- 顯式泛型調用
示例:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
集合元素的訪問
常用的集合:arrays、lists、sparse lists以及maps,為了簡便都可以使用 [] 來訪問。
<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<String, String>"/>
<variable name="index" type="int"/>
<variable name="key" type="String"/>
</data>
android:text="@{list[index]}"
android:text="@{sparse[index]}"
android:text="@{map[key]}"
注意,在XML布局的某個屬性值中使用泛型時,要用轉義字符【<】來代替【<】,否則編譯失敗:
> org.xml.sax.SAXParseException; 與元素類型 "variable" 相關聯的 "type" 屬性值不能包含 '<' 字符。
可以使用單引號包含屬性值,而在表達式中使用雙引號:
android:text='@{map["name"]}'
也可以使用雙引號來包含屬性值,而在字符串前后使用【`】,這個是ESC下面、Tab上面的那個鍵:
android:text="@{map[`name`]}"
android:text="@{map["name"]}"
也可以使用雙引號來包含屬性值,而在字符串前后使用轉義字符【"】:
android:text="@{map["name"]}"
PS,XML中需要的轉義字符:
&(邏輯與) &
<(小於) <
>(大於) >
"(雙引號) "
'(單引號) '
使用資源數據 Resources
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
//dimens.xml中的定義
<dimen name="largePadding">20dp</dimen>
android:padding="@{large ? (int)@dimen/largePadding : (int)@dimen/smallPadding}" //錯誤寫法
android:text="@{@string/nameFormat(firstName, lastName)}"
<string name="nameFormat">Full Name: %1$s %2$s</string> //格式化字符串
其他一些小知識點
android:text="@{MyStringUtils.upper(user.firstName)}"
android:text="@{User.SEX}"
<import type="com.examle.detail.data.User" alias="DetailUser" />
<variable name="user" type="DetailUser" />
<data class=".CustomBinding"> ... //放在package的根目錄,即上述databinding的父目錄
<data class="com.mypackage.CustomBinding"> ... //提供整個包名
【Null合並操作】
??運算符:左邊的對象如果它不是null,選擇左邊的對象;或者如果它是null,選擇右邊的對象:
android:text="@{user.displayName ?? user.lastName}"
//等價於:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
【帶 ID 的 View】
只要給 View 定義一個 ID,Data Binding 就會為我們生成一個對應的 final 字段。Binding在View層次結構上做單一的傳遞,提取帶ID的Views。這種機制比起某些Views使用findViewById還要快。例如:
android:id="@+id/firstName"
binding.firstName.setText("包");//firstName是DataBinding自動生成一個對應的變量
屬性中的 variable 名字從容器 layout 中傳遞到被包含的 layout。
注意:在include的layout文件中定義的variable必須在外部也要定義,比如下例中,在 user.xml 中必需要有 user variable。
<include layout="@layout/layout_input"/>
<include
layout="@layout/user"
bind:user="@{user}"/>
【避免 NullPointerException】
Data Binding代碼生成時自動檢查是否為nulls來避免出現NullPointerException錯誤。
例如,在表達式 @{user.name} 中,如果user是null,則 user.name 會賦予它的默認值(null)。如果你引用 user.age(age是int類型),那么它的默認值是0。
【直接Binding】
當一個variable或observable變化時,binding會在下一幀之前被計划要改變。有很多次,但是在Binding時必須立即執行。要強制執行,使用executePendingBindings()方法。
Evaluates評估 the pending bindings, updating any Views that have expressions bound to modified variables. This must be run on the UI thread.
【后台線程】
只要它不是一個集合,你可以在后台線程中改變你的數據模型。在判斷是否要避免任何並發問題時,Data Binding會對每個Varialbe/field本地化。
使用ViewStubs
xml 文件與之前的代碼一樣,根節點改為 layout,在 LinearLayout 中添加一個 ViewStub,添加 ID。
<ViewStub
android:id="@+id/m_view_stub"
android:layout="@layout/m_view_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
編譯后會在 Binding 中生成同名的 ViewStubProy 類型的成員:
public final android.databinding.ViewStubProxy mViewStub;
在 Java 代碼中,通過 Binding 實例為 ViewStubProy 注冊 ViewStub.OnInflateListener 事件,當監聽到ViewStub的OnInflateListener事件時需要為新的布局創建一個Binding:
mBinding.mViewStub.setOnInflateListener(new ViewStub.OnInflateListener() {//監聽ViewStub的OnInflateListener監聽器
@Override
public void onInflate(ViewStub stub, View inflated) {//當載入另一個layout時為新的布局創建一個Binding
MViewStubBinding binding = DataBindingUtil.bind(inflated);//同樣,此Binding的名字取決於ViewStub布局的名字。
User user = new User("fee", "lang");
binding.setUser(user);//此Binding只能處理ViewStub布局中帶id的View或變量,而不能處理根布局中的東西
}
});
//填充ViewStub
if (!mBinding.mViewStub.isInflated()) mBinding.mViewStub.getViewStub().inflate();
動態Variables,如RecyclerView
static class UserAdapter2 extends RecyclerView.Adapter<UserAdapter2.UserHolder> {
private List<User> mUsers;
public UserAdapter2(List<User> mUsers) {
this.mUsers = mUsers;
}
static class UserHolder extends RecyclerView.ViewHolder {
private ViewDataBinding binding;
public UserHolder(View itemView) {
super(itemView);
}
public ViewDataBinding getBinding() {
return binding;
}
public void setBinding(ViewDataBinding binding) {
this.binding = binding;
}
}
@Override
public int getItemCount() {
return mUsers.size();
}
@Override
public UserHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()),
R.layout.user_item, viewGroup, false);//【在 onCreateViewHolder 的時候創建這個 DataBinding】
UserHolder holder = new UserHolder(binding.getRoot());
holder.setBinding(binding);
return holder;
}
@Override
public void onBindViewHolder(UserHolder holder, int position) {
ViewDataBinding binding = holder.getBinding();//【在 onBindViewHolder 中獲取這個 DataBinding】
binding.setVariable(BR.user, mUsers.get(position));
binding.executePendingBindings();
}
}
還有另外一種比較簡潔的方式,直接在構造 Holder 時把 View 與自動生成的 XXXBinding 進行綁定。
static class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserHolder> {
private List<User> mUsers;
public UserAdapter(List<User> mUsers) {
this.mUsers = mUsers;
}
static class UserHolder extends RecyclerView.ViewHolder {//最主要的區別就是在這里!
private UserItemBinding mBinding;
public UserHolder(View itemView) {
super(itemView);
mBinding = DataBindingUtil.bind(itemView);//【在構造 Holder 時把 itemView 與自動生成的 XXXBinding 進行綁定】
}
public void bind(User user) {
mBinding.setUser(user);//綁定數據
}
}
@Override
public int getItemCount() {
return mUsers.size();
}
@Override
public UserHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.user_item, viewGroup, false);
return new UserHolder(itemView);
}
@Override
public void onBindViewHolder(UserHolder holder, int position) {
holder.bind(mUsers.get(position));//只需要為每一個item綁定數據,而不需要手動操作item中的UI
}
}
屬性 Setters
自動Setters
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}"/>
同樣,對於自定義View,即使屬性沒有在 declare-styleable 中定義,我們也可以通過 xml 進行賦值操作。
為了演示這個功能,我自定義了一個 View - NameCard,屬性資源 R.styleable.NameCard 中只定義了一個 age 屬性:
<resources>
<declare-styleable name="NameCard">
<attr name="age" format="integer" />
</declare-styleable>
</resources>
其中 firstName 和 lastName 有對應的兩個 setter 方法(前提條件):
public class NameCard extends LinearLayout {
private TextView mFirstName, mLastName;
public void setFirstName(@NonNull final String firstName) {
mFirstName.setText(firstName);
}
public void setLastName(@NonNull final String lastName) {
mLastName.setText(lastName);
}
public void setAge(@IntRange(from = 1) int age) {
mAge = age;
}
...
}
只要有 setter 方法就可以像下面代碼一樣賦值:
<com.bqt.databinding.NameCard
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:onClickListener="@{activity.clickListener}"
app:firstName="@{@string/firstName}"
app:lastName="@{@string/lastName}"
app:age="27" />
onClickListener 也是同樣道理(因為任何View都有對應的setOnClickListener方法),只不過我們是在 Activity 中定義了一個 Listener。
重命名Setters
- BindingMethod:Used within an BindingMethods annotation to describe a renaming of an attribute to the setter used to set that attribute. By default, an attribute attr will be associated with setter setAttr.
- BindingMethods:Used to enumerate attribute-to-setter renaming. By default, an attribute is associated with setAttribute setter. If there is a simple rename, enumerate them in an array of BindingMethod annotations in the value.
@Target(ElementType.ANNOTATION_TYPE)
public @interface BindingMethod {
Class type();//The View Class that the attribute is associated with.
String attribute();//The attribute to rename. Use android: namespace for all android attributes or no namespace for application attributes.
String method();//The method to call to set the attribute value.
}
@Target({ElementType.TYPE})
public @interface BindingMethods {
BindingMethod[] value();
}
@BindingMethods({
@BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"),
@BindingMethod(type = TextView.class, attribute = "android:drawablePadding", method = "setCompoundDrawablePadding"),
...
})
public class TextViewBindingAdapter { ... }
自定義Setters
@BindingAdapter注解簡介
@Target(ElementType.METHOD)
public @interface BindingAdapter {
String[] value();//The attributes associated with this binding adapter.
boolean requireAll() default true;
}
@BindingAdapter("android:bufferType")
public static void setBufferType(TextView view, TextView.BufferType bufferType) {
view.setText(view.getText(), bufferType);
}
@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue, View.OnLayoutChangeListener newValue) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (oldValue != null) view.removeOnLayoutChangeListener(oldValue);//先移除舊的
if (newValue != null) view.addOnLayoutChangeListener(newValue);//再添加新的
}
}
@BindingAdapter({"android:onClick", "android:clickable"})
public static void setOnClick(View view, View.OnClickListener clickListener, boolean clickable) {
view.setOnClickListener(clickListener);
view.setClickable(clickable);
}
系統提供的示例
public class TextViewBindingAdapter {
@BindingAdapter({"android:bufferType"})
public static void setBufferType(TextView view, TextView.BufferType bufferType) {
view.setText(view.getText(), bufferType);
}
private static void setIntrinsicBounds(Drawable drawable) {
if (drawable != null) drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
}
@BindingAdapter({"android:drawableBottom"})
public static void setDrawableBottom(TextView view, Drawable drawable) {
setIntrinsicBounds(drawable);//設置邊界
Drawable[] drawables = view.getCompoundDrawables();
view.setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawable);//修改DrawableBottom
}
}
自定義案例一
<variable name="pt" type="int"/>
android:paddingTop="@{pt}"
@BindingAdapter({"android:paddingTop"})
public static void setPaddingTopAndBottom(View view, int paddingTB) {//當設置paddingTop時,同時設置paddingTop和paddingBottom
view.setPadding(view.getPaddingLeft(), paddingTB, view.getPaddingRight(), paddingTB);
}
mBinding.setPt(10 * new Random().nextInt(10));
自定義案例二
<com.liangfeizc.avatarview.AvatarView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:error="@{@drawable/error}"
app:imageUrl="@{imageUrl}"
app:onClickListener="@{activity.mClickListener}"/>
@BindingAdapter({"bind:imageUrl", "error"})//自定義 namespaces 將被忽略
public static void loadBqtImage(ImageView view, String url, Drawable error) {//方法名隨意,但參數必須和注解中指定的屬性一一對應
Picasso.with(view.getContext()).load(url).error(error).into(view);
}
- 匹配過程中自定義 namespaces 將被忽略,你可以在@BindingAdapter中加任何 namespaces,但是會有如下提示:
Error:(37, 21) 警告: Application namespace for attribute bind:imageUrl will be ignored.
- 允許重寫android的命名空間。
- 當你創建的適配器屬性與系統默認的產生沖突時,你的自定義適配器將會覆蓋掉系統原先定義的注解,這將會產生一些意外的問題。
轉換器 @BindingConversion
對象轉換
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
@BindingConversion注解簡介
@Target({ElementType.METHOD})
public @interface BindingConversion {
}
自定義轉換
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
/**
* !!! Binding conversion should be forbidden, otherwise it will conflict with{@code android:visiblity} attribute.
*/
@BindingConversion
public static int convertColorToString(int color) {
switch (color) {
case Color.RED:
return R.string.red;
case Color.WHITE:
return R.string.white;
}
return R.string.app_name;
}
2017-9-27
