Android Data Binding

前提

之前讲过 Android 中的框架:传送门,里面有一个 MVVM 框架,在 MVVM 框架中就用到了 Data Binding,在这里我们详细说一下,Data Binding 的优势有什么呢?有下面几点:

  • 去掉了 Activity 和 Fragment 中的 UI 相关代码
  • 让 XML 变成 UI 的唯一真实来源
  • 不再需要 findViewById
  • 性能超过了手写代码,并且更加安全,不会因为 id 错而导致 crash
  • 所有的 UI 修改代码保证执行在主线程

Data Binding 使用

Data Binding 声明

我们要去使用 Data Binding 的话,就需要在 Android app 的 build.gradle 中声明使用 Data Binding。
build.gradle( app )

android {
    dataBinding {
        enabled = true
    }
}

如果使用的是 Kotlin 的话还需要添加下面代码

apply plugin: 'kotlin-kapt'

kapt {
    generateStubs = true
}
Layout 文件改写

其实也很简单,就是在原有的 Layout 文件外再套上一层 <layout></layout> 标签

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <EditText
            android:id="@+id/userName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:hint="请输入用户名"
            android:textSize="14sp" />

        <Button
            android:id="@+id/submit"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="30dp"
            android:text="提交" />

        <TextView
            android:id="@+id/result"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:textSize="14sp" />
    </LinearLayout>
</layout>

然后在 Activity 中修改导入 Layout 文件的部分代码,最后我们就可以直接通过 binding 去访问控件。
Activity

ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.result.setText("hello, world");
UI / 事件绑定

这里就要说到 xml 文件中的 <data></data> 标签了。我们需要在 xml 文件中定义变量,然后在 Activity 中绑定这个数据,最后直接在控件中使用该变量,就可以达到 Activity 中数据发生改变,控件中的数据也会随之改变的效果。然后因为我们不需要在外部通过 ID 去修改控件中的值了,所以为了防止这种情况的发生,我们也可以将 xml 中控件的 ID 删去。

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

    <data>

        <variable
            name="viewModel"
            type="com.xjh.queryuser.mvvm.MVVMViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:hint="请输入用户名"
            android:textSize="14sp" />

        <Button
            android:id="@+id/submit"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="30dp"
            android:text="提交" />

        <TextView
            android:id="@+id/result"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:text="@{viewModel.result}"
            android:textSize="14sp" />
    </LinearLayout>
</layout>

然后方法我们是不是也可以这样做呢,当然是可以的,总共有两种绑定方式:

  1. 方法引用
  2. 监听器绑定
方法引用

我们只要传递进一个类,类中有我们需要调用的方法,我们通过控件的事件属性去调用这个方法就可以实现了,这样我们就可以使得外部更加干净。
事件属性如:

  • android:onClick
  • android:onLongClick
  • android:onTextChanged

下面就拿 android:onClick 为例吧:

<Button
     android:id="@+id/submit"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_margin="30dp"
     android:onClick="@{viewModel.getData}"
     android:text="提交" />

然后我们在外部调用 setXXX() 方法,将我们需要的绑定的值传递进来就可以了。
Activity

ViewModel viewModel = new ViewModel();
binging.setViewModel(viewModel);
监听器绑定

监听器绑定其实也是在类中声明一个方法,但是这个方法是需要参数传递的,我们也接着用 android:onClick 为例:

<Button
    android:id="@+id/submit"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="30dp"
    android:onClick="@{() -> viewModel.getData(viewModel.userInput)}"
    android:text="提交" />

Data Binding 源码分析

其实我们从之前的使用中可以发现,和 DataBinding 有关的主要是有下面三种:

  • android.binding 包下生成的相关代码
  • BR 类似与 R 文件,通过这个文件可以访问 XML 里面的控件,类似与 R 文件
  • XXXBinding XML 对应的自动生成的 Binding 类

实现我们可以看到我们的入口函数,我们是通过 DataBindingUtil 的 setContentView 方法去获取我们的 Binding 对象。

public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
        int layoutId, @Nullable DataBindingComponent bindingComponent) {
    activity.setContentView(layoutId);
    View decorView = activity.getWindow().getDecorView();
    ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
    return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
}

