【Jetpack】学穿:DataBinding → 数据绑定 (使用篇)(中)

简介: 前面的章节 《【Jetpack】学穿:ViewBinding → 视图绑定》 剥源码的时候就有看到 DataBinding 相关的代码。 ViewBinding(视图绑定) 的作用和原理一言以蔽之: 作用 → 代替findViewById 的同时,还能保证 空安全 和 类型安全,且 支持Java; 原理 → AGP为模块中的每个XML生成绑定类,本质上还是findViewByid,只是自动生成控件实例,并一一对应;

资源


表达式中引用应用资源,示例如下:


android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"


还支持格式化字符串及复数的参数传入,示例如下:


android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"


还可以把属性引用和View引用作为资源参数进行传递,示例如下:


android:text="@{@string/example_resource(user.lastName, exampleText.text)}"
<!-- 当一个复数带有多个参数时,您必须传递所有参数 -->
android:text="@{@plurals/orange(orangeCount, orangeCount)}"


某些资源需要显式类型求值,如下表所示:


网络异常,图片无法展示
|


4) 事件处理


事件属性名一般由 监听器方法名称确定,如:View.OnClickListeneronClick()android:onClick。但存在特例,如下表:


网络异常,图片无法展示
|


另外,可以使用 方法引用监听器绑定 来进行事件处理,两者的代码示例如下:


// 方法引用
class MyHandlers {
    fun onClickFriend(view: View) { ... }
}
<?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>
// 监听器绑定
class Presenter {
    fun onSaveClick(task: Task){}
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="task" type="com.android.example.Task" />
        <variable name="presenter" type="com.android.example.Presenter" />
    </data>
    <LinearLayout 
        android:layout_width="match_parent" 
        android:layout_height="match_parent">
            <Button 
                android:layout_width="wrap_content" 
                android:layout_height="wrap_content"
                // lambda表达式,事件分发后会对此表达式进行求值
                android:onClick="@{() -> presenter.onSaveClick(task)}" />
    </LinearLayout>
</layout>


不难看出区别,引用方法需要和监听器的参数一致,而监听器绑定更加灵活,可在运行时动态运行lambda表达式,参数无需一致。


上述忽略了onClick(View)的View参数,如果后面的lambda表达式有用到的话,可以定义 命名参数,示例如下:


class Presenter {
    fun onCompletedChanged(task: Task, completed: Boolean){}
}
<CheckBox 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content"
    android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />



  • 如果监听事件的返回类型不为Void,lambda表达式也要返回相同类型的值
  • 监听器表达式这种写法功能强大,可以使代码更易阅读,但不建议写太复杂的表达式,本末倒置,反而使得布局难以阅读和维护。


变量


变量类型在编译时会进行检查,如果不同配置(如横向或纵向)有不同的布局文件,变量会合并到一起。所以这些布局文件的变量定义不要存在冲突!(如同样的变量类型不一致)

系统会根据需要生成名为 context 的特殊变量,用于绑定表达式,它的值是根视图的 getContext() 获取到的Context对象。如果另外定义了同名变量会覆盖。


包含


在include其他布局时,有时需要把变量值传递过去,可以通过 bind:变量名 进行传递,要求两个布局文件拥有同一个变量。示例如下:


<variable name="user" type="com.example.User"/>
...
<include layout="@layout/name"
   bind:user="@{user}"/>
<!-- include指向的布局 -->
<variable name="user" type="com.example.User"/>
...
android:text="@{user}"


注:不支持include作为merge元素的直接子元素,如这样的布局是编译不通过的:


<?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="cn.coderpig.awayfornoise.entity.User"/>
   </data>
   <merge><!-- Doesn't work -->
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </merge>
</layout>


② 可观察的数据对象


DataBinding中可观察的数据对象有三种不同类型:字段集合对象,通过数据绑定,数据对象可在数据发生更改时通知其他对象,即监听器。


1) 可观察字段


示例如下:


class User {
    val firstName = ObservableField<String>()
    val lastName = ObservableField<String>()
    val age = ObservableInt()
}
// 访问字段值,使用set()、get() 访问器方法
user.firstName = "Google"
val age = user.age


除了ObservableInt类外还有这些基本类型:


ObservableBoolean、ObservableByte、ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble、ObservableParcelable


:AS 3.1及更高版本允许使用LiveData对象替换可观察字段。


2) 可观察集合


示例如下:


ObservableArrayMap<String, Any>().apply {
    put("firstName", "Google")
    put("lastName", "Inc.")
    put("age", 17)
}
// 布局中通过字符串key找到值
<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap<String, Object>"/>
</data>
<TextView android:text="@{user.lastName}" ... />
ObservableArrayList<Any>().apply {
    add("Google")
    add("Inc.")
    add(17)
}
// 布局中通过索引访问列表
<data>
    <import type="android.databinding.ObservableList"/>
    <variable name="user" type="ObservableList<Object>"/>
</data>
<TextView android:text="@{user[index]}" ... />


3) 可观察对象


可以自行实现 Observable 接口,但更建议使用DataBinding提供的 BaseObservable


网络异常,图片无法展示
|


实现Observable接口,线程安全,使用 PropertyChangeRegistry 来执行 OnPropertyChangedCallback


使用代码示例如下:


class User : BaseObservable() {
    @get:Bindable
    var firstName: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.firstName)
        }
    @get:Bindable
    var lastName: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.lastName)
        }
}
// 附:Java中的写法
private 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);
    }
}


流程:getter设置Bindable注解 + setter中调用notifyPropertyChanged()

问:上面的BR哪来的


DataBinding会在模块包中生成名为 BR 的类,该类包含数据绑定的资源ID。在编译期,Bindable 注释会在BR类文件中生成一个条目。如果数据类的父类没办法更改,Observable接口可以使用 PropertyChangeRegistry 对象实现。


③ 生成的绑定类


生成的绑定类都是继承的 ViewDataBinding,类名基于布局名称,采用 Pascal命名法 进行转换并添加Binding 后缀,如 activity_main.xmlActivityMainBinding


1) 创建绑定对象


直接点开 DataBindingUtil 类,可以看到里面提供的多种绑定相关的方法:


网络异常,图片无法展示
|


如果可以 预知绑定类型,如ActivityMainBinding,也可以直接用ActivityMainBinding.bind()来绑定~


2) 带ID的View


DataBinding会对布局中拥有ID的每个View在绑定类中创建不可变字段。


3) 变量


DataBinding会为布局中声明的每个变量生成getter、setter方法。


4) ViewStub


占位置,惰性加载,当ViewStub被inflate或setVisible可见,它会从视图层次结构消失,如果想绑定里面的View,需要在监听 OnInflateListener,在此完成绑定


5) 即时绑定


当可变或可观察对象发生更改时,绑定会按照计划在下一帧之前发生更改。如果需要立即执行绑定,强制执行,可 executePendingBindings(),但要注意,此方法必须运行在UI线程


相关文章
|
6月前
|
前端开发 Android开发
Android架构组件JetPack之DataBinding玩转MVVM开发实战(四)
Android架构组件JetPack之DataBinding玩转MVVM开发实战(四)
Android架构组件JetPack之DataBinding玩转MVVM开发实战(四)
|
Android开发
Android JetPack组件之DataBinding的使用详解
Android JetPack组件之DataBinding的使用详解
236 0
|
API Android开发 Kotlin
【Jetpack】学穿:Activity Results API(下)
【Jetpack】学穿:Activity Results API
364 0
|
API 开发者
【Jetpack】学穿:Activity Results API(中)
【Jetpack】学穿:Activity Results API
239 0
|
API 开发者
【Jetpack】学穿:Activity Results API(上)
【Jetpack】学穿:Activity Results API
213 0
|
存储 缓存 数据管理
【Jetpack】学穿:ViewModel → 视图模型(下)
本节带来组件 → ViewModel 视图模型的解读!叫 视图数据 可能更贴切,有人也叫 视图状态
269 0
|
XML 数据格式 Python
【Jetpack】学穿:ViewModel → 视图模型(中)
本节带来组件 → ViewModel 视图模型的解读!叫 视图数据 可能更贴切,有人也叫 视图状态
160 0
|
存储 编解码 数据管理
【Jetpack】学穿:ViewModel → 视图模型(上)
本节带来组件 → ViewModel 视图模型的解读!叫 视图数据 可能更贴切,有人也叫 视图状态
199 0
|
缓存 Java 编译器
【Jetpack】学穿:LiveData → ???(下)
在开始这篇文章前,我就遇到了第一个关于LiveData的问题:该怎么翻译这个词呢?
309 0
【Jetpack】学穿:LiveData → ???(中)
在开始这篇文章前,我就遇到了第一个关于LiveData的问题:该怎么翻译这个词呢?
166 0