JetPack-Databinding

简介

将布局中的界面组件绑定到应用的数据源中

简单使用

使用步骤:

  1. 创建数据对象,即一个类
  2. 创建布局文件
  3. 绑定数据

例子:

  1. 编译环境

    在对应模块的build.gradle下添加dataBinding元素

    android {
        // DataBinding必须
        dataBinding {
            enabled = true
        }
    }
    
  2. 创建数据对象

    这里我创建User类,用于绑定到xml文件上

    data class User(val name: String)
    
  3. 创建布局文件

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
    
        <data>
            <!-- 定义一个变量,名称为user,类型为User-->
            <variable
                name="user"
                type="com.hhh.jetpacktest.databinding.User" />
        </data>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <!--直接使用上面定义的变量的值来填充TextView-->
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@{user.name}" />
    
        </LinearLayout>
    </layout>
    

    布局文件一定要以 layout 标签为顶层元素。

    当前布局文件名称为 activity_data.xml ,build 一下,就会发现在 build/data_binding_base_class_source_out/debug/out/应用包名/databinding 目录下生成了

    ActivityDataBinding.java 文件。

    生成规则:根据 xml 文件的名称使用驼峰命名法,然后后面加上Binding生成。当然,也可以在 data 标签中使用 class 自定义名称

  4. 绑定数据

    class DataBindingActivity : AppCompatActivity() {
    yinyong
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            // 根据activity_data.xml名称自动生成了ActivityDataBinding类
            val binding = ActivityDataBinding.inflate(layoutInflater)
            setContentView(binding.root)
    
            binding.user = User("张三")
        }
    }
    

    完成,运行一下发现,主界面中就一个TextView,显示着 User 的名称

布局与绑定表达式

获取ViewDataBinding

数据绑定库会根据根标签为 layout 的布局生成一个类,该类是继承 ViewDataBinding ,绑定数据前必须要获取这个继承类,获取方法主要分为两类

  • 直接使用生成的文件名称,调用 inflate 方法
  • 使用 DataBindingUtil 。在Activity中可直接调用setContentView方法,在Fragment,RecycleView等可以使用inflate方法

以上面的简单使用中的xml文件为例子,可以这样获取

// 直接使用生成的类的名称ActivityDataBinding
val binding = ActivityDataBinding.inflate(layoutInflater)
setContentView(binding.root)

// 使用DataBindingUtil的获取
val binding2 = DataBindingUtil.setContentView<ActivityDataBinding>(this, R.layout.activity_data)

inflate重载的方法有很多,使用的时候根据ide提示即可。

表达式语言

就是布局xml中的语言,在View的属性里面使用 @{} 里面的就是表达式语言,基本上和外面的java语言类似,就是少了new,super,this 等,不建议表达式语言写复杂的,太复杂的直接在外面写个方法传递到 @{}里面,类似下面的事件处理的方法引用

事件处理

方法引用

当表达式求值结果为方法引用的时候,被引用的方法和所有者对象都会封装到监听器中,并在目标View中设置该监听器。如果表达式求值结果为null,相当于没有设置监听器。

方法引用实际监听器是在绑定数据的时候创建的,不是在事件出发的时候创建的。如果希望在事件发生时对表达式求值,应当使用监听器绑定。

举个例子:上面的简单示例中,给TextView设置监听,点击的时候弹出Toast

  1. 先创建一个类,定义方法,将来要被引用

    class MyHandler {
    
        fun toastText(view: View) {
            if (view is TextView) {
                Toast.makeText(
                    MyApp.appInstance().applicationContext// 获取的一个context对象,MyApp继承Application
                    , view.text, Toast.LENGTH_SHORT
                ).show()
            }
        }
    }
    

    当前定义的是一个普通的类,也可以使用对象申明使用单例,即object Myhander、

  2. 布局文件中使用

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
    
        <data>
            <!-- 定义一个变量,名称为user,类型为User-->
            <variable
                name="user"
                type="com.hhh.jetpacktest.dbinding.User" />
    
            <variable
                name="myHandler"
                type="com.hhh.jetpacktest.dbinding.MyHandler" />
        </data>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <!--android:onClick 引用myHandler的toastText方法 -->
            <TextView
                android:id="@+id/tv"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="@{myHandler::toastText}"
                android:text="@{user.name}" />
    
        </LinearLayout>
    </layout>
    

    这里解释了为啥 MyHandler#toastText 方法的参数是 View, 而不是直接使用 TextView,因为 onClick方法的参数就是 View。

    与在kotlin 代码中使用setOnclickListener 对比,使用方法引用的优点是表达式在编译时进行处理,所以,如果方法的参数或者签名不正确,编译的时候就无法通过。这里解释了方法引用的监听器是在绑定数据的时候创建的。

    最后,一定不要忘记在Activity中传入MyHandler的示例哦

    binding.myHandler = MyHandler()
    

监听器绑定

是在事件发生时运行的绑定表达式。在监听器绑定中,方法的返回值必须和监听器预期的范志毅相匹配

来改造一下上面的,使用监听器来重写,直接把布局文件中的 TextView改变一下即可

<TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onClick="@{(view)->myHandler.toastText(view)}"
    android:text="@{user.name}" />

android:onClick 里面的表达式是一个 lambda 表达式,为啥能够这么写呢?

tv.setOnClickListener { view ->
    myHandler.toastText(view)
}

看上面 setOnClickListener 里面的 lambda 表达是就知道了

导入、变量与包含

    <data>
        <!-- 导入-->
        <import
            alias="CusView"
            type="android.view.View" />

        <!--变量-->
        <variable
            name="user"
            type="com.hhh.jetpacktest.dbinding.User" />
    </data>

import标签中type表示类型,要包括包名,可以使用alias重命名。在导入中,注意某些特殊符号需要使用转义。例如 < 就需要转义。

在下面,就可以使用 CusView应用 android.view.View 这个类。

数据绑定支持 include 标签,但是不支持 include 作为 merge 标签的直接子元素

可观察的数据对象

可观察对象,就是如果这个对象改变了,就会通知其他对象说自己变了,其实跟 LiveData 很相似,更推荐使用 LiveData

可观察字段与集合

可观察字段都是继承 androidx.databinding.BaseObservable 的类

可观察的集合 ObservableList,ObservableMap ,主要就这两个,初始化的时候都是用他们的继承类

可观察字段和可观察集合的使用方法都是类似的

接下来举个例子,还是上面的例子,只是在点击TextView的时候修改User的属性,然后看看TextView会不会有什么变化

步骤如下:

  1. 修改数据对象,将其变为可观察的数据对象

    data class User(val name: ObservableField<String>)
    
  2. 在 MyHandler 中定义一个方法,用于修改User的属性

    class MyHandler {
        fun changeName(user: User, newName: String) {
            user.name.set(newName)
        }
    }
    
  3. 修改 Activity_data.xml 文件中 TextView 的属性

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick='@{()-> myHandler.changeName(user,"法外狂徒")}'
        android:text="@{user.name}" />
    

    其实这里并没有什么变化,只是给TextView设置了监听器绑定,在点击该 View 的时候就会调用 MyHandler#changeName 方法。

    记住,一定要在这个 xml 文件中传入 MyHandler 实例

  4. 在 Activity 中传入数据

    binding.user = User(ObservableField("张三"))
    

    使用可观察数据对象的时候,都是使用 set ,get 方法来修改值

    ok,步骤完成,安装,就会发现,点击了 TextView ,就会发现 TextView 的文字发生了改变了

可观察对象

只需要将一个类实现 androidx.databinding.Observable 接口即可,但是更方便的是继承 androidx.databinding.BaseObservable类。

如下所示:

class User : BaseObservable() {
    var name: String = ""
        @Bindable
        get() = "法外狂徒-${field}"
        set(value) {
            field = value
            notifyPropertyChanged(BR.name)
        }

    @get:Bindable
    var age: Int = 1
        set(value) {
            field = age
            notifyPropertyChanged(BR.age)
        }
}

对于一个属性,需要使用 @Bindable 注解在 get 方法上,就可以在 build/generated/source/kapt/debug/包名 目录下生成的 BR 对象中生成一个字段。比如当前 name 属性的 get 方法被注解了后,build 一下,就可以看到 BR 内生成了 name 字段。

对于某些字段,不需要重写 get 方法,用默认的即可,那就在属性上面 @get:Bindable 注解。 如上面的 age 属性

对于set 方法,需要调用 notifyPropertyChanged 方法通知发生了改变。

绑定适配器

自动选择方法

绑定适配器一般都会对一个属性自动选择方法,不需要我们自定义方法名称,一般就是调用的方法名称就是 set+属性名,参数就是在布局文件中传入的属性的值,必须方法名称与参数类型都对上,库才能够自动选择方法

<TextView
    android:id="@+id/tv"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@{user.name}" />

如上,text属性,传入的值 user.name 是 String 类型的,库就会查找 TextView#setText(String text) 方法,查找到就可以正常调用,否则编译时就出错了。一般我们不用去管

BindingMethods

上面提到,必须要 TextView#setText(String text) 方法,上面代码才能正常,如果没有呢,就需要自己自定义方法,使用 BindingMethods 注解,但是不推荐,很少使用到这种方式,一般直接使用 BindingAdapter

BindingAdapter

重头戏,基本上都是使用这个注解自定义方法以及逻辑

例子:对 ImageView 设置两个自定义属性(不需要在res目录下attrs.xml创建),用 Glide 框架加载图片。

先自定义一个方法,使用该注解

@BindingAdapter(
    value = ["imgUrl", "placeHolder"],
    requireAll = true
)
fun loadCusImg(view: ImageView, url: String, placeHolder: Drawable) {
    GlideApp.with(MyApp.appInstance().applicationContext)
        .load(url)// 加载url
        .placeholder(placeHolder)// url加载失败,就使用占位图
        .into(view)
}

在布局文件中适应 BindingAdapter 中 value 的属性,注意,值一定要与上面自定的方法的参数的类型相同,否则出错:

<ImageView
    android:id="@+id/img"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:placeHolder="@{@drawable/test}"
    app:imgUrl="@{user.url}" />

user.url 返回的是一个字符串。@drawable/test 返回的是一个 Drawable 图片。

BindingAdapter 中,value 有多个属性的时候,requireAll = true 则表明,在 ImageView 中,value 的多个属性都必须要求有,否则直接报错。比如上面的 ImageView 中如果只写了app:placeHolder ,没有 app:imgUrl 则直接报错。如果 requireAll = false,则不会报错。

对象转换

类似room那样,将一个特定的类型转换为另一个类型

比如下面,将 String 类型转换为 Int

@BindingConversion
fun convertStringToInt(str: String): Int = str.length

接下下来实用一下,看看是否生效。先自定义一个方法:

@BindingAdapter("length")
fun setTextWithLength(view: TextView, length: Int) {
    view.text = "长度为$length"
}

然后在TextView中实用自定义的属性:

<TextView
    length="@{user.name}"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

ok,完成,可以在 BindingConversion 注解的方法中加上注解,发现的确被调用了

#Other

待补充

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值