进入这个方法我们可以发现,他首先做的就是 Activity 的 setContentView 方法,也就是和一般的设置布局文件的操作是一样的,后面就是通过 Activity 拿到 DecorView ,再通过 DecorView 拿到对应的 ViewGroup,最后通过 bindToAddedViews 去生成对应的 Binding 类。

private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component, ViewGroup parent, int startChildren, int layoutId) {
    final int endChildren = parent.getChildCount();
    final int childrenAdded = endChildren - startChildren;
    if (childrenAdded == 1) {
        final View childView = parent.getChildAt(endChildren - 1);
        return bind(component, childView, layoutId);
    } else {
        final View[] children = new View[childrenAdded];
        for (int i = 0; i < childrenAdded; i++) {
            children[i] = parent.getChildAt(i + startChildren);
        }
        return bind(component, children, layoutId);
    }
}

其实就可以发现最后返回的都是通过对应类的 bind 函数去获取的 Binding 类。然后一系列调用 bind 函数过后就到了 DataBinderMapper 的实现类 DataBinderMapperImpl 的 getDataBinder 方法中。

@Override
public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
  int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
  if(localizedLayoutId > 0) {
    final Object tag = view.getTag();
    if(tag == null) {
      throw new RuntimeException("view must have a tag");
    }
    switch(localizedLayoutId) {
      case  LAYOUT_ACTIVITYMVVM: {
        if ("layout/activity_mvvm_0".equals(tag)) {
          return new ActivityMvvmBindingImpl(component, view);
        }
        throw new IllegalArgumentException("The tag for activity_mvvm is invalid. Received: " + tag);
      }
    }
  }
  return null;
}

首先先去比对 layoutId,如果是 LAYOUT_ACTIVITYMVVM 的话,再去比较对应的 tag 是否相同,如果相同则 new 出 ActivityMvvmBindingImpl 这个实现类。

private ActivityMvvmBindingImpl(android.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
    super(bindingComponent, root, 1
        , (android.widget.TextView) bindings[3]
        , (android.widget.Button) bindings[2]
        , (android.widget.EditText) bindings[1]
        );
    this.mboundView0 = (android.widget.LinearLayout) bindings[0];
    this.mboundView0.setTag(null);
    this.result.setTag(null);
    this.submit.setTag(null);
    this.userName.setTag(null);
    setRootTag(root);
    // listeners
    mCallback1 = new com.xjh.queryuser.generated.callback.OnClickListener(this, 1);
    invalidateAll();
}

这里我们就能发现,我们将控件放入对应的数组中,方便之后进行调用。
整体的主要流程就是:

开始编译
处理 Layout 文件
解析表达式
Java 编译
解析依赖
找到 setter

Data Binding 特性

自动空指针检查

Data Binding 会对变量进行空判断,假如说未对给定的变量赋值的话,就会给予变量一个默认的值,比如:
{ user.name } -> null
{ user.age } -> 0

include

Data Binding 支持 include 传递变量,如:

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

但是 Data Binding 并不支持 direct child,例如引入的 layout 根标签为 merge

动态更新数据

现在我们去更新数据的话,就需要查询传递一个类进去,将这个类中的所有值重新进行赋值,这样其实是不好的,效率很低,所以说 Data Binding 提供了几种手段去只刷新更新的值。

BaseObservable

我们的类需要继承 BaseObservable。然后在元素的 get 和 set 方法做出修改,get 方法需要添加注解 @Bindable ,而 set 方法中需要加入刷新显示的代码 notifyPropertyChanged(BR.XXX)

package com.xjh.queryuser.mvvm

import android.databinding.BaseObservable
import android.databinding.Bindable
import android.view.View

import com.xjh.queryuser.bean.Account
import com.xjh.queryuser.callback.MCallback

import com.xjh.queryuser.BR

class MVVMViewModel : BaseObservable() {

    private val mvvmModel: MVVMModel = MVVMModel()
    @get:Bindable
    var userInput: String? = null
        set(userInput) {
            field = userInput
            notifyPropertyChanged(BR.userInput)
        }
    @get:Bindable
    var result: String? = null
        set(result) {
            field = result
            notifyPropertyChanged(BR.result)
        }

