Android Data Binding Library(DBL)分享

What`s DBL

Google IO may, 2015,Android M Preview:

  • ASD(Android Support Design)
  • APL(Android Percent Layout)
  • DBL(Data Binding Library)

ASD(Android Support Design)

  • Navigation View 抽屉导航
  • nput Edittext Lable 输入框控件的悬浮标签
  • Floating Action Button 悬浮操作按钮
  • Snackbar 显示在屏幕的底部,包含了文字信息与一个可选的操作按钮,是轻量级的,快速的反馈
  • TabLayout 选项卡,与viewpager一起使用
  • CoordinatorLayout 手势, 以及滚动,Design library中的很多控件都利用了它
  • Collapsing Toolbar 可伸缩折叠的Toolbar

APL(Android Percent Layout)

百分比的layout

What`s DBL

DBL(Data Binding Library),最早是微软提出来的,winphone已经使用多年,这个框架是站在开发者这边的,是开发者更高效系统的开发程序。
原理:遍历contentView得到View数组对象,然后通过数据绑定library生成对应的Binding类,自动的替我们生成了标签里面数据的setter函数。

Typically, the developer will be able to call the subclass's set method directly. For
* example, if there is a variable <code>x</code> in the Binding, a <code>setX</code> method
* will be generated.

DBL 即MVVM框架

Why DBL

无论是MVC MVP MVVM 模式都是为了隔离样板代码

  • DBL 接管Presenter(MVP),个人认为MVP学习成本比较高,而且耦合度高
  • DBL实现view中数据的所有绑定和更新操作
  • The Data Binding Library offers both flexibility and broad compatibility
  • Model-View-View-Model,View和Model的双向绑定
  • 当数据更新,框架收到通知,视图可以自动更新,View和Model可以做到松耦合
  • 减轻Activity和Fragment的压力
  • 减少代码行数,简洁,易读性,findviewbyid,只需遍历一次
  • Butterknife,Ahibernate,Afinal,注解拖慢解析速度,DBL不会
  • 官方说会提高xml的解析速度
  • 可以让我们在不改变既有代码框架的前提下,非常容易地使用这些新特性

Use DBL

  • platform: Android 2.1 (API level 7+)
  • dataBinding{
    enabled = true
    }
Activity & Fragment
private MyTestBinding myTestBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (myTestBinding == null) {
// 这样写
myTestBinding = MyTestBinding.inflate(getLayoutInflater());
// 也可以这样
myTestBinding = DataBindingUtil.setContentView(this, R.layout.my_test_layout);
}
}
/*
* {@code Fragment}
*/
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (myTestBinding == null) {
myTestBinding = MyTestBinding.inflate(inflater, container, false);
}
return myTestBinding.getRoot();
}

简单小栗子

public class MyViewModel {
String appName;
String appCode;
public String getAppName() {
return appName;
}
public void setAppName(String appName) {
this.appName = appName;
}
public String getAppCode() {
return appCode;
}
public void setAppCode(String appCode) {
this.appCode = appCode;
}
public MyViewModel(String appName, String appCode) {
this.appName = appName;
this.appCode = appCode;
}
}
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- model与view进行绑定,data相当于一个通信桥梁,搭建了UI与业务之间的通路-->
<!-- MyTestBinding如果没有自定义命名,则会自动生成以layout命名的binding类-->
<data class="MyTestBinding">
<import type="android.view.View"/>
<!-- MyViewModel viewModel; -->
<variable
name="viewModel"
type="com.baidu.test.MyViewModel"/>
<!-- 也可以这样来写,先导入,再给命名
<import type="com.baidu.test.MyViewModel"/>
<variable name="viewModel" type="MyViewModel" />
-->
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.appName}"
android:textAppearance="@android:style/TextAppearance.Holo.Widget.TextView"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.appCode}"
android:textAppearance="@android:style/TextAppearance.Holo.Widget.TextView"/>
</LinearLayout>
</layout>
*** @{viewModel.appName} == viewModel.appName == viewModel.getAppName() == viewModel.appName() ***
myTestBinding.setViewModel(new MyViewModel("my app name", "8.0"));

如何编写xml

想怎么写就怎么写~

支持的表达式

