构建环境
使用条件
- 支持 Android 2.1 (API level 7+).
- Android Plugin for Gradle 1.5.0-alpha1 或更高
buildscript { ... dependencies { classpath 'com.android.tools.build:gradle:1.5.0-alpha1' } }
开启
android { .... dataBinding { enabled = true } }
基本用法 (Data Binding Layout Files)
绑定实体数据到指定布局(Layout)
public class User { private final String firstName; private final String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return this.firstName; } public String getLastName() { return this.lastName; } }
如果我需要用Layout展示用户信息 User,通常我们的做法就是
- 新建
Layout.xml
; - 找到对应控件,findIdByView或 Butterknife ;
- 设置数据;
感觉用Butterknife已经很高大上,很高效了,看一下Data Binding的效率:
main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:id="@+id/mFirstName"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
</layout>
MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
User user = new User("Test", "User");
binding.setUser(user);
}
高效没看出来呀!为了让接手的下一个兄弟不骂娘,我还是选择普通写法o(╯□╰)o,那如果是这种布局呢
你用几百行的代码,DataBinding一行搞定,Boss此刻应该蹲在厕所喜极而泣,我XX我早该找一个这样的程序员。
小知识点
- Fragment ,ListView,RecyclerView 中的使用
// ListView RecyclerView ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false); //Fragment MainFragmentBinding binding = DataBindingUtil.inflate(inflater, R.layout.main_fragment, container, false);
- 如果想操作对应的控件需要设置id号,如main_activity.xml, 设置
android:id="@+id/mFirstName"
,可以直接用binding.mFirstName
找到控件 ;
【待处理】
测试中得到binding
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
事件绑定(Event Handling)
最简单的应用
main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.MyHandlers"/>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
android:onClick="@{handlers::onClickFriend}"/>
</LinearLayout>
</layout>
事件处理类
public class/*也可以是接口*/ MyHandlers {
public void onClickFriend(View view) { ... }
}
控制代码同上,设置变量就可以binding.setHandlers(...)
带参数的事件绑定(Android Plugin for Gradle 2.0 或更高)
main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.MyHandlers"/>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
android:onClick="@{() -> handlers.onSaveClick(user)}"/>
</LinearLayout>
</layout>
事件处理类
public class MyHandlers {
public void onSaveClick(User user){}
}
传入View方式
android:onClick="@{(theView) -> handlers.onSaveClick(theView, user)}"
或
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onCheckedChanged="@{(cb, isChecked) -> handlers.completeChanged(user, isChecked)}" />
注意:避免一些比较复杂的listeners,这样会使得Layout更易读更容易维护
Data Layout 小细节及简单语法
Imports
<data>
<import type="android.text.TextUtils"/> <!--静态类的使用-->
<import type="android.view.View"/> <!--导入-->
<variable name="note" type="String"/><!--自动导入java.lang.*-->
<import type="com.example.real.estate.View"
alias="Vista"/> <!--别名解决同名类-->
</data>
<TextView
android:text="@{TextUtils.isEmpty(user.lastName)? "default":user.lastName }"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/> <!-【使用】-->
Note: 默认自动导入java.lang.*,常用的有Integer,String,StringBuffer,Math类
Binding Class Names 如上:MainActivityBinding 类
- 默认命名规则
包名+布局名去‘_’首字母大写+Binding
如: main_activity.xml ==> MainActivityBinding.class - 自定义的三种解释
<data class="ContactItem"></data> <!--自定义名称--> <data class=".ContactItem"></data> <!--使用module包名--> <data class="com.example.ContactItem"></data> <!--指定包名-->
Includes
Data layout 复用<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:bind="http://schemas.android.com/apk/res-auto"> <data> <variable name="user" type="com.example.User"/> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <include layout="@layout/name" bind:user="@{user}"/> <include layout="@layout/contact" bind:user="@{user}"/> </LinearLayout> </layout>
简单语法
Java共同特征
- Mathematical + - / * %
- String concatenation +
- Logical && ||
- Binary & | ^
- Unary + - ! ~
- Shift >> >>> <<
- Comparison == > < >= <=
- instanceof
- Grouping ()
- Literals - character, String, numeric, null
- Cast
- Method calls
- Field access
- Array access []
- Ternary operator ?:
Examples:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
android:text="@{user.displayName ?? user.lastName}" 【重点】<==> 等价于
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
Resources引用
Type | Normal Reference | Expression Reference |
---|---|---|
String[] | @array | @stringArray |
int[] | @array | @intArray |
TypedArray | @array | @typedArray |
Animator | @animator | @animator |
StateListAnimator | @animator | @stateListAnimator |
color int | @color | @color |
ColorStateList | @color | @colorStateList |
Sample
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
String formatting
<resources>
<string name="greeting">Hello, %s</string>
</resources>
<TextView
android:text="@{@string/greeting(user.firstName)}"/>
Math in expressions
<TextView
android:padding="@dimen/padding"
android:padding="@{@dimen/padding}"
android:padding="@{@dimen/padding * 2}"
android:padding="@{@dimen/padding + @dimen/padding}"
android:padding="@{largeScreen ? @dimen/padding * 2 : @dimen/padding}"
/>
更改数据自动更新UI界面 (Data Objects)
经过上的学习,有木有发现, 布局Layout中的数据variables是固定的,只有初始化的时候赋值 数据变动时(设置数据,得到数据),还需要找控件,设置参数,更改UI状态, 基本还是老套路,官方的解决方案是Observable,使用观察者模式,当数据变化时通知layout自动变化,
官方提供的类:
- Observable Objects ;
- ObservableFields ;
- Observable Collections;
Observable Objects
public static class User extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return this.firstName;
}
@Bindable
public String getLastName() {
return this.lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
经过改造后的User,同上的初始化
private User user ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
user = new User("Test", "User");
binding.setUser(user);
//点击LastName触发
binding.setHandlers(new MyHandlers() {
@Override public void onClickFriend(View view) {
user.setFirstName("FirstName"); //布局自动更新为`FirstName`
//此后常用的操作
//布局中Progress android:visibility="@{TextUtil.isEmpty(user.firstName) ? View.GONE : View.VISIBLE}"
// 自动控制对应的Progress是否显示,或进行一些简单的逻辑处理
}
});
}
ObservableFields
private static class User {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
// 对应的set/get
user.firstName.set("Google");
int age = user.age.get();
Observable Collections
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
高阶用法
在RecyclerView中的使用 (Dynamic Variables)
BindingHolder.java
public static class BindingHolder extends RecyclerView.ViewHolder{
//加入 ViewDataBinding setter/getter 方法
private ViewDataBinding binding ;
public ViewDataBinding getBinding() {
return binding;
}
public void setBinding(ViewDataBinding binding) {
this.binding = binding;
}
public BindingHolder(View itemView) {
super(itemView);
}
}
MyAdapter.java
private class MyAdapter extends RecyclerView.Adapter<BindingHolder>{
private List<User> users;
public MyAdapter(List<User> users) {
this.users = users;
}
@Override public BindingHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
//核心代码
ViewDataBinding binding = DataBindingUtil.inflate(
LayoutInflater.from(viewGroup.getContext()),
R.layout.list_item,
viewGroup,
false);
BindingHolder holder = new BindingHolder(binding.getRoot());
holder.setBinding(binding);
return holder;
}
@Override public void onBindViewHolder(BindingHolder holder, int position) {
User user = users.get(position);
holder.getBinding().setVariable(BR.user, user);
//不等待下一帧(Frame),直接更新,这个方法必须运行在UI线程
holder.getBinding().executePendingBindings();
}
@Override public int getItemCount() {
return users.size();
}
}
更简洁的用法
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserHolder> {
private static final int USER_COUNT = 10;
@NonNull
private List<User> mUsers;
public UserAdapter() {
mUsers = new ArrayList<>(10);
for (int i = 0; i < USER_COUNT; i ++) {
User user = new User(RandomNames.nextFirstName(), RandomNames.nextLastName());
mUsers.add(user);
}
}
public static class UserHolder extends RecyclerView.ViewHolder {
private UserItemBinding mBinding;
public UserHolder(View itemView) {
super(itemView);
mBinding = DataBindingUtil.bind(itemView);
}
public void bind(@NonNull User user) {
mBinding.setUser(user);
}
}
@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));
}
@Override
public int getItemCount() {
return mUsers.size();
}
}
ViewStubs使用
ViewStub 是不可见的,0大小常用于惰性加载,说白了就是占给位置,在父容器的inflate时候不用处理,当在ViewStub setVisibility(int) or inflate()才会加载到布局中,应用场景:错误提示,帮助提示,用户引导等;
用法:
<ViewStub android:id="@+id/stub"
android:inflatedId="@+id/subTree"
android:layout="@layout/mySubTree"
android:layout_width="120dip"
android:layout_height="40dip" />
ViewStub stub = (ViewStub) findViewById(R.id.stub);
View inflated = stub.inflate();
Data layout
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
...>
<!--需要增加ID-->
<ViewStub
android:id="@+id/view_stub"
android:layout="@layout/view_stub"
... />
</LinearLayout>
</layout>
监听ViewStub inflate 事件
binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
ViewStubBinding binding = DataBindingUtil.bind(inflated);
User user = new User("fee", "lang");
binding.setUser(user);
}
});
自定义Setter
实用小例子:
设置url,控件直接加载url地址的图片
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="url" format="string"/>
</resources>
在任意代码中加入如下:
@BindingAdapter("url")
public static void loadImage(final ImageView imageView, final String url){
Glide.with(imageView.getContext().getApplicationContext())
.load(url)
.into(imageView);
}
能得到变化的url及对应的ImageView控件
特殊说明: MasteringAndroidDataBinding中是用
declare-styleable
加自定义控件的方法来处理数据变化,上面的例子最能达到实践效果,在原生控件中加入自定义是属性,简洁易用;
BindingAdapter进价用法
观察多个属性 如: @BindingAdapter(value = {"url","drawable"},requireAll = false)
requireAll指 布局控件中是否需要"url"``"drawable"
属性都存在,默认为true;
监听原生控件的属性变化,做响应的处理(重写),如:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
获取变化的旧数据和新数据,自义定属性也能使用
@BindingAdapter("android:paddingLeft") //注意:android:paddingLeft 可以改为自定义的Setter属性,如上‘url’
public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
if (oldPadding != newPadding) {
view.setPadding(newPadding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
}
总结: Data Binding 核心思想是把控件的属性用静态变量代表,并监听属性的变化,通过BindingAdapter来处理数据变化;如果不是很有感觉, 有兴趣进一步研究,建议看一下MVVMLight源码, 一个Data Binding 和RxJava 结合使用的库
单元测试【待实践】
学习资料
实在的博客
工作原理
- Android Data Binding从抵触到爱不释手 比较详细,特别是有源码分析
开源库学习资料
- MVVMLight源码, 一个Data Binding 和RxJava 结合使用的库
- TODO-DataBinding 和MVP结合使用,Google 官方例子
补充 View Binding
使用条件 Android Studio 3.6 +
buildscript {
……
dependencies {
classpath "com.android.tools.build:gradle:3.6.0-beta01"
}
}
激活配置
android {
buildFeatures {
viewBinding = true
}
}
Activity 使用
布局 : activity_main.xml
使用 : 命名根据布局名称 + Binding
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding inflate = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(inflate.getRoot());
}
Fragment 使用
布局 : fragement_main
使用: 命名同上
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
FragementMainBinding inflate = FragementMainBinding.inflate(inflater, container, false);
inflate.tv.setText("Holy G00gie");
return inflate.getRoot();
}
附录
Resource 引用对照表
Type | Normal Reference | Expression Reference |
---|---|---|
String[] | @array | @stringArray |
int[] | @array | @intArray |
TypedArray | @array | @typedArray |
Animator | @animator | @animator |
StateListAnimator | @animator | @stateListAnimator |
color int | @color | @color |
ColorStateList | @color | @colorStateList |
### 常用转义表 | ||
```shell | ||
显示结果 描述 转义字符 十进制 | ||
空格 | ||
< 小于号 < < | ||
> 大于号 > > | ||
& 与号 & & | ||
“ 引号 " " | ||
‘ 撇号 ' ' | ||
× 乘号 × × | ||
÷ 除号 ÷ ÷ | ||
``` |