    fun getData(view: View) {
        this.userInput?.let {
            mvvmModel.getAccountData(it, object : MCallback {
                override fun onSuccess(account: Account) {
                    result = "用户账号:" + account.name + " | " + "用户等级:" + account.level
                }

                override fun onFailed() {
                    result = "获取数据失败"
                }
            })
        }
    }
}


Observable Fields

如果我们只有简单的几个变量需要传递的话,为这几个变量封装一个类的话,他的消耗会比较大,那我们应该怎么去做呢。其实就是是在 变量类型前加上 Observable,比如:ObservableBoolean,ObservableByte,ObservableChar … ObservableParcelable。然后修改或者获取值的话就要调用他的 get 或者 set 方法,这样的话,就可以做到动态更新变量。

Observable Collection

如果说我们需要用到一些容器类的话怎么办呢,和 Observable Fields 一样,我们只需要使用 ObservableArrayMap 和 ObservableArrayList 就可以了。

刷新

类或者 Observable 发生改变后,会在下一个帧进行绑定的时候发生改变,如果需要立即刷新的话,可以执行 executePendingBindings() 方法去进行立即执行。
Data Binding 会在本地化变量或者值域,以避免同步的问题发生,但是对于 Collection 是不会的。

生成

Data Binding 生成 Binding 类的规则有两种,一种就是之前说的那种,直接默认生成,下划线分割,大写开头,比如:

activity_main - > ActivityMainBinding

第二种方法的化就是自定义 class:

<layout>
	<data class = "XXX">
		...
	</data>
</layout>

这样就可以生成我们想要的名字的类,直接使用 XXX 就可以了。

RecycleView

我们如何在 RecycleView 中使用 DataBinding 功能呢,这样的话我们就可以省略掉对 viewholder 的控件赋值的一系列操作了,只需要对数据源做相对应的改变就可以实现。
首先我们新建三个布局文件,这样我们就能模拟出两个不同样式的 View 在同一个 list 中的显示了。
activity_list.xml

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

    <data>

        <variable
            name="presenter"
            type="com.xjh.databinding.list.ListActivity.Presenter" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".list.ListActivity">

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{presenter::onClickAddItem}"
            android:text="增加" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{presenter::onClickRemoveItem}"
            android:text="删除" />

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        </android.support.v7.widget.RecyclerView>
    </LinearLayout>
</layout>

item_employee.xml

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

    <data>

        <variable
            name="item"
            type="com.xjh.databinding.Employee" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:layout_width="50dp"
            android:layout_height="wrap_content"
            android:gravity="left"
            android:text="@{item.firstName}" />

        <TextView
            android:layout_width="50dp"
            android:layout_height="wrap_content"
            android:gravity="left"
            android:text="@{item.lastName}" />
    </LinearLayout>
</layout>

item_employee_off.xml

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

    <data>

        <variable
            name="item"
            type="com.xjh.databinding.Employee" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:layout_width="50dp"
            android:layout_height="wrap_content"
            android:gravity="left"
            android:text="@{item.firstName}" />

        <TextView
            android:layout_width="50dp"
            android:layout_height="wrap_content"
            android:gravity="left"
            android:text=" is fired" />
    </LinearLayout>
</layout>

这两个 item 的数据都是通过同一个数据源进行传递的,直接在代码中进行调用这个类的相关属性。
Employee.kt

package com.xjh.databinding

import android.databinding.BaseObservable
import android.databinding.Bindable
import android.databinding.ObservableBoolean

class Employee constructor(firstName: String, lastName: String, isFired: Boolean) : BaseObservable() {
    @get:Bindable
    var firstName: String? = null
        set(userInput) {
            field = userInput
            notifyPropertyChanged(BR.firstName)
        }
    @get:Bindable
    var lastName: String? = null
        set(result) {
            field = result
            notifyPropertyChanged(BR.lastName)
        }
    var isFired = ObservableBoolean()

    init {
        this.firstName = firstName
        this.lastName = lastName
        this.isFired.set(isFired)
    }
}

既然用到了 RecyclerView,那首先就要去实现他的 ViewHolder,使用了 Data Binding 的 ViewHolder 和原有使用的地方有一些不同,不再是传递 View 去进行展示了,而是传递 Binding 对象去进行实现,展现的时候就直接调用 Binding 对象的 root 对象。
BindingViewHolder.kt

