Android--Data Binding Library

引言

原文地址:https://developer.android.com/topic/libraries/data-binding/index.html#generated_binding

其实我最开始听说的butterknife,databinding根本没听过。。
翻官方文档才知道有这么个东西,那肯定要问了,用它干嘛

这里写图片描述

好了,这篇已经有人翻译了,然而我看了下还是不全。。
参考:
(译)Data Binding 指南(有部分自己没看明白的,这里翻的很通畅)
https://github.com/LyndonChin/MasteringAndroidDataBinding(配合demo说明,感觉有些简洁)
棉花糖给 Android 带来的 Data Bindings(数据绑定库)(有几处没有详细示例,自己调用失败的,这里参考了一下)

Data Binding 库

绑定layout和code,减少代码量
支持Android2.1以上
需求Android Plugin for Gradle 1.5.0-alpha1 or higher
需求AS 1.3以上

构建环境

module的gradle.build文件下:

android {
    ....
    dataBinding {
        enabled = true
    }
}

常规用法:bean和layout绑定

Layout配置

比起常规配置,加了个layout和data标签

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

纵观一下,不难明白,data标签的name对应的值是下面view引用的实例名;type对应的类,是下面view引用的实例类。

数据类

POJO就行,结构如下:

public class User {
   public final String firstName;
   public final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
}

注意要final的。你也可以设置成JavaBean的样式,就是域改成private,增加setter\getter。

代码中使用

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   //这个类名根据avtivity对应的layout名,加个binding自动生成
   //比如此处 main_activity =》 MainActivityBinding
   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
   //你也可以这么获取 
   // binding = MainActivityBinding.inflate(getLayoutInflater());
   // setContentView(binding.getRoot());
   User user = new User("Test", "User");
   binding.setUser(user);
}

但是使用列表的时候你会发现,需要在Adapter中用:

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup,
false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

adapter实际使用代码参考:

            @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        ItemMainBinding binding;
        if(convertView == null) {
            binding = DataBindingUtil.inflate(
                    LayoutInflater.from(context),
                    R.layout.item_main, parent, false);
            convertView = binding.getRoot();
        } else {
            binding = (ItemMainBinding) convertView.getTag();
        }

        binding.setItem(this.getItem(position));
        convertView.setTag(binding);
        return convertView;
    }

处理事件

和基本的绑定(在android:txt中绑定值)类似,而进行事件的绑定时,属性要根据事件的类型进行匹配。比如点击的要在onClick中,长按的在onLongClick。
有如下面两节所示的两种方式:

方法引用:

类似于在onClick属性中写方法名的方式,在编译时就能判断是否正确。
监听的实现是在数据绑定的时候,而不是事件触发的时候。

处理类

public class MyHandlers {
    //参数需要是事件监听器的参数,此处为view
    public void onClickFriend(View view) { ... }
}

layout

<variable name="handlers" type="com.example.Handlers"/>
...
<View
...
android:onClick="@{handlers::onClickFriend}"/>

Main

然后你会发现并没有反应,因为你需要绑定。。我是参考

//你可以直接把onClickFriend写在mainActivity里,这样setHandlers(this)就好了
//好了问题来了,那这个方法的存在意义是什么,最最原生的onClick不就是这样的吗,还简单很多
MyHandlers handlers = new MyHandlers();
binding.setHandlers(handlers);

###Listener绑定:
监听的实现是在触发的时候。
需求:Android Gradle Plugin for Gradle version 2.0 and later.
####处理类

public class Presenter {
    public void onSaveClick(Task task){}
}

layout

<variable name="task" type="com.android.example.Task" />
<variable name="presenter" type="com.android.example.Presenter" />
...
<View
...
<!--lambda形式,view参数被忽略了-->
android:onClick="@{() -> presenter.onSaveClick(task)}" 

带上View参数

//此时view参数被带上,但是没有用它
android:onClick="@{(view) -> presenter.onSaveClick(task)}"
或者
public class Presenter {
    public void onSaveClick(View view, Task task){}
}
//如果你要用他,肯定是要带上参数名(名字可以被更改)
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"

带上其他参数
如果有需要,你可以遵循lambda,设置多个参数:

