android data binding实践之:ViewDataBinding
使用过data binding的应该都知道,在编译之后,这个库会为我们生成两种java文件。
BR.java:
这个文件主要是给我们在XML文件中每个标签设置的variable以及被Bindable注解的属性添加一个静态的int类型的索引,在以后对这些variable进行操作的时候都可以使用这个索引id作为参数。
ViewDataBinding的相应的子类:
每一个绑定的XML布局文件都会生成一个以该布局文件名称作为前缀(默认情况下)的ViewDataBinding的相应的子类。在这个文件中主要的处理逻辑主要包括:
- 自动化view管理
- 用户事件监听处理
- 变量管理
- data binding逻辑流
自动化view管理
当我们在XML布局文件中,设置了View的”android:id”属性之后,在生成的ViewDataBinding子类当中会生成一个声明为 public final 的属性值,属性的名称就是我们定义的id的值。而对于那些没有被设置ID的view则会声明为 private final 。因此当我们给view设置了id之后可以不必再自己声明和初始化这些view。
首先XML布局声明如下
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="net.uni_unity.databindingdemo.model.ObserVableUser"/>
<variable
name="activity"
type="net.uni_unity.databindingdemo.SecondActivity"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="@{user.firstName}"/>
...
<Button
android:onClick='@{()->activity.onViewClick("click")}'
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="ListenerBinding"/>
</LinearLayout>
</layout>
在生成的ViewDataBinding子类中,我们会发现:
// views
public final android.widget.Button btn1;
public final android.widget.Button btn2;
private final android.widget.LinearLayout mboundView0;
private final android.widget.TextView mboundView1;
private final android.widget.TextView mboundView2;
private final android.widget.TextView mboundView3;
private final android.widget.TextView mboundView4;
public SecondActivityBinding(android.databinding.DataBindingComponent bindingComponent, View root) {
...
this.btn1 = (android.widget.Button) bindings[5];
this.btn1.setTag(null);
this.btn2 = (android.widget.Button) bindings[6];
this.btn2.setTag(null);
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(null);
this.mboundView1 = (android.widget.TextView) bindings[1];
this.mboundView1.setTag(null);
this.mboundView2 = (android.widget.TextView) bindings[2];
this.mboundView2.setTag(null);
this.mboundView3 = (android.widget.TextView) bindings[3];
this.mboundView3.setTag(null);
this.mboundView4 = (android.widget.TextView) bindings[4];
this.mboundView4.setTag(null);
...
}
这样我们就可以直接在activity当中使用这些声明为public的view了,只要为每个view添加上id之后,连一大堆的”findViewById()”的方法都不用我们去写了。
事件处理(Event Handling)
data binding可以让我们通过定义一些表达式处理从view分发的事件,比如点击事件之类的。事件的属性名字基本由java的listener方法的名字控制(当然也有其他复杂的事件例外)。比如View.OnLongClickListener 拥有一个 onLongClick() 的方法,所以这个事件对应的属性名称就是 android:onLongClick。我们有下面两种方式处理对应的事件。
Method References
使用示例
首先定义我们的事件处理方法
public class SecondActivity extends AppCompatActivity {
...
private void bindData(SecondActivityBinding binding){
ObserVableUser user=new ObserVableUser("firstName","lastName","observable",11);
binding.setUser(user);
binding.setPresenter(this);
}
public void simpleClick(View view){
Log.d("MethodReference","MethodReference click");
}
}
然后是在我们的布局文件中通过调用表达式进行绑定
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="presenter"
type="net.uni_unity.databindingdemo.SecondActivity"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:onClick="@{presenter::simpleClick}"
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="MethodReference"/>
</LinearLayout>
</layout>
使用解析
关于Method References的详细介绍,我们可以参考官网当中的描述Method References。这里简单总结一下关键的内容:
- 与在XML文件中定义属性“android:onClick”,然后在我们对应的activity中定义onClick()方法类似,我们可以通过自定义一个方法,然后在XML中绑定该方法的调用表达式,完成事件的绑定。但是不同的是,我们的表达式是在编译期就被处理的,所以一旦我们表达式调用的方法不存在的话或者方法签名不正确的话就会出现编译异常
非常关键的一点,我们 自己定义的方法的方法签名必须与实际的listener回调方法的签名保持一致 (即方法的参数类型与个数要一致),否则我们将会在编译期得到这样的编译错误
java.lang.RuntimeException: Found data binding errors.
*/ data binding error **msg:Listener class android.view.View.OnClickListener with method onClick did not match signature of any method presenter::simpleClick file:/DataBindingDemo/app/src/main/res/layout/second_activity.xml loc:42:31 - 42:52 *\ data binding errorXML文件中绑定的表达式是在编译期就被处理掉,如果我们希望在事件响应的时候再进行处理,就可以使用下面的 Listener Bindings 的方法
ViewDataBinding当中的实现
当我们使用 Method References 处理事件的时候,data binding library回在 ViewDataBinding 生成下面的处理代码
// Listener Stub Implementations
public static class OnClickListenerImpl implements android.view.View.OnClickListener{
private net.uni_unity.databindingdemo.SecondActivity value;
public OnClickListenerImpl setValue(net.uni_unity.databindingdemo.SecondActivity value) {
this.value = value;
return value == null ? null : this;
}
@Override
public void onClick(android.view.View arg0) {
this.value.simpleClick(arg0);
}
}
上面的代码可以发现,在 ViewDataBinding 中生成了一个实现了 android.view.View.OnClickListener 事件接口的静态内部类,而这个类主要做了两件事情
- 添加了一个 setValue() 的方法。方法的参数其实就是我们自己定义的事件回调方法所在的类的实例。在上面的实例中,我们定义了一个 simpleClick() ,因为我把方法定义在了在activity里面,所以对应的就是activity的实例。)
- 实现了 onClick() 的方法。而方法的实现里面其实调用的就是我们自己定义的那个回调方法。
而我们会在生成的ViewDataBinding的子类中看到一个 OnClickListenerImpl mAndroidViewViewOnCl 的filed的声明,而这个对象的初始化就放在了binding调用链的最后里面。如下所示:
@Override
protected void executeBindings() {
...
if ((dirtyFlags & 0x28L) != 0) {
if (activity != null) {
// read activity::simpleClick
androidViewViewOnCli = (((mAndroidViewViewOnCl == null) ? (mAndroidViewViewOnCl = new OnClickListenerImpl()) : mAndroidViewViewOnCl).setValue(activity));
}
}
...
}
从上面的代码中我们可以看出,使用 Method References 的方式处理回调事件,其实就是对我们平常一直使用的 OnClickListener 的一层简单的包装而已。
Listener Bindings
通过使用 Listener Bindings 的方式,我们可以在事件回调的时候再触发我们所绑定的 lambda 表达式。它的使用方法与 Method References 相似,但是这种方式却可以使我们具备调用已经被编译成了字节码的表达式的能力。
值得注意的是,这个特性只有在Gradle2.0及以后的版本当中才被支持。
使用示例
首先定义我们的事件处理方法
public void onViewClick(String tag){
Log.d("ListenerBinding","ListenerBinding "+tag);
}
然后是在我们的布局文件中通过lambda表达式进行绑定
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="presenter"
type="net.uni_unity.databindingdemo.SecondActivity"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:onClick='@{()->presenter.onViewClick("click")}'
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="ListenerBinding"/>
</LinearLayout>
</layout>
使用解析
关于Listener Bindings的详细介绍,我们可以参考官网当中的描述Listener Bindings.这里还是总结一下使用的关键点:
- 在上面的 Method References 中我们提及到,自定义的方法签名必须与listener的回调方法的签名保持一致。但是 Listener Bindings 却没有这样的要求。它只要求 自定义的方法的返回值与listener的回调方法的返回值保持一致 即可。
- 在定义的 lambda 表达式中,我们可以选择是否传入view参数,而且参数的名称也是可以自己随意定义的。
ViewDataBinding当中的实现
Listener Bindings 在ViewDataBinding当中的实现要比 Method References 稍微复杂一点。
首先,也是对 android.view.View.OnClickListener 进行了一层包裹,生成了一个实现了点击事件监听接口的类
public final class OnClickListener implements android.view.View.OnClickListener {
final Listener mListener;
final int mSourceId;
public OnClickListener(Listener listener, int sourceId) {
mListener = listener;
mSourceId = sourceId;
}
@Override
public void onClick(android.view.View callbackArg_0) {
mListener._internalCallbackOnClick(mSourceId , callbackArg_0);
}
public interface Listener {
void _internalCallbackOnClick(int sourceId , android.view.View callbackArg_0);
}
}
这个类里面关键的点就是内部又定义了一个Listener的接口,并且点击事件的最终调用的就是接口里面的 _internalCallbackOnClick() 方法。那么根据上面的 Method References 的实现套路,我们大概能猜到我们的 ViewDataBinding 应该实现了上面的接口。