package com.xjh.databinding.list

import android.databinding.ViewDataBinding
import android.support.v7.widget.RecyclerView

class BindingViewHolder<T : ViewDataBinding>(private val mBinding: T) : RecyclerView.ViewHolder(mBinding.root) {

    fun getmBinding(): T {
        return mBinding
    }
}

在不使用 DataBinding 的 Adapter 中可能需要重新一项一项的加载数据进去,但是如果使用了 Data Binding 的话,就直接在 set 更新后的对象进行就可以了,其他的功能 Data Binding 都帮我们做好了。
EmployeeAdapter.kt

package com.xjh.databinding.list

import android.content.Context
import android.databinding.DataBindingUtil
import android.databinding.ViewDataBinding
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import com.xjh.databinding.BR
import com.xjh.databinding.Employee
import com.xjh.databinding.R
import java.util.*

class EmployeeAdapter() : RecyclerView.Adapter<BindingViewHolder<*>>() {

    companion object {
        private const val ITEM_VIEW_TYPE_ON = 1
        private const val ITEM_VIEW_TYPE_OFF = 2
    }

    private lateinit var mLayoutInflater: LayoutInflater
    private var mListener: OnItemClickListener? = null
    private lateinit var mEmployeeList: ArrayList<Employee>

    constructor(context: Context) : this() {
        mLayoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
        mEmployeeList = ArrayList()
    }

    override fun getItemViewType(position: Int): Int {
        val employee = mEmployeeList.get(position)
        if (employee.isFired.get()) {
            return ITEM_VIEW_TYPE_OFF
        }
        return ITEM_VIEW_TYPE_ON
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder<*> {
        var binding: ViewDataBinding = if (viewType == ITEM_VIEW_TYPE_ON) {
            DataBindingUtil.inflate(mLayoutInflater, R.layout.item_employee, parent, false)
        } else {
            DataBindingUtil.inflate(mLayoutInflater, R.layout.item_employee_off, parent, false)
        }
        return BindingViewHolder(binding)
    }

    override fun onBindViewHolder(holder: BindingViewHolder<*>, position: Int) {
        val employee = mEmployeeList[position]
        holder.getmBinding().setVariable(BR.item, employee)
        holder.getmBinding().executePendingBindings()
        holder.itemView.setOnClickListener {
            mListener?.onEmployeeClick(employee)
        }
    }

    override fun getItemCount(): Int {
        return mEmployeeList.size
    }

    fun setListener(listener: OnItemClickListener) {
        mListener = listener
    }

    fun addAll(employees: List<Employee>) {
        mEmployeeList.addAll(employees)
        notifyDataSetChanged()
    }

    val mRandom = Random(System.currentTimeMillis())

    fun add(employee: Employee) {
        val position = mRandom.nextInt(mEmployeeList.size + 1)
        mEmployeeList.add(position, employee)
        notifyItemInserted(position)
    }

    fun remove() {
        if (mEmployeeList.size != 0) {
            val position = mRandom.nextInt(mEmployeeList.size + 1)
            mEmployeeList.removeAt(position)
            notifyItemRemoved(position)
        }
    }

    interface OnItemClickListener {
        fun onEmployeeClick(employee: Employee)
    }
}

如何使用的话就和一般的 RecycleView 一样进行使用就可以了
ListActivity.kt

package com.xjh.databinding.list

import android.databinding.DataBindingUtil
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import android.view.View
import android.widget.Toast
import com.xjh.databinding.Employee
import com.xjh.databinding.R
import com.xjh.databinding.databinding.ActivityListBinding

class ListActivity : AppCompatActivity() {

    lateinit var mBinding: ActivityListBinding
    lateinit var mEmployeeAdapter: EmployeeAdapter

