Databinding

Databinding 介绍 与开启

Databinding 是一个能够实现双向绑定的google自带的工具库,也就是他能够让V界面与M数据bean进行绑定在一起,正是因为界面绑定着数据,所以当数据发生改变的时候,界面也会跟谁改变,而界面改变的时候,数据也会发生改变,这就是双向绑定,想要使用他很简单,就是再 build.gradle 下加上

android{
 dataBinding.enabled = true   代表开启即可使用
 }

这里额外说下,数据驱动UI,livedata也是可以做到, 就是在livedata的观察者里,UI监听着livedata的数据,livedata的数据一改变,UI就跟随状态,只是livedata需要手动去设置UI监听数据这件事,并且由于livedata 参数传入了 LifecycleOwner lifecycle,所以只在可见的时候数据改变 才界面跟随改变,不可见的时候,数据改变 监听不到,界面当然就不会刷新改变,
而Databinding则是以布局里面进行数据和界面的绑定的方式,他并没有像 livedata有生命周期限定,对于界面绑定的 ObservableField字段
当界面不可见的时候, ObservableField 字段的数据改变, ObservableField 字段的数据绑定的界面也会跟随改变,当可见的时候,你就会看到界面变化了。

Databinding 的使用

Databinding的布局绑定就是Viewbinding的功能,具体在这一篇 Viewbinding使用,我们后续使用不在追溯
我们只讨论关键的界面与数据绑定

首先是数据 这里数据 他是个类,有可能是个bean类,有可能是ViewModel类等情况
然后控件绑定的就是 该类对应的字段或者调用该类对应的函数方法

打个比方是个bean类,这里其实跟什么类没关系,关键是属性用的类型
bean

class Student {

    //注意类的字段要用 ObservableField,才会后续,数据变,界面才跟着改变
    var name: ObservableField<String>  = ObservableField<String>()

    //如果你只是个普通的字段, 数据变,界面是不会跟着改变
    var name :String? = null

}

布局layout写法

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">


<!--    <data></data> 标签代表的是数据M,也就是下面的V界面将要绑定的M-->
<!--    这里只是先声明下面的V界面 要跟哪个类进行绑定,而具体是类的哪个实例还没真正进行绑定-->
    <data>

<!--        //M模型的名字,以及具体是哪个类-->
        <variable
            name="student"
            type="com.example.myapplication.Student" />

//可以有很多个M,又不是一定只能绑定一个data 的数据。多个类也是正常的
  <variable
            name="xxx"
            type="com.example.myapplication.xxx" />

    </data>

<!--    //而下面的就是界面V-->
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">


<!--        这里就是代表了这个textview要绑定数据Model也就是student的name字段,当name字段发生改变的时候,-->
<!--        这个里的textview就会自动更新,做到M变,V跟着变。-->
<!--        而如果这里的   android:text="@{student.name}" 写成  android:text="@={student.name}"-->
<!--        那么当这个textview发生改变的时候,student的name 字段也会发生改变,做到V变,M也跟着改变,也就是双向绑定-->
<!--        但是一般情况下,我们只需要M变,V跟着变一向改变就可以,V变,M也跟着改变太耗性能,所以就不需要情况下不会写上这个=号-->
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/text1"
            android:text="@{student.name}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Mainactivity

class MainActivity : AppCompatActivity() {


    var student: Student? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)


        var activityMainBinding: ActivityMainBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_main)


        //创建student实例
        student = Student()


        //这里就是真正的建议 界面和数据的绑定
        // 界面里面只是说明 这个 R.layout.activity_main  要跟一个类 com.example.myapplication.Student 建立绑定
        //而这里就是要跟一个具体实例 也就是当前的 student实例建议绑定,这样之后,
//        student的数据一改变,界面显示就会发生改变
        activityMainBinding.student = student

       //当这个 student实例对象的 的name 字段发送改变那么textview显示的文案就会发送改变
       //对于 ObservableField 用要 set get 设置字段值
       student.name.set("zjs")


     //延迟3s,在设置值,界面也会跟随数据改变
        Handler().postDelayed({
            student.name.set("1234")
        },3000)


}