//这里有on,下面调用的时候没有,我测试的时候用的一样的名字,可行。
public class Presenter {
    public void onCompletedChanged(Task task, boolean completed){}
}
//没有onCheckedChanged属性,但是这里就是用这个值
//cb表示的是view参数,isChecked是CheckBox的属性,两个作为参数传递
  <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />

根据实际event设置返回值

//比如onLongClick是要返回true或false表示事件消费的,那么presenter里的方法也是boolean返回
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
public class Presenter {
    public boolean onLongClick(View view, Task task){}
}

三元语法判断是否返回:
重看

//这里的void表示不作什么
//话说view没有这个isvisible的属性啊???不过换了个属性也不好使
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"

注意事项

尽量保持listener的简洁,因为他的初衷就是使代码易于维护(易读、易修改)。所以对于一些逻辑操作请放在外部调用的地方处理。

避免冲突

类名 监听器setter 属性
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

Layout(标签)细则

Imports

在data中导入类,使用方式像java

    <data>
    <import type="android.view.View"/>
    <!--类名冲突,别名-->
    <import type="com.example.real.estate.View"
        alias="Vista"/>

    <import type="com.example.User"/>
    <variable name="user" type="User"/>
    </data>
    ...
    android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"
<!--提示:AS还不能很好的自动处理variable导入,如上面的user
    但是我使用的时候没出现问题
    user.connection是为了解决这个问题,我尝试了一下反倒有问题。。-->
<TextView
   android:text="@{((User)(user.connection)).lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<!--静态方法调用-->
<data>
    <import type="com.example.MyStringUtils"/>
    <variable name="user" type="com.example.User"/>
</data><TextView
   android:text="@{MyStringUtils.capitalize(user.lastName)}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Variables

上面的使用之后,variables的大意已经了然于胸了。
- 变量类型会在编译时被检查

所以如果变量声明了 Observable 接口或者是一个observable collection,那它会被反射使用。如果变量是一个没有实现 Observable* 接口的基类或接口,变量的变动不会被观测到(即不会引起 UI 的变化)!
双向支持的内容请查下下文 数据对象

  • 不同layout(比如横向、纵向)的变量会合并在一起,如果命名重复会有冲突。
  • 定义variable之后,binding会自动生成setter\getter方法。在未被setter之前,变量值的初始化和java类似: 引用类型为null ,int为0 , 布尔为false,etc..
  • binding 类也会生一个名为 context 的特殊变量,这个变量可以被用于表达式中。context 变量其实就是 rootView 的 getContext() 的返回值。context 变量会被同名的显式变量覆盖。

自定义Binding类名

如果 module 包名为 com.example.my.app,binding 类默认会被放在 com.example.my.app.databinding 中

<!--重命名-->
<data class="ContactItem">
    ...
</data>
<!--包前缀改到module包下,即没有databinding,需要“.”前缀-->
.ContactItem
<!--任意包名-->
com.example.ContactItem

Includes

当要包含一个布局的时候,使用bind:user=”@{user}”的形式,会自动添加命名空间。

<include layout="@layout/name"
           bind:user="@{user}"/>

注意:

  • name.xml中也要声明user variable。。
  • merge的直接子布局不能使用

表达式语言

常规元素

查看原文或者参考文章即可。。

缺失操作

this、super、new、明确的泛型调用

Null Coalescing Operator

<!--双问号-->
android:text="@{user.displayName ?? user.lastName}"
<!--等价于-->
android:text="@{user.displayName != null ? user.displayName : user.lastName}"

属性引用

你已经用了千百遍了。(user.name)

杜绝空指针

自动处理空指针,即引用为空则为null, int则为0 , etc…

容器类

    <!--列表-->
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String&gt;"/>
    <variable name="sparse" type="SparseArray&lt;String&gt;"/>
    <variable name="map" type="Map&lt;String, String&gt;"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>

    android:text="@{list[index]}"
    …
    android:text="@{sparse[index]}"
    …
    android:text="@{map[key]}"

字符串

