之前讲过 Android 中的框架:传送门,里面有一个 MVVM 框架,在 MVVM 框架中就用到了 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 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");
这里就要说到 xml 文件中的 标签了。我们需要在 xml 文件中定义变量,然后在 Activity 中绑定这个数据,最后直接在控件中使用该变量,就可以达到 Activity 中数据发生改变,控件中的数据也会随之改变的效果。然后因为我们不需要在外部通过 ID 去修改控件中的值了,所以为了防止这种情况的发生,我们也可以将 xml 中控件的 ID 删去。
<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>
然后方法我们是不是也可以这样做呢,当然是可以的,总共有两种绑定方式:
我们只要传递进一个类,类中有我们需要调用的方法,我们通过控件的事件属性去调用这个方法就可以实现了,这样我们就可以使得外部更加干净。
事件属性如:
下面就拿 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="提交" />
其实我们从之前的使用中可以发现,和 DataBinding 有关的主要是有下面三种:
实现我们可以看到我们的入口函数,我们是通过 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();
}
这里我们就能发现,我们将控件放入对应的数组中,方便之后进行调用。
整体的主要流程就是:
Data Binding 会对变量进行空判断,假如说未对给定的变量赋值的话,就会给予变量一个默认的值,比如:
{ user.name } -> null
{ user.age } -> 0
Data Binding 支持 include 传递变量,如:
但是 Data Binding 并不支持 direct child,例如引入的 layout 根标签为 merge
现在我们去更新数据的话,就需要查询传递一个类进去,将这个类中的所有值重新进行赋值,这样其实是不好的,效率很低,所以说 Data Binding 提供了几种手段去只刷新更新的值。
我们的类需要继承 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,比如:ObservableBoolean,ObservableByte,ObservableChar … ObservableParcelable。然后修改或者获取值的话就要调用他的 get 或者 set 方法,这样的话,就可以做到动态更新变量。
如果说我们需要用到一些容器类的话怎么办呢,和 Observable Fields 一样,我们只需要使用 ObservableArrayMap 和 ObservableArrayList 就可以了。
类或者 Observable 发生改变后,会在下一个帧进行绑定的时候发生改变,如果需要立即刷新的话,可以执行 executePendingBindings()
方法去进行立即执行。
Data Binding 会在本地化变量或者值域,以避免同步的问题发生,但是对于 Collection 是不会的。
Data Binding 生成 Binding 类的规则有两种,一种就是之前说的那种,直接默认生成,下划线分割,大写开头,比如:
activity_main - > ActivityMainBinding
第二种方法的化就是自定义 class:
<layout>
...
data>
layout>
这样就可以生成我们想要的名字的类,直接使用 XXX 就可以了。
我们如何在 RecycleView 中使用 DataBinding 功能呢,这样的话我们就可以省略掉对 viewholder 的控件赋值的一系列操作了,只需要对数据源做相对应的改变就可以实现。
首先我们新建三个布局文件,这样我们就能模拟出两个不同样式的 View 在同一个 list 中的显示了。
activity_list.xml
<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
<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
<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 这个值,但是在 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 方法去改变属性的值。
就是在一些属性上,他所对应的 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 方法了。
假如我们完全自定义一个 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
<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
}
}
有些时候 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 的属性上重复的写同一个表达式,这样的话就导致代码较为累赘,其实我们就可以直接用之前计算好的属性给他进行赋值,这样的话就避免了多次的计算。
<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 中我们就直接使用隐式更新就可以了。
<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
<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地址:传送门