    inner class Presenter {
        fun onClickAddItem(view: View) {
            mEmployeeAdapter.add(Employee("X", "JH", false))
        }

        fun onClickRemoveItem(view: View) {
            mEmployeeAdapter.remove()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_list)
        mBinding.presenter = Presenter()
        mBinding.recyclerView.layoutManager = LinearLayoutManager(this)
        mEmployeeAdapter = EmployeeAdapter(this)
        mEmployeeAdapter.setListener(object : EmployeeAdapter.OnItemClickListener {
            override fun onEmployeeClick(employee: Employee) {
                Toast.makeText(this@ListActivity, employee.firstName, Toast.LENGTH_SHORT).show()
            }
        })
        var list = ArrayList<Employee>()
        list.add(Employee("Xiong1", "JH1", false))
        list.add(Employee("Xiong2", "JH2", false))
        list.add(Employee("Xiong3", "JH3", true))
        list.add(Employee("Xiong4", "JH4", false))
        mEmployeeAdapter.addAll(list)
        mBinding.recyclerView.adapter = mEmployeeAdapter
    }
}

自定义属性

在 Android 的开发中我们会用到很多的自定义 View,也就会有一些自定义属性会让我们用到,而这些属性如果 Data Binding 不支持的话我们怎么去做呢?

自动 set 方法

一种就是系统中有方法去 set 这个值,但是在 XML 中没有属性可以去设置,这样的话 Data Binding 就会去自动寻找我们需要的 set 方法了。

<android.support.v4.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor ="@{@color/scrimColor}"/>

这样的话 Data Binding 就会去调用 setScrimColor 方法去改变属性的值。

Binding 方法

就是在一些属性上,他所对应的 set 方法并不是以该属性名为后缀的,使用在 DataBinding 中没有办法顺利的访问到这个方法,所以这里我们就需要对该属性和对应的 set 方法进行映射,我们只需要在一个类之前加下 @BindingMethods 注解,就可以定义这样的一个映射了。
Java 版本

@BindingMethods({
    @BindingMethod(
        type = ImageView.class,
        attribute = "android:tint",
        method = "setImageTintList"),
})

Kotlin 版本

@BindingMethods(
    BindingMethod(
        type = ImageView::class,
        attribute = "android:tint",
        method = "setImageTintList"
    )
)

这样的话,DataBinding 就知道在 XML 文件中使用 android:tint 的属性对应的是应该调用 setImageTintList 方法了。

BindingAdapter

假如我们完全自定义一个 View,我们有自己的属性和方法,我们应该怎么做呢,这里就要使用 BindingAdapter 来实现了。
首先就要定义一个适配器,来进行方法的实现:
DemoBindingAdapter.kt

package com.xjh.databinding.expression

import android.databinding.BindingAdapter
import android.graphics.drawable.Drawable
import android.widget.ImageView
import com.bumptech.glide.Glide

class DemoBindingAdapter {

    companion object {
        @BindingAdapter("app:imageUrl", "app:placeholder")
        @JvmStatic
        fun loadImageFromUrl(view: ImageView, url: String, drawable: Drawable) {
            Glide.with(view.context).load(url).placeholder(drawable).into(view)
        }
    }
}

这样就可以利用这两个属性传递相关变量,然后完成对图片进行展示。
activity_expression.xml

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

    <data>

        <variable
            name="employee"
            type="com.xjh.databinding.Employee" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="150dp"
            android:layout_height="150dp"
            app:imageUrl="@{employee.avatar}"
            app:placeholder="@{@drawable/ic_launcher}" />

    </LinearLayout>
</layout>

ExpressionActivity.kt

package com.xjh.databinding.expression

import android.databinding.DataBindingUtil
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import com.xjh.databinding.Employee
import com.xjh.databinding.R
import com.xjh.databinding.databinding.ActivityExpressionBinding

class ExpressionActivity : AppCompatActivity() {

    private lateinit var binding: ActivityExpressionBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_expression)
        val employee = Employee("X", "JH", false)
        employee.avatar = "https://avatar.csdnimg.cn/D/4/D/1_xjh_shin.jpg"
        binding.employee = employee
    }
}

BindingConversion

有些时候 Android 系统中帮我们实现了相关方法,但是传入的参数并不是符合方法要求的,这时候我们就要将属性进行转换,这里就要用到 BindingConversion 方法了。

<View
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
	android:background="@{isError ? @color/red : @color/white}" />

Java 版本

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
	return new ColorDrawable(color);
}

Kotlin 版本

companion object {
    @BindingConversion
    @JvmStatic
    fun convertColorToDrawable(color: Int): ColorDrawable {
        return ColorDrawable(color)
    }
}
双向绑定

