前提
之前講過 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>
然後方法我們是不是也可以這樣做呢,當然是可以的,總共有兩種綁定方式:
- 方法引用
- 監聽器綁定
方法引用
我們只要傳遞進一個類,類中有我們需要調用的方法,我們通過控件的事件屬性去調用這個方法就可以實現了,這樣我們就可以使得外部更加乾淨。
事件屬性如:
- 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();
}
這裏我們就能發現,我們將控件放入對應的數組中,方便之後進行調用。
整體的主要流程就是:
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地址:傳送門