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 文件外再套上一层
标签
<?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 文件中的 标签了。我们需要在 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>
然后方法我们是不是也可以这样做呢,当然是可以的,总共有两种绑定方式:
- 方法引用
- 监听器绑定
方法引用
我们只要传递进一个类,类中有我们需要调用的方法,我们通过控件的事件属性去调用这个方法就可以实现了,这样我们就可以使得外部更加干净。
事件属性如:
- 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
方法中。
@Overridepublic 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 特性
自动空指针检查
Data Binding 会对变量进行空判断,假如说未对给定的变量赋值的话,就会给予变量一个默认的值,比如:
{ user.name } -> null
{ user.age } -> 0
include
Data Binding 支持 include 传递变量,如:
但是 Data Binding 并不支持 direct child,例如引入的 layout 根标签为 merge
动态更新数据
现在我们去更新数据的话,就需要查询传递一个类进去,将这个类中的所有值重新进行赋值,这样其实是不好的,效率很低,所以说 Data Binding 提供了几种手段去只刷新更新的值。
BaseObservable
我们的类需要继承 BaseObservable。然后在元素的 get 和 set 方法做出修改,get 方法需要添加注解 @Bindable
,而 set 方法中需要加入刷新显示的代码 notifyPropertyChanged(BR.XXX)
。
package com.xjh.queryuser.mvvmimport android.databinding.BaseObservableimport android.databinding.Bindableimport android.view.Viewimport com.xjh.queryuser.bean.Accountimport com.xjh.queryuser.callback.MCallbackimport com.xjh.queryuser.BRclass 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>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.databindingimport android.databinding.BaseObservableimport android.databinding.Bindableimport android.databinding.ObservableBooleanclass 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.listimport android.databinding.ViewDataBindingimport android.support.v7.widget.RecyclerViewclass 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.listimport android.content.Contextimport android.databinding.DataBindingUtilimport android.databinding.ViewDataBindingimport android.support.v7.widget.RecyclerViewimport android.view.LayoutInflaterimport android.view.ViewGroupimport com.xjh.databinding.BRimport com.xjh.databinding.Employeeimport com.xjh.databinding.Rimport 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.listimport android.databinding.DataBindingUtilimport android.support.v7.app.AppCompatActivityimport android.os.Bundleimport android.support.v7.widget.LinearLayoutManagerimport android.view.Viewimport android.widget.Toastimport com.xjh.databinding.Employeeimport com.xjh.databinding.Rimport com.xjh.databinding.databinding.ActivityListBindingclass 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.expressionimport android.databinding.BindingAdapterimport android.graphics.drawable.Drawableimport android.widget.ImageViewimport com.bumptech.glide.Glideclass 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.expressionimport android.databinding.DataBindingUtilimport android.support.v7.app.AppCompatActivityimport android.os.Bundleimport com.xjh.databinding.Employeeimport com.xjh.databinding.Rimport com.xjh.databinding.databinding.ActivityExpressionBindingclass 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 方法了。
<Viewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:background="@{isError ? @color/red : @color/white}" />
Java 版本
@BindingConversionpublic 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 文件中的 @ 改为 @= 就实现了数据的双向绑定。
<EditTextandroid: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.animationimport android.databinding.DataBindingUtilimport android.databinding.OnRebindCallbackimport android.os.Buildimport android.support.v7.app.AppCompatActivityimport android.os.Bundleimport android.support.annotation.RequiresApiimport android.transition.TransitionManagerimport android.view.Viewimport android.view.ViewGroupimport com.xjh.databinding.Rimport com.xjh.databinding.databinding.ActivityAnimationBindingclass 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地址:传送门
更多相关文章
- Android大厂一面面试:记录第一次跳槽经历
- Android(安卓)用户界面---XML布局
- Android面试(13): Android(安卓)中返回数据给上一个活动---start
- [Android]Fragment自定义动画、动画监听以及兼容性包使用
- Android(安卓)home键和back键区别
- Android数据存储之SQLite
- Android(安卓)Hook 机制之实战模拟
- Android开发学习之路--Content Provider之初体验
- Android(安卓)自定义SeekBar样式