假如我们想要监听输入框的值怎么办呢?这里单纯的用单向绑定就没有办法实现效果了,就要使用到 DataBinding 的双向绑定,其实现在 DataBinding 实现双向绑定其实很简单,就是将 XML 文件中的 @ 改为 @= 就实现了数据的双向绑定。

<EditText
	android:id="@+id/userName"
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
	android:layout_margin="10dp"
	android:hint="请输入用户名"
	android:text="@={viewModel.userInput}"
	android:textSize="14sp" />

绑定方式就是和单向绑定是一样的,这里就不再重复说明了。
但是双向绑定并不是可以支持所有属性,他主要是用于那些带有额外事件的属性,比如:text,checked,year,month,hour,rating,progress 等。

表达式链
重复表达式

当我们有很多的 View 需要用到同一个表达式运算的结果进行显示的话,我们可能需要在这些 View 的属性上重复的写同一个表达式,这样的话就导致代码较为累赘,其实我们就可以直接用之前计算好的属性给他进行赋值,这样的话就避免了多次的计算。

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

    <data>

        <import type="android.view.View" />

        <variable
            name="employee"
            type="com.xjh.databinding.Employee" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/avatar"
            android:layout_width="150dp"
            android:layout_height="150dp"
            android:visibility="@{employee.isFired() ? View.INVISIBLE : View.VISIBLE}"
            app:imageUrl="@{employee.avatar}"
            app:placeholder="@{@drawable/ic_launcher}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{employee.firstName}"
            android:visibility="@{avatar.visibility}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{employee.lastName}"
            android:visibility="@{avatar.visibility}" />

    </LinearLayout>
</layout>
隐式更新

如果我们需要绑定两个 View,一个 View 的样式改变依赖于另一个 View 的结果,这样的话我们就需要去监听这个 View 的值,然后手动去改变另一个 View,在 Data Binding 中我们就直接使用隐式更新就可以了。

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

    <data>
        <import type="android.view.View" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        
        <CheckBox
            android:id="@+id/seeAds"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="@{seeAds.checked ? View.INVISIBLE : View.VISIBLE}" />
    </LinearLayout>
</layout>
动画

当有一个 View 要随着选择的状态进行显示的时候,如果我们直接进行刷新的话就会让整个用户的体验很差,所以要使用动画的效果来优化整个体验,而 DataBinding 已经帮我们实现了动画效果,只需要我们实现 OnRebindCallback 回调就可以进行实现了。
activity_animation.xml

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

    <data>

        <import type="android.view.View" />

        <variable
            name="presenter"
            type="com.xjh.databinding.animation.AnimationActivity.Presenter" />

        <variable
            name="showImage"
            type="boolean" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ImageView
            android:layout_width="150dp"
            android:layout_height="150dp"
            android:src="@drawable/ic_launcher"
            android:visibility="@{showImage ? View.VISIBLE : View.GONE}" />

        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onCheckedChanged="@{presenter.onCheckedChanged}"
            android:text="show Image" />

    </LinearLayout>
</layout>

AnimationActivity.kt

package com.xjh.databinding.animation

import android.databinding.DataBindingUtil
import android.databinding.OnRebindCallback
import android.os.Build
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.annotation.RequiresApi
import android.transition.TransitionManager
import android.view.View
import android.view.ViewGroup
import com.xjh.databinding.R
import com.xjh.databinding.databinding.ActivityAnimationBinding

class AnimationActivity : AppCompatActivity() {

    private lateinit var binding: ActivityAnimationBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_animation)
        binding.presenter = Presenter()
        binding.addOnRebindCallback(object : OnRebindCallback<ActivityAnimationBinding>() {
            @RequiresApi(Build.VERSION_CODES.KITKAT)
            override fun onPreBind(binding: ActivityAnimationBinding?): Boolean {
                val view = binding?.root as ViewGroup
                TransitionManager.beginDelayedTransition(view)
                return true
            }
        })
    }


    inner class Presenter {
        fun onCheckedChanged(view: View, isChecked: Boolean) {
            binding.showImage = isChecked
        }
    }
}


项目GitHub地址:传送门

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值