表达式
Mathematical + - / * %                    算术运算
String concatenation +                字符串连接
Logical && ||                        逻辑运算
Binary & | ^                        二进制
Unary + - ! ~                        一元运算
Shift >> >>> <<                    位运算
Comparison == > < >= <=                关系运算符
instanceof                        类型比较
Grouping ()                        组
Literals - character, String, numeric, null         复数常量
Cast                            类型转换
Method calls                        方法调用
Field access                        字段访问
Array access []                        数组存取
Ternary operator ?:                    三元运算

如何赋值

当然是在java代码里面赋值啦 ~

myTestBinding.setViewModel(new MyViewModel("appname", "8.0"));
myTestBinding.setIsOK(true);
myTestBinding.setIndex(0);
myTestBinding.setName("name");
myTestBinding.setList(new ArrayList<String>() {
{
add("a string");
}
});
myTestBinding.setMyMap(new HashMap<String, String>(){
{
put("key","value");
}
});
// 到这里,是不是发现比findviewById好写多了,有木有!

绑定事件

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{viewModel.onNameViewClick}"
android:text="@{viewModel.appName}"/>
public class MyViewModel {
public void onNameViewClick(View nameView) {
Log.e(TAG, "onNameViewClick");
}

动态赋值实例1:

那么,到现在为止,绑定,编写,赋值,事件,已经Ok了,但是还远远不能满足我们的需求,我们的业务逻辑往往是复杂的。
庆幸的是DataBinding也支持内容观察者,observe,而且使用起来也相当方便。

分3步:

  • 在getter()方法上添加注解:@Bindable
  • Bindable注解会自动生成一个entry类:BR (AS1.5不自动生成,AS2.2自动生成,不知原因)
  • 在setter方法里,通知UI更新数据:notifyPropertyChanged(BR.appName)
public class MyViewModel extends BaseObservable {
public void onNameViewClick(View nameView) {
Log.e(TAG, "onNameViewClick");
setAppName("change name");
}
@Bindable
public String getAppName() {
return appName;
}
public void setAppName(String appName) {
this.appName = appName;
notifyPropertyChanged(BR.appName);
}

动态赋值实例2

其实使用BaseObservable已经基本够用,但是Google还给我们封装了一系列的数据结构Observable Collections,使用起来也更爽一些。主要使用get()和set()取存。

BaseObservable Subclasses
ObservableBoolean, ObservableByte, ObservableChar, ObservableDouble, ObservableField<T>, ObservableFloat, ObservableInt, ObservableLong,ObservableShort, ObservableArrayMap,ObservableArrayList

动态赋值实例3

ObservableFields的使用方法就更加简单了,例如下面代码:

// layout的写法不变:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{viewModel.onNameViewClick}"
android:text="@{viewModel.appName}"/>
// java 代码就更简单了,因为我们已经不需要getter()和setter()了,有木有~
public class MyViewModel {
public ObservableField<String> appName = new ObservableField<>();
public void onNameViewClick(View nameView) {
appName.set("appname");
appName.get();
}
}

动态赋值实例4

addOnPropertyChangedCallback

dashboard.isMarking.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(Observable observable, int i) {
if (dashboard.willShowCollectMark.get()
&& !dashboard.isMarking.get()
&& DevController.getInstance().isBatchTrackTesting()) {
onClickMark(marker);
}
}
});

使用view ID

<Button
android:id="@+id/btn_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{viewModel.onNameViewClick}"
android:text="@{viewModel.appName}"/>
// 其实myTestBinding.btnTest就是View#Button
myTestBinding.btnTest.setText("its a view");

ViewStubProxy

ViewStub , View的嵌套,因为viewstub与其他view不同,viewstub一开始是不可见的,实际上是不存在与view的结构中,所以要监听OnInflateListener,来设置binding。

<ViewStub
android:id="@+id/view_stub"
android:layout="@layout/view_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
myTestBinding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
ViewStubBinding viewStubBinding = DataBindingUtil.bind(inflated);
viewStubBinding.setViewModel(xxx);
}
});

Include

<?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>

注意:name.xml和contact.xml里面必须声明User,另外,不支持merge

ListView

可以看到代码里,没有viewHolder的身影了,我们再也不用写一堆代码了。