直接量
外围用单引号,内部用双引号就行了
如果外围用双引号,内部用1键边上的 ` (文档说单引号也可以,但是我这不行)
字符串的拼接
字符串拼接:’@{“image_” + id}’

资源引用

重看,plurals

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
<!--字符串-->
<!--可以设置参数,即StringFormat的感觉,在nameFormat中添加对应s%-->
android:text="@{@string/nameFormat(firstName, lastName)}"
<!--plurals,还没用过。。尴尬-->
android:text="@{@plurals/banana(bananaCount)}"

需求类型说明

常规引用 表达式引用
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

数据对象

现在POJO的绑定情况基本已经说完了。但是如果更改了数据,显然UI是不会改变的。
要实现UI的改变,有三个机制:

可观察对象

private static class User extends BaseObservable {
   private String firstName;
   private String lastName;
   //注意到标签,标签在编译时在BR类(类似R文件)里生成一个元素,
   @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);
   }
}

如果基类不能修改,那么可以自己实现:
重看,没实现
今天重新使用发现,firstName要符合驼峰规则(不能用全大写),getFirstName()的写法也一样,这样编译了之后BR中才找的到。。具体要多符合就不去测试了。

public class Item implements Observable {
    private PropertyChangeRegistry callbacks = new …
    …
    @Override
    public void addOnPropertyChangedCallback( 
            OnPropertyChangedCallback callback) {
        callbacks.add(callback);
    }
    @Override
    public void removeOnPropertyChangedCallback( 
            OnPropertyChangedCallback callback) {
        callbacks.remove(callback);
    }
}

可观察数据域

private static class User {
    //会自动根据类型装箱
   public final ObservableField<String> firstName =
       new ObservableField<>();
   public final ObservableField<String> lastName =
       new ObservableField<>();
   //直接指明int
   public final ObservableInt age = new ObservableInt();
}

重看,没变化

//代码中调用
user.firstName.set("Google");
int age = user.age.get();

可观测容器类

//除了加个Observable,其他和上面不可观测的使用一样。
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();

顺便还讲了个技巧,用ObservableArrayList和布局绑定的时候,可以预见,属性的顺序肯定要长久固定,比如age,是第三个属性,list[2],那么2可以用静态常量表示,写成list[Feilds.AGE]。可读性大增

绑定的生成

不管是否自定义,binding类全部继承自ViewDataBinding

创建

最常规的用法

MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);

有时候layout通过不同机制已经inflater了。可以直接binding

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

有时候binding(类型)不能预先知道

ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
    parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);

这么多款式,总有一款适合你

带有ID的view

会根据layout中view的id(有的话),在binding类中生成一个public final域
这里的传递机制会比很多view的findViewById方式要快。

Variables

//在binding中样式如下,类型自动会匹配
public abstract com.example.User getUser();
public abstract void setUser(com.example.User user);

ViewStubs

重看,没用过
ViewStub有些特殊,它本质上不存在于view结构,binding 类中的类也得移除掉,以便系统回收。因为 binding 类中的 View 都是 final 的,所以我们使用了一个叫 ViewStubProxy 的类来代替 ViewStub。开发者可以使用它来操作 ViewStub,获取 ViewStub inflate 时得到的视图。

当 inflate 一个新的布局时,binding重新确立。因此,ViewStubProxy 必须监听 ViewStub 的 ViewStub.OnInflateListener,并及时建立 binding。由于 ViewStub 只能有一个 OnInflateListener,你可以将你自己的 listener 设置在 ViewStubProxy 上,在 binding 建立之后, listener 就会被触发。

进阶绑定

动态变量

比如在recycler的使用中,item的layout没法预先知道,得在 onBindViewHolder(VH, int).中绑定。
重看,没实现

public void onBindViewHolder(BindingHolder holder, int position) {
   final T item = mItems.get(position);
   holder.getBinding().setVariable(BR.item, item);
   holder.getBinding().executePendingBindings();
}

立即执行

当可观测物改变时,binding都会在下一帧的时候执行变化,不管如何都需要时间。如果要强制马上执行,调用executePendingBindings()方法。

后台线程

只要数据不是容器类,你可以直接在后台线程做数据变动。Data binding 会将变量/域转为局部量,避免同步问题。

明天再看… …

属性Setter

自动生成Setters

重命名Setters

自定义Setters

转换器

猜你喜欢

转载自blog.csdn.net/u013867301/article/details/53080067