一、概览
数据绑定库(dataBinding)是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。
二、使用入门
要将应用配置为使用数据绑定,请在应用模块的 build.gradle 文件中添加 dataBinding 元素
dataBinding {
enabled = true
}
三、布局和绑定表达式
数据绑定布局文件略有不同,以根标记 layout开头,后跟 data 元素和 view 根元素。此视图元素是非绑定布局文件中的根。
User
data class User(val firstName: String, val lastName: String) {
var like: Int = 0
}
activity_normal
<?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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="user"
type="com.anniljing.jetpackviewmodel.model.User" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.NormalActivity">
<TextView
android:id="@+id/normalName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<TextView
android:id="@+id/normalLike"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/normalName"
app:layout_constraintTop_toTopOf="@id/normalName"
android:text="@{Integer.toString(user.like)}"
android:layout_marginLeft="20dp"/>
<Button
android:id="@+id/addLikeBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="@id/normalName"
app:layout_constraintTop_toBottomOf="@id/normalName"
android:text="Like"
android:onClick="addLike"/>
<TextView
android:id="@+id/currentLike"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="@id/addLikeBtn"
app:layout_constraintTop_toBottomOf="@id/addLikeBtn"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
data标签
data标签下面有两种子级标签:import和variable,variable 标签为变量,类似于我们定义了一个变量,name 为变量名,type 为变量全限定类型名,包括包名。布局中通过 @{} 来引用这个变量的值,{} 中可以是任意 Java 表达式;
<variable
name="user"
type="com.anniljing.jetpackviewmodel.model.User" />
同样,我们还可以使用import来定义变量
<import
alias="User"
type="com.anniljing.jetpackviewmodel.model.User" />
<variable
name="user"
type="User" />
NormalActivity
class NormalActivity : AppCompatActivity() {
private var binding:ActivityNormalBinding ?=null
private val user=User("张三","李四")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding=DataBindingUtil.setContentView(this,R.layout.activity_normal)
user.like=1
binding!!.user=user
}
fun addLike(view: View) {
user.like=user.like+1
binding!!.currentLike.text="当前点赞量:"+user.like
}
}
上面截图可以看出,无论怎么增加点赞数,都不会及时的“更新”到UI上,有没有方案实现动态更新UI呢?答案是肯定的,使用可观察的数据对象。
使用可观察的数据对象
可观察性是指一个对象将其数据变化告知其他对象的能力。通过数据绑定库,您可以让对象、字段或集合变为可观察。当其中一个可观察数据对象绑定到界面并且该数据对象的属性发生更改时,界面会自动更新。
可观察字段
在创建实现 Observable 接口的类时要完成一些操作,但如果您的类只有少数几个属性,这样操作的意义不大。在这种情况下,您可以使用通用 Observable 类和以下特定于基元的类,将字段设为可观察字段:
- ObservableBoolean
- ObservableBoolean
- ObservableByte
- ObservableChar
- ObservableShort
- ObservableInt
- ObservableLong
- ObservableFloat
- ObservableDouble
- ObservableParcelable
ObservableUser
package com.anniljing.jetpackviewmodel.model
import androidx.databinding.ObservableInt
data class ObservableUser(val firstName:String, val like:ObservableInt)
activity_observable_normal
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.anniljing.jetpackviewmodel.model.ObservableUser" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/normalLike"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:text="@{Integer.toString(user.like)}"/>
<Button
android:id="@+id/addLikeBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="addLike"
android:text="Like" />
<TextView
android:id="@+id/currentLike"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</layout>
ObservableNormalActivity
package com.anniljing.jetpackviewmodel.activity
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.databinding.ObservableInt
import com.anniljing.jetpackviewmodel.R
import com.anniljing.jetpackviewmodel.databinding.ActivityObservableNormalBinding
import com.anniljing.jetpackviewmodel.model.ObservableUser
class ObservableNormalActivity : AppCompatActivity() {
private lateinit var binding: ActivityObservableNormalBinding
private val observableUser = ObservableUser("张三", ObservableInt(0))
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding =
DataBindingUtil.setContentView(this, R.layout.activity_observable_normal)
binding.user = observableUser
}
fun addLike(view: View) {
observableUser.like.set(observableUser.like.get() + 1)
binding!!.currentLike.text = "当前点赞量:" + observableUser.like.get()
}
}
可观察对象
实现 Observable 接口的类允许注册监听器,以便它们接收有关可观察对象的属性更改的通知。Observable 接口具有添加和移除监听器的机制,但何时发送通知必须由您决定。为便于开发,数据绑定库提供了用于实现监听器注册机制的 BaseObservable 类。实现 BaseObservable 的数据类负责在属性更改时发出通知。具体操作过程是向 getter 分配 Bindable 注释,然后在 setter 中调用 notifyPropertyChanged() 方法
CustomObservableUser
package com.anniljing.jetpackviewmodel.model
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import androidx.databinding.library.baseAdapters.BR
class CustomObservableUser : BaseObservable() {
@get:Bindable
var firstName: String = ""
set(value) {
field = value
notifyPropertyChanged(BR.firstName)
}
@get:Bindable
var like: Int = 0
set(value) {
field = value
notifyPropertyChanged(BR.like)
}
}
PS:
1、要导入androidx.databinding.library.baseAdapters.BR
2、如果在notifyPropertyChanged的参数中无法BR指向某个属性时,可以执行android studio工具栏build---->make progect
activity_observable_custom
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.anniljing.jetpackviewmodel.model.CustomObservableUser" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:id="@+id/normalLike"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:text="@{Integer.toString(user.like)}"/>
<Button
android:id="@+id/addLikeBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="addLike"
android:text="Like" />
<TextView
android:id="@+id/currentLike"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</layout>
ObservableCustomActivity
package com.anniljing.jetpackviewmodel.activity
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.databinding.ObservableInt
import com.anniljing.jetpackviewmodel.R
import com.anniljing.jetpackviewmodel.databinding.ActivityObservableCustomBinding
import com.anniljing.jetpackviewmodel.databinding.ActivityObservableNormalBinding
import com.anniljing.jetpackviewmodel.model.CustomObservableUser
import com.anniljing.jetpackviewmodel.model.ObservableUser
class ObservableCustomActivity : AppCompatActivity() {
private lateinit var binding: ActivityObservableCustomBinding
private val observableUser = CustomObservableUser()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding =
DataBindingUtil.setContentView(this, R.layout.activity_observable_custom)
binding.user = observableUser
}
fun addLike(view: View) {
observableUser.like=observableUser.like+ 1
binding!!.currentLike.text = "当前点赞量:" + observableUser.like
}
}
四、绑定适配器
绑定适配器负责发出相应的框架调用来设置值。例如,设置属性值就像调用 setText() 方法一样。再比如,设置事件监听器就像调用 setOnClickListener() 方法。
数据绑定库允许您通过使用适配器指定为设置值而调用的方法、提供您自己的绑定逻辑,以及指定返回对象的类型。
指定自定义方法名称
一些属性具有名称不符的 setter 方法。在这些情况下,某个特性可能会使用 BindingMethods 注释与 setter 相关联。注释与类一起使用,可以包含多个 BindingMethod 注释,每个注释对应一个重命名的方法。绑定方法是可添加到应用中任何类的注释。在以下示例中,android:tint 属性与 setImageTintList(ColorStateList) 方法相关联,而不与 setTint() 方法相关联
/**
* Applies a tint to the image drawable. Does not modify the current tint
* mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
* <p>
* Subsequent calls to {@link #setImageDrawable(Drawable)} will automatically
* mutate the drawable and apply the specified tint and tint mode using
* {@link Drawable#setTintList(ColorStateList)}.
* <p>
* <em>Note:</em> The default tint mode used by this setter is NOT
* consistent with the default tint mode used by the
* {@link android.R.styleable#ImageView_tint android:tint}
* attribute. If the {@code android:tint} attribute is specified, the
* default tint mode will be set to {@link PorterDuff.Mode#SRC_ATOP} to
* ensure consistency with earlier versions of the platform.
*
* @param tint the tint to apply, may be {@code null} to clear tint
*
* @attr ref android.R.styleable#ImageView_tint
* @see #getImageTintList()
* @see Drawable#setTintList(ColorStateList)
*/
@android.view.RemotableViewMethod
public void setImageTintList(@Nullable ColorStateList tint) {
mDrawableTintList = tint;
mHasDrawableTint = true;
applyImageTint();
}
BindingTintMethods
package com.anniljing.jetpackviewmodel.util
import android.widget.ImageView
import androidx.databinding.BindingMethod
import androidx.databinding.BindingMethods
@BindingMethods(value = [
BindingMethod(
type = ImageView::class,
attribute = "tint",
method = "setImageTintList")])
class BindingTintMethods {
}
activity_observable_binding_methods
<?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="user"
type="com.anniljing.jetpackviewmodel.model.ObservableUser" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{Integer.toString(user.like)}"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_whatshot_black_96dp"
app:tint="@{user.like > 9 ? @color/star : @android:color/black}" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Like"
android:onClick="addLike"/>
</LinearLayout>
</layout>
ObservableBindingMethodsActivity
package com.anniljing.jetpackviewmodel.activity
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.databinding.ObservableInt
import com.anniljing.jetpackviewmodel.R
import com.anniljing.jetpackviewmodel.databinding.ActivityObservableBindingMethodsBinding
import com.anniljing.jetpackviewmodel.model.ObservableUser
class ObservableBindingMethodsActivity : AppCompatActivity() {
private val user: ObservableUser by lazy {
ObservableUser("张三", ObservableInt(2))
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityObservableBindingMethodsBinding =
DataBindingUtil.setContentView(this, R.layout.activity_observable_binding_methods)
binding.user = user
}
fun addLike(view: View) {
user.like.set(user.like.get() + 1)
}
}
提供自定义逻辑
一些属性需要自定义绑定逻辑,Android 框架类的特性已经创建了 BindingAdapter 注释。如果我们想把上面例子的tint属性和src属性通过一个自定义属性来实现:
ObservableBindingAdapterUser
package com.anniljing.jetpackviewmodel.model
import androidx.databinding.ObservableInt
class ObservableBindingAdapterUser {
var like: ObservableInt = ObservableInt(0)
set(value) {
field = value
}
var popularity: ObservableInt by ::like
}
BindingMergeAdapters
package com.anniljing.jetpackviewmodel.util
import android.content.Context
import android.content.res.ColorStateList
import android.widget.ImageView
import androidx.core.content.ContextCompat
import androidx.core.widget.ImageViewCompat
import androidx.databinding.BindingAdapter
import androidx.databinding.ObservableInt
import com.anniljing.jetpackviewmodel.R
object BindingMergeAdapters {
@BindingAdapter("mergeTintAndSrc")
@JvmStatic fun mergeTintAndSrc(imageView: ImageView, mergeType: ObservableInt){
val context:Context=imageView.context
imageView.setImageResource(R.drawable.ic_whatshot_black_96dp)
if (mergeType.get()>4){
ImageViewCompat.setImageTintList(imageView, ColorStateList.valueOf(ContextCompat.getColor(context, R.color.popular)))
}else if (mergeType.get()>9){
ImageViewCompat.setImageTintList(imageView, ColorStateList.valueOf(ContextCompat.getColor(context, R.color.star)))
}
}
}
activity_observable_binding_adapter
<?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="user"
type="com.anniljing.jetpackviewmodel.model.ObservableBindingAdapterUser" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{Integer.toString(user.like)}" />
<ImageView
app:mergeTintAndSrc="@{user.popularity}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="addLike"
android:text="Like" />
</LinearLayout>
</layout>
ObservableBindingAdapterActivity
package com.anniljing.jetpackviewmodel.activity
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.anniljing.jetpackviewmodel.R
import com.anniljing.jetpackviewmodel.databinding.ActivityObservableBindingAdapterBinding
import com.anniljing.jetpackviewmodel.model.ObservableBindingAdapterUser
class ObservableBindingAdapterActivity : AppCompatActivity() {
private val observableUser: ObservableBindingAdapterUser by lazy {
ObservableBindingAdapterUser()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityObservableBindingAdapterBinding =
DataBindingUtil.setContentView(this, R.layout.activity_observable_binding_adapter)
binding.user = observableUser
}
fun addLike(view: View) {
observableUser.like.set(observableUser.like.get() + 1)
}
}