jetpack-DataBinding学习

介绍

曾经我们经常遇到一个View和一个实体字段有绑定关系,View的属性随着实体字段相互依赖变化,我们经常的做法就是给这个View增加变化监听,然后修改对应属性实体。
这种方法很复杂,而且经常稍有不慎出现死循环的,我以前的公司就是这种方案,很难受。学了DataBinding我发现这种xml布局和实体对象绑定的功能,能完美的解决这个问题。

使用

启用dataBinding
android {
    ...
    dataBinding {
        enabled = true
    }
}
修改布局文件

选择根布局,选择`Convert to data binding layout 在这里插入图片描述
生成的结果

<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable name="user" type="com.example.User"/>
    </data>
    <ConstraintLayout... /> <!-- UI layout's root element -->
</layout>

发现跟布局使用了layout,并且多生成了data节点,其中的user变量data描述了可在此布局中使用的属性。
布局中的表达式使用“ @{}”语法写入属性属性中。例如TextView文本设置为变量的 user的firstName:

<TextView android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.firstName}" />

到这里本布局就和User实体绑定了,并且一个TextView和user的firstName做了绑定,也就是这个view展示的内容就是firstName
当写完布局后,build下项目,你会多生成一个类,类名是:布局名字Binding

绑定数据

xml已经知道了,创建本布局View需要一个User对象了,哪这个对象多久设置了??该如何设置呢?

  1. 设置Activity的DataBinding
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val binding: ActivityMainBinding = DataBindingUtil.setContentView(
            this, R.layout.activity_main)

    binding.user = User("Test", "User")
}
  1. 通过LayoutInflater创建DataBinding对象
// ActivityMainBinding对应布局生成的Binding类,也就是有一个布局文件是activity_main,的根部局势使用了layout
val bindng: ActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater())
  1. 通过DataBindlingUitl
// 传入对用的布局
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)
// 如果需要使用view  调用listItemBinding.root

布局中的data节点

variable

需要绑定变量,例如当前布局需要一个com.example.User类型的对象,name这个对应下面引用的昵称(类似设置个昵称),每个name对应在生成的DataBinding对象中生成了一个字段,将来需要赋值

<variable name="user" type="com.example.User"/>
import

导入,例如多个对象,都是list类型,可以用这种形式

 <import type="java.util.List"/>
 <variable name="list" type="List&lt;String>"/>
# 集合使用可以使用这种方式
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"

这里有个坑,有时候有泛型,怎么处理呢?如何你在xml中写List<String>,会报错,请使用&lt;代替
在这里插入图片描述

alias

重新命名,如果名字有冲突了,可以使用alias重新命名

# 重新将List命名成AliasList
 <import type="java.util.List" alias="AliasList"/>
 <variable name="list" type="AliasList<String>"/>
自定义绑定类名称

默认情况下,将根据布局文件的名称生成绑定类,以大写字母开头,删除下划线(_),大写以下字母,并为单词Binding添加后缀。该类放在 databinding模块包下的包中。例如,布局文件 contact_item.xml生成ContactItemBinding类。如果模块包是com.example.my.app,则绑定类放在 com.example.my.app.databinding包中。

通过调整元素的class属性,可以重命名绑定类或将绑定类放在不同的包中 data。例如,以下布局在当前模块ContactItemdatabinding包中生成绑定类:

<data class="ContactItem"></data>

您可以通过在类名前加一个句点来为不同的包生成绑定类。以下示例在模块包中生成绑定类:

<data class=".ContactItem"></data>

您还可以使用要在其中生成绑定类的完整包名称。以下示例ContactItem在com.example包中创建绑定类 :

<data class="com.example.ContactItem"></data>

布局文件中可用的表达式

  • 数学的 + - / * %
  • 字符串连接 +
  • 逻辑 && ||
  • 二进制 & | ^
  • 比较== > < >= <=(注意<需要转义为&lt;)
  • 位移 >> >>> <<
  • instanceof
  • 三元运算符 ?:
  • 方法调用
  • 数组访问 []
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

这里说一个比较特殊的表达式 ??

# 如果displayName为null就使用lastName,否则就是用displayName
android:text="@{user.displayName ?? user.lastName}"

事件绑定

前面说了字段绑定,现在说下事件绑定

传入方法引用
    <data>
      	。。。。
        <variable
                name="activity"
                type="com.qihoo.jectpackdemo.ui.fragment.RegisterFragment"/>
    </data>
 # 对应按钮的点击事件,方法在activity对象的clear方法
 <Button
       android:text="clear"
       android:onClick="@{activity::clear}"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"/>

对用类的方法这里要注意,形参必须要对应方法的参数,不然会报错

    fun clear(view: View) {
        register.user.set("")
        register.pass.set("")
    }
传入lambda表达式
# 这种方式比较灵活了,不需要填写对应参数的形参了,但是我如果需要形参呢?
#  android:onClick="@{(view)->activity.changeRemark(view)} 在lambda填写参数即可,如果事件参数多个,一定要把lambda形参补全
   <Button
                android:text="修改备注"
                android:onClick="@{()->activity.changeRemark()}"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>

Includes布局

如果使用了includes呢?如何将数据导入呢?

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
# 关键点1
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <merge><!-- Doesn't work -->
       <include layout="@layout/name"
       # 关键点2
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </merge>
</layout>

可观察数据

通过上面的只能达到view创建的时候,对应内容展示对应字段。 当字段发生变化,view发生变化,这种的时候应该怎么处理呢?

创建可观察数据
class User {
    val firstName = ObservableField<String>()
    val lastName = ObservableField<String>()
    val age = ObservableInt()
}
// 修改数据
fun clear(view: View) {
     user.firstName.set("")
     user.lastName.set("")
}

这样创建的变量,就可以达到字段发生变化,对应View的属性也发生变化。接下来问题,如何达到view属性变化,对应字段也发生变化呢?

# 其实很简单,只需要在引用的时候使用  @={} (注意有个 = )
        <EditText
                android:hint="请输入账号"
                android:text="@={register.user}"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
自定义可观察对象
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)
        }
}

立即绑定

当变量或可观察对象发生更改时,绑定计划在下一帧之前更改。但是,有时必须立即执行绑定。要强制执行,请使用该 executePendingBindings() 方法。
有时,特定的绑定类是未知的。例如,RecyclerView.Adapter针对任意布局的操作不知道特定的绑定类。它仍然必须在调用onBindViewHolder()方法期间分配绑定值。

在以下示例中,RecyclerView绑定的所有布局都具有 item变量。该BindingHolder对象有一个getBinding()返回ViewDataBinding基类的方法 。

override fun onBindViewHolder(holder: BindingHolder, position: Int) {
    item: T = items.get(position)
    holder.binding.setVariable(BR.item, item);
    holder.binding.executePendingBindings();
}

绑定适配器

基本用法

当一个view有setXXX方法设置属性,就都可以在DataBinding中配置。例如DrawerLayout没有任何属性,但有很多setter。以下布局分别自动使用 setScrimColor(int)setDrawerListener(DrawerListener)可以通过app:scrimColorapp:drawerListener`设置:

<android.support.v4.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}">
自定义适配器

举个简单的例子,例如一个imageview没有加载图片的功能。能不能自定义这样的一个熟悉,然后定义熟悉的方法实现,自动完成加载图片呢?

@BindingAdapter("loadImage")
fun ivLoadImage(view: ImageView, url: String) {
    // 加载图片逻辑
}
<ImageView
# 关键点在这里,使用了loadImage属性
                app:loadImage='@{register.iconUrl}'
                android:src="@mipmap/ic_launcher"
                android:layout_width="200dp"
                app:paddingLeftAndRight="@{30}"
                app:paddingTopAndBottom="@{60}"
                android:layout_height="200dp"/>

当创建后,就会自动调用ivLoadImage方法了

这里有个坑,我使用@BindingAdapter,编译报错,提示我没有app:loadImage属性。最后原来没有使用kotlin的apt(注解处理器)
在项目的build,gradle加入 apply plugin: 'kotlin-kapt'

配置多个适配器:

@BindingAdapter("imageUrl", "error")
fun loadImage(view: ImageView, url: String, error: Drawable) {
    Picasso.get().load(url).error(error).into(view)
}

这样就配置了2个属性,一个imageUrl,一个error。注意:这种触发条件必须2个属性同时存在才会触发

<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />

有时候,我定义了多个。但是我想任意一个都触发,这时候只需要配置requireAll=false,注意:如果这样一定要对形参做非null判断哦!

@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false)
fun setImageUrl(imageView: ImageView, url: String?, placeHolder: Drawable?) {
    if (url == null) {
        imageView.setImageDrawable(placeholder);
    } else {
        MyImageLoader.loadInto(imageView, url, placeholder);
    }
}

自定义转化

举个例子,一个ImageView的background属性,期望属性值是Drawable,但是我传入了一个字符串,我们需要定义一个转换方法

   <ImageView
                android:background='@{"我专门传一个字符串"}'
                android:layout_width="200dp"
                app:paddingLeftAndRight="@{30}"
                app:paddingTopAndBottom="@{60}"
                android:layout_height="200dp"/>

形参String类型,返回值Drawable类型

@BindingConversion
fun convertColorToDrawable(colorStr: String): Drawable {
    log("收到转换:$colorStr")
  return ColorDrawable(Color.RED)
}

注意:但是,绑定表达式中提供的值类型必须一致。您不能在同一表达式中使用不同的类型,如以下示例所示:

<View
   android:background="@{isError ? @drawable/error : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值