listAdapter = new ArrayAdapter<BrtaTestResult>(getActivity(),
R.layout.dev_item_brta_test_result, mockLocation.testResults) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
BrtaTestResultBinding binding;
if (convertView == null) {
binding = BrtaTestResultBinding.inflate(LayoutInflater.from(parent.getContext()),
parent, false);
binding.getRoot().setTag(binding);
} else {
binding = (BrtaTestResultBinding) convertView.getTag();
}
binding.setIndex(position);
binding.setItemViewModel(getItem(position));
binding.setListViewModel(mockLocation);
binding.test.setTag(binding);
return binding.getRoot();
}
};
}
ListView listView = testBinding.listView;
listView.setDividerHeight(4);
listView.setAdapter(listAdapter);

RecyclerView

private static class MyHolder extends RecyclerView.ViewHolder {
private MyItemBinding mBinding;
private MyHolder(MyItemBinding binding) { // 构造方法,传入binding
super(binding.getRoot());
mBinding = binding;
}
static MyHolder create(LayoutInflater inflater, ViewGroup parent) {
MyItemBinding binding = MyItemBinding.inflate(inflater, parent, false);
return new MyHolder(binding); // 使用binding inflate item 布局
}
void bindTo(User user) { // 传入model
mBinding.setUser(user);
mBinding.executePendingBindings();
}
}
public MyHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
return MyHolder.create(inflater, viewGroup); // 得到holder
}
public void onBindViewHolder(MyHolder myHolder, int position) {
myHolder.bindTo(userList.get(position)); // 获取model
}

Noting is perfect

我们的业务逻辑各种各样,也比较复杂

  • 在xml文件里写表达式的方式,对于view的测试是有些困难的,所以我们可以把一些逻辑写入model类
  • 对于一些复杂的view,显得比较笨重
  • 找不到BR文件,clean,你懂得
  • Android Studio may say: annot resolve symbol , 有些符号还不是支持的很好BR,但是不妨碍编译
  • Android Studio 提示找不到binder,绝大多数是xml写的有问题,没有generate成功object
  • xml里面的变量要全路径书写:java.util.List not List
  • 临时更改xml的时候,编译器会报一些编译的错误,忽略之
  • text不要写入int类型,String.valueOf 很有必要
  • 有的时间BR要写全路径
  • 如果viewmodel中包含{import android.content.Context;} , 那么说明你用错了,内存泄露
  • 出现bug很难定位问题所在,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题

不管mvc,mvp,mvvm都只是个工具,敢于拥抱新的技术,但也不能盲从,如phonegap,jQuerymobile,然并卵

倒计时Demo

/**
* </p>
* Created by liutao18 on 2016/9/30.
*/
public class CountdownViewModel extends BaseObservable {
private static final String TAG = "CountdownViewModel";
private static CountdownViewModel instance;
private CountDownTimer countDownTimer;
private long timeLeftMillis;
private boolean isRunning;
public static CountdownViewModel me() {
if (instance == null) {
instance = new CountdownViewModel();
}
return instance;
}
@Bindable
public long getTimeLeftMillis() {
return timeLeftMillis;
}
public void setTimeLeftMillis(long timeLeftMillis) {
timeLeftMillis = timeLeftMillis;
notifyPropertyChanged((int) timeLeftMillis);
}
public void startTimer(long timeLeftMillis) {
if (!isRunning) {
isRunning = true;
countDownTimer = new CountDownTimer(timeLeftMillis, 1000) {
@Override
public void onTick(long millisUntilFinished) {
setTimeLeftMillis(millisUntilFinished / 1000);
}
@Override
public void onFinish() {
isRunning = false;
setTimeLeftMillis(0);
}
};
countDownTimer.start();
} else {
Log.i(TAG, "Timer already started, call restart first");
}
}
/**
* 取消倒计时
*/
public void cancelTimer() {
if (isRunning) {
countDownTimer.cancel();
isRunning = false;
}
}
/**
* 重启倒计时
*/
public void restartTimer() {
cancelTimer();
startTimer(60000);
}
}
public class MyTestClassActivity extends Activity {
private CountdownViewBinding binder;
private CountdownViewModel countdownViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (binder == null) {
CountdownViewBinding.inflate(getLayoutInflater());
}
countdownViewModel = CountdownViewModel.me();
binder.setViewModel(countdownViewModel);
binder.btnTest.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
countdownViewModel.startTimer(60 * 1000);
// countdownViewModel.cancelTimer();
// countdownViewModel.restartTimer();
// countdownViewModel.getTimeLeftMillis();
}
});
}
}
<data class="CountdownViewBinding">
<import type="com.baidu.test.CountdownViewModel"/>
<variable
name="viewModel"
type="CountdownViewModel"/>
</data>