介绍
Data Binding 类库(Android 2.1(API level 7+))是用于编写xml layout 布局,并且尽量减少粘合代码对你的应用逻辑和布局上的绑定。
构建
在应用 module 的 build.gradle 添加 dataBinding 支持,Android Studio 版本必须 1.3+。
android {
....
dataBinding {
enabled = true
}
}
XML布局文件
布局文件必须以 <layout/> 标签作为根标签,子标签包括一个 <data/> 元素和一个 <view/> 元素。
<?xml version="1.0" encoding="utf-8"?>
<!--根标签-->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<!--data-->
<data>
<import type="android.view.View" />
<import type="android.text.TextUtils" />
<variable
name="item"
type="cn.com.bluemoon.washmaster.video.Industry" />
</data>
<!--view-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
...
</LinearLayout>
</layout>
-
import
data元素内可以使用零个或多个 import
元素。 这些就像在 Java 中一样可以轻松地引用类到你的布局文件中。
<data>
<import type="android.view.View" />
<import type="android.text.TextUtils" />
</data>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="@{TextUtils.isEmpty(item.remark) ? View.GONE : View.VISIBLE}" />
如果类名有冲突的话,则需起别名。
<import type="android.view.View"/>
<import type="cn.com.bluemoon.washmaster.video.View" alias="Bview"/>
-
variable
variable
在布局中设置的属性,以用于布局文件中的绑定表达式。
<data>
<import type="android.graphics.drawable.Drawable"/>
<variable name="item" type="cn.com.bluemoon.washmaster.video.Industry" />
<variable name="image" type="Drawable"/>
</data>
Observable 接口
变量类型在编译时被检查,所以如果一个变量实现了 Observable
或者一个 observable collection
,那么它应该被反映在类型中。 如果变量是没有实 Observable
接口的基类或接口,那么它将不会被观察!
//BaseObservable是Observable的子类
public class Industry extends BaseObservable {
private String remark;
private boolean likeFlag; //点赞
@Bindable
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
@Bindable
public boolean isLikeFlag() {
return likeFlag;
}
public void setLikeFlag(boolean likeFlag) {
this.likeFlag = likeFlag;
notifyPropertyChanged(BR.likeFlag);
}
...
}
@Bindable
注解给getter赋值
notifyPropertyChanged 是 Observable
接口通过方便的PropertyChangeRegistry
来实现用于储存和有效地通知监听器,比如点赞需要动态改变颜色状态
BR 类似R.class的文件,通过编译后生成,@Bindable 或 variable 声明变量后会生成对应的int值
public class BR {
public static final int _all = 0;
public static final int authorInfo = 1;
public static final int comeFrom = 2;
public static final int commentNum = 3;
public static final int contentId = 4;
public static final int displayPic = 5;
public static final int item = 6;
public static final int likeFlag = 7;
public static final int likeNum = 8;
public static final int position = 9;
public static final int remark = 10;
public static final int title = 11;
public static final int type = 12;
public static final int viewModel = 13;
public BR() {
}
}
- Observable 字段
如果只有少许变量,可以使用ObservableField,或者 ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable.
private static class User {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
<data>
<import type="android.databinding.ObservableMap"/>
<variable name="user" type="ObservableMap<String, Object>"/>
</data>
…
<TextView
android:text='@{user["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text='@{String.valueOf(1 + (Integer)user["age"])}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
- ObservableArrayList
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);
<data>
<import type="android.databinding.ObservableList"/>
<import type="com.example.my.app.Fields"/>
<variable name="user" type="ObservableList<Object>"/>
</data>
…
<TextView
android:text='@{user[Fields.LAST_NAME]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
-
include
variable变量可以从包含的布局中传递到包含的布局的绑定中:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="item" type="cn.com.bluemoon.washmaster.video.Industry" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/home" bind:item="@{item}"/>
<include layout="@layout/contact" bind:item="@{item}"/>
</LinearLayout>
</layout>
home.xml
和 contact.xml
布局文件中都必须有一个 item 变量。数据绑定不支持 include
作为 merge
元素的直接子元素。
绑定数据
-
Activity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainActivity binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
Industry item = new Industry();
binding.setVariable(BR.item, item);
}
-
Fragment
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
ViewDataBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment, false);
Industry item = new Industry();
binding.setVariable(BR.item, item);
return binding.getRoot();
}
-
RecyclerView
class VideoAdapter extends RecyclerView.Adapter {
private List<Industry> mList;
public VideoAdapter(List<Industry> list) {
this.mList = list;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()), R.layout.video_list, viewGroup, false);
return new BaseViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
((BaseViewHolder) viewHolder).getBinding().setVariable(BR.item, mList.get(i));
((BaseViewHolder) viewHolder).getBinding().setVariable(BR.position, i);
}
@Override
public int getItemCount() {
return mList.size();
}
public class BaseViewHolder extends RecyclerView.ViewHolder {
protected ViewDataBinding binding;
public BaseViewHolder(ViewDataBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public ViewDataBinding getBinding() {
return binding;
}
}
}
Views With IDs
-
自定义 Binding 类名称
默认情况下,Binding类的命名是基于所述layout文件的名称,用大写开头,除去下划线(_)以及(_)后的第一个字母大写,然后添加“Binding”后缀。这个类将被放置在一个模块封装包里的databinding
封装包下。例如,所述layout文件video_list.xml
将生成VideoListBinding
Binding类可通过调整data
元素中的class
属性来重命名或放置在不同的包中。例如:
<data class=".VideoLayout">
...
</data>
<data class="com.bluemoon.VideoLayout">
...
</data>
-
带ID的Views
编译后生成的Binding类中每个有id的View都会生成public final 属性。Binding在View层次结构上做单一的传递,提取有id的Views。这种机制比起某些Views使用findViewById
还要快。
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data class=".VideoLayout"/>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F0F4F6"
tools:context=".HomeFragment">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</layout>
public class VideoLayout extends android.databinding.ViewDataBinding {
@NonNull
public final android.support.v7.widget.RecyclerView recyclerView;
...
}
用法例如:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
VideoLayout binding = DataBindingUtil.inflate(inflater, R.layout.fragment_video, container, false);
mRecyclerView = binding.recyclerView; //代替findViewById
return binding.getRoot();
}
拓展
使用官方提供给的API来进行AdapterView的绑定需要写很多代码,使用起来不方便,但是由于Data Binding Library提供丰富的扩展功能,所以出现了很多第三方的库来扩展它,下面就来介绍一个比较好用的库binding-collection-adapter,Github地址:https://github.com/evant/binding-collection-adapter
-
ViewModel的写法
使用ObservableList,如果有数据变化它会自动更新UI。
public class ViewModel {
public final ObservableList<String> items = new ObservableArrayList<>();
public final ItemBinding<String> itemBinding = ItemBinding.of(BR.item, R.layout.item);
}
如果有多种样式的布局,那么就需要把ItemBinding换成OnItemBind,如下:
public final OnItemBind<String> onItemBind = new OnItemBind<String>() {
@Override
public void onItemBind(ItemBinding itemBinding, int position, String item) {
itemBinding.set(BR.item, position == 0 ? R.layout.item_header : R.layout.item);
}
};
- layout文件
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="com.example.R" />
<import type="me.tatarka.bindingcollectionadapter2.LayoutManagers" />
<variable name="viewModel" type="com.example.ViewModel"/>
</data>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:items="@{viewModel.items}"
app:itemBinding="@{viewModel.itemBinding}"/>
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="@{LayoutManagers.linear()}"
app:items="@{viewModel.items}"
app:itemBinding="@{viewModel.itemBinding}"/>
<android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="match_parent"
app:items="@{viewModel.items}"
app:itemBinding="@{viewModel.itemBinding}"/>
<Spinner
android:layout_width="match_parent"
android:layout_height="match_parent"
app:items="@{viewModel.items}"
app:itemBinding="@{viewModel.itemBinding}"
app:itemDropDownLayout="@{R.layout.item_dropdown}"/>
</layout>
更多用法请参考作者Github:https://github.com/evant/binding-collection-adapter
事件处理
-
无参数
public class EventHandlers {
// EventHandlers 执行click事件
public void click(){
//do
}
}
//xml:
<variable
name="handler"
type="com.xx.xxx.EventHandlers" />
<Button
android:layout_width="match_parent"
android:layout_height="48dp"
android:onClick="@{handler::click}" />
// UI类:绑定handler,如绑定ViewModel那样
EventHandlers handler = new EventHandlers();
binding.setHandler(handler);
或者
//xml:
<Button
android:layout_width="match_parent"
android:layout_height="48dp"
android:onClick="@{() -> viewModel.click()}"
/>
//ViewModel:
public void click(){
}
-
带参数
//xml:
<variable
name="viewModel"
type="com.xx.xxx.ViewModel" />
<variable
name="obj"
type="com.xx.xxx.User" />
<Button
android:layout_width="match_parent"
android:layout_height="48dp"
android:onClick="@{() -> viewModel.click(obj.id)}" />
-
带view
//xml:
<variable
name="viewModel"
type="com.xx.xxx.ViewModel" />
<variable
name="obj"
type="com.xx.xxx.User" />
<Button
android:layout_width="match_parent"
android:layout_height="48dp"
android:onClick="@{(view) -> viewModel.click(obj.id, view)}" />
如果您需要使用带谓词的表达式(例如三元),则可以使用 void
作为符号。
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
-
返回值
如果正在侦听的事件返回值不是 void
,则表达式必须返回相同类型的值。
public class Presenter {
public boolean like(View cb, boolean isChecked){
if (isChecked) {
//do something 取消点赞成功
return false;
} else {
//do something 点赞成功
return true;
}
}
}
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onCheckedChanged="@{(cb, isChecked) -> presenter.like(cb, isChecked)}" />
表达式语言
常用表达式跟Java表达式很像,以下这些是一样的:
-
数学
+
-
/
*
%
-
字符串连接
+
-
逻辑
&&
||
-
二进制
&
|
^
-
一元运算
+
-
!
~
-
移位
>>
>>>
<<
-
比较
==
>
<
>=
<=
-
instanceof
-
分组
()
-
null
-
Cast
-
方法调用
-
数据访问
[]
-
三元运算
?:
Null合并操作
??
- 左边的对象如果它不是null,选择左边的对象;或者如果它是null,选择右边的对象:
android:text="@{user.displayName ?? user.lastName}"
//等同于
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
避免 NullPointerException
Data Binding代码生成时自动检查是否为nulls来避免出现null pointer exceptions错误。例如,在表达式@{user.name}
中,如果user
是null,user.name
会赋予它的默认值(null)。如果你引用user.age
,age是int
类型,那么它的默认值是0。
字符串
android:text='@{map["firstName"]}'
android:text="@{map[`firstName`]}"
资源
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
android:text="@{large? @string/largeText : @string/smallText}"
格式化
<string name="vinctor">vinctor is so bad %1$s!</string>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{@string/vinctor(`boy`)}" />
表达式链
- 重复的表达式
<ImageView android:visibility=“@{user.isAdult ? View.VISIBLE : View.GONE}”/>
<TextView android:visibility=“@{user.isAdult ? View.VISIBLE : View.GONE}”/>
<CheckBox android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
//可以简化为:
<ImageView android:id=“@+id/avatar” android:visibility=“@{user.isAdult ? View.VISIBLE : View.GONE}”/>
<TextView android:visibility=“@{avatar.visibility}”/>
<CheckBox android:visibility="@{avatar.visibility}"/>
- 隐式更新
<CheckBox android:id=”@+id/seeAds“/>
<ImageView android:visibility=“@{seeAds.checked ? View.VISIBLE : View.GONE}”/>
自定义setter
有些xml属性需要自定义它对应的方法。比如android:margin 、android:paddingLeft、android:src等属性并没有相关setter。
BindingAdapter 注解的静态绑定适配器方法,可以为一个xml属性自定义setter方法。
-
paddingLeft
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
-
src
@BindingAdapter({"headImage", "comeFrom"})
public static void headImage(ImageView view, String url, String comeFrom) {
GlideApp.with(view.getContext())
.load(url)
.error(loadCircleImage(view, "2".equals(comeFrom)?
R.mipmap.bluemoonlogo:R.mipmap.laundrymaster_avantar_guest))
.circleCrop().into(view);
}
<ImageView
android:layout_width="28dp"
android:layout_height="28dp"
android:scaleType="fitXY"
app:headImage="@{item.authorInfo.headPicture}"
app:comeFrom="@{item.comeFrom}"/>
双向绑定
双向绑定就是在UI 发生变化,同步更新data中的变量。
使用@= 来进行双向绑定
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={model.name}"/>
自定义双向绑定
(待更新。。。)