布局类Databinding的写法

原本如果是引用string文件资源是
android:text=“@string/xxx”
现在如果是数据的形式写法就是 text=“@{}” 一样是 双引号下, @ {里面写很多语言写法}

在这个{}花括号内是有可以写很多种语句的。
其实这相当与在{}花括号内写代码了
而当写的代码需要导入其他类的时候 里面就需要导入 相关的类

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        //当你写的语句需要用到其他类的时候,这里就需要通过标签导入对应的类<import
        <import type="android.view.View"/>
        <variable
            name="student"
            type="com.example.jepackstudy.Student" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/text"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:textColor="@color/black"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"

            android:text="@{String.valueOf(student.index + 1)}"
            android:visibility="@{student.age > 13 ? View.GONE : View.VISIBLE}"
            android:transitionName='@{"image_" + student.id}'

            />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

{} 花括号内可以写的代码示例如下:

  1. android:text=“@{user.displayName ?? user.lastName}” // ??代表 如果左边不是null就用左边,如果是null,就用右边

  2. android:OnClick=“@{()->user.testOnClick()}”// 也可以设置点击事件 绑定在 user 的testOnClick()方法里面
    写法 @{}花括号内 ()->user.testOnClick() 对应的数据的对应方法

  3. 在调用某个数据的对应方法需要参数的数据也可以直接写死或者是直接参数 用@string/xxx去写
    android:onClick=“@{()->user.test(@string/app_name)}”

    android:onClick=“@{()->user.test(String.valueof(“123”))}”

这里如果写string 写死 test(“123”) 会报错的,得用String.valueof()包住‘

BR文件

BR文件是 Data Binding 自动生成的类,当你在布局中,写入一个 user 字段的时候,就会在BR文件中为这个user 生成一个对应id

叫做 BR.user 。正是id 所以 BR.user 是个int 值。

 <data>
        <variable
            name="user"
            type="com.android.myapplication.User" />

    </data>

生成对应id的一些使用场景是这样的

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        var activityMainBinding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        var user = User("zjs")
        
        //对指定的id设置一个指定的的实例对象
        activityMainBinding.setVariable(BR.user,user)
        //这代码就是
        activityMainBinding.uset =user

        user.name ="123"

    }
}

BindingAdapter

如果想要 自定义控件的属性,这时候需要用到 BindingAdapter 注解

DataBinding支持在一个方法上添加@BindingAdapter(“xxx”)注解 来为任意控件添加一个或者多个 xxx 属性,注意是任意控件都可以使用这些自定义属性,该方法需满足以下条件:

方法参数第一个要求必须是View ,这个参数View,就是代表是当前哪个控件在用
方法最好是静态的
BindingAdapter 注解的方法并不严格要求是静态的。它也可以是实例方法,但这通常不推荐,因为这样做可能会导致额外的性能开销和内存消耗,因为每次调用时都需要创建或维护该类的实例

比如说 ,这里添加了一个叫做 imageUrl 的属性,当有控件使用这个属性,其实就是
调用这里的 setImageUrl 方法 ,而这个方法叫做的第一个参数是一个View,
这个参数View,就是代表是哪个控件在用

@JvmStatic//这个注解代表是静态的意思
@BindingAdapter("imageUrl")  
fun setImageUrl(imageView: ImageView, imageUrl: String?) {  
    if (!imageUrl.isNullOrEmpty()) {  
        Glide.with(imageView.context)  
            .load(imageUrl)  
            .into(imageView)  
    }  
}

那么在使用的时候 ,就可以在任何控件上添加 app:imageUrl 属性,并且值要是方法
setImageUrl的参数,而第一个参数View 就是这里的 ImageView

<ImageView  
    android:layout_width="wrap_content"  
    android:layout_height="wrap_content"  
    app:imageUrl="@{viewModel.image}" />

@BindingAdapter 还可以为控件添加多个属性,

比如下面就是为 progressBar 添加了两个属性
app:progressScaled
android:max

而 requireAll 在默认情况是true, 当为true,他的意思是 ,
为 progressBar 添加的两个属性

app:progressScaled
android:max
必须在使用控件 progressBar 的时候都写上,这样才有对应的值传过来这个方法 setProgress

而如果 requireAll = false ,代表,不要求 使用 progressBar 的时候,两个属性都必须添加
没有添加的缺少的属性如果是对象类型,则为 null,Boolean 类型则为 false,Int 类型则为 0

@JvmStatic
@BindingAdapter(value = ["app:progressScaled", "android:max"], requireAll = true)
fun setProgress(progressBar: ProgressBar, likes: Int, max: Int) {
    progressBar.progress = (likes * max / 5).coerceAtMost(max)
}
<ProgressBar
                android:id="@+id/progressBar"
                app:hideIfZero="@{viewmodel.likes}"
                app:progressScaled="@{viewmodel.likes}"
                android:max="@{100}"

包括也可以为一个ImageView添加多个属性

@JvmStatic
@BindingAdapter("imageUrl", "error")
    fun loadImage(view: ImageView, url: String, error: Drawable) {
        Picasso.get().load(url).error(error).into(view)
    }
<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />

BindingAdapter的好处就是可以把view的功能给抽出到一个方法出来
而往往这些方法就拿来当做工具方法,放在Object 单例类里

//像这里的,自定义了一个 onClick属性,那么任务控件就可以直接使用这个属性

object BindingAdapter {

    @JvmStatic
    @androidx.databinding.BindingAdapter("onClick")
    fun onClick(view: View,  doSomething: () -> Unit ) {
        view.setOnClickListener {
            doSomething
        }
    }
}

像在这里的 TextView他就用了 onClick 属性,而因为onClick 属性需要穿个函数引用过去
这里调用的就是 mainViewModule.textViewOnClick 的方法
而上面 onClick方法里面以及对传进来的view 设置点击监听,并且事件做的事情就是传过来的函数
也就是这里就会做 mainViewModule.textViewOnClick方法

 <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            onClick = "@{mainViewModule.textViewOnClick}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

这就是 BindingAdapter 一个点击的抽取功能到独立方法的一个实例,这样,不止上面的
TextView,任何一个可点击的View 都可以使用这个 onClick 属性然后传入要做的函数

不仅仅上如此,在MVVM,数据驱动UI的形式上,我们说的数据一般都是 VM 里的Livedata
而界面就是利用Databinding ,也就是 界面上,绑定的是 VM 里的Livedata
当 VM 里的Livedata的数据一改变,Livedata的观察者 Databinding 就会驱动绑定的界面 改变
那么在加上 BindingAdapter 我就可以写出这样代码:

object BindingAdapter {

    @JvmStatic
    @androidx.databinding.BindingAdapter("onClick")
    fun onClick(view: View,  doSomething: () -> Unit ) {
        view.setOnClickListener {
            doSomething
        }
    }

   //这里自定义一个 Color 属性并且为传来的view 设置传来的参数Color值
    @JvmStatic
    @androidx.databinding.BindingAdapter("Color")
    fun setColor(view: View,  color:Int ) {
        view.setBackgroundColor(color)
    }
}

那么我就可以在任务控件上使用这个 Color 属性,并且他的参数是使用了
mainViewModule.colorLiveData

   <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            onClick = "@{mainViewModule.textViewOnClick}"
            Color = "@{mainViewModule.colorLiveData}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

也就是上面的这种写法,自定义一个属性,把功能抽出来一个方法,参数用VM的数据 , 他的目的就是为了,当mainViewModule.colorLiveData的数据改变, setColor方法会被自动调用,
从而在抽出来的方法做逻辑,

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值