Android架构组件中的DataBingding

DataBingding的使用

首先设置环境

https://developer.android.google.cn/studio/intro/update

1
2
3
4
5
6
android {
...
dataBinding {
enabled = true
}
}

AS 3.1 对DataBinding支持了编译时错误查询,需要升级AS并且在gradle.properties中加入

1
databinding.enableV2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
</layout>

在activity中绑定xml的方法

1
2
3
4
5
6
7
8
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val binding: ActivityMainBinding = DataBindingUtil.setContentView(
this, R.layout.activity_main)

binding.user = User("Test", "User")
}

在adapter中使用dataBinding

1
2
3
val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false)
// or
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)

可以使用的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Mathematical + - / * %
String concatenation +
Logical && ||
Binary & | ^
Unary + - ! ~
Shift >> >>> <<
Comparison == > < >= <=
instanceof
Grouping ()
Literals - character, String, numeric, null
Cast
Method calls
Field access
Array access []
Ternary operator ?:

例如:

1
2
3
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

也有类似于kotlin的evli 操作符

1
android:text="@{user.displayName ?? user.lastName}"

类似于三元操作符

1
android:text="@{user.displayName != null ? user.displayName : user.lastName}"

列表的使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<String, String>"/>
<variable name="index" type="int"/>
<variable name="key" type="String"/>
</data>

android:text="@{list[index]}"

android:text="@{sparse[index]}"

android:text="@{map[key]}"
1
2
3
@{map[key]}
等价于
@{map.key}

表示String的值,单双引号交叉

1
2
android:text='@{map["firstName"]}'
android:text="@{map[`firstName`]}"

表达资源的话可以用

1
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

String的format可以利用参数来提供

1
2
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"

如果复数有多个的话可以用

1
2
3
4
5

Have an orange
Have %d oranges

android:text="@{@plurals/orange(orangeCount, orangeCount)}"

这些类型需要显示的显示评估类型

1
2
3
4
5
6
7
8
Type	    Normal reference	Expression reference
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

处理表达式

你可以通过两种方式来处理表达式

第一种:方法引用

比直接在view中设置onclick的好处是,如果语法错误,会在编译时报错
怎么使用?

首先定义一个类里面处理handler
那么view就是相对应的TextView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MyHandlers {
fun onClickFriend(view: View) { ... }
}
再将此类引用到xml当中
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.MyHandlers"/>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
android:onClick="@{handlers::onClickFriend}"/>
</LinearLayout>
</layout>

tips:这里的事件是用::来表示的

第二种:绑定监听器

这种绑定是在事件发生的时候进行绑定的,它可以对任何数据进行绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Presenter {
fun onSaveClick(task: Task){}
}

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="task" type="com.android.example.Task" />
<variable name="presenter" type="com.android.example.Presenter" />
</data>
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
<Button android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onClick="@{() -> presenter.onSaveClick(task)}" />
</LinearLayout>
</layout

暂时还没搞明白怎么弄

import标签

1
2
3
4
5
6
7
8
9
10
<data>
<import type="android.view.View"/>
</data>


<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>

别名

遇到相同的类名,可以用别名来标识

1
2
3
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>

引用的type可以用于别的type比如list

1
2
3
4
5
6
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
</data>

类型转换也是可以用的哦

1
2
3
4
<TextView
android:text="@{((User)(user.connection)).lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

静态的字段,方法也是可以直接使用的哦

variable

1
2
3
4
5
6
<data>
<import type="android.graphics.drawable.Drawable"/>
<variable name="user" type="com.example.User"/>
<variable name="image" type="Drawable"/>
<variable name="note" type="String"/>
</data>

Includes

变量在include是可以通过bind属性进行传递的而merge不行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/name"
bind:user="@{user}"/>
<include layout="@layout/contact"
bind:user="@{user}"/>
</LinearLayout>
</layout>

observable data objects

在项目中可以用以下的变量标识Overvable

1
2
3
4
5
6
7
8
9
ObservableBoolean
ObservableByte
ObservableChar
ObservableShort
ObservableInt
ObservableLong
ObservableFloat
ObservableDouble
ObservableParcelable

使用livedata

现在可以用Livedata去代替原来的Observable
如果在代码里面动态创建了map,则可以在xml中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
val user = ObservableArrayMap<String, Any>()
user.put("firstName", "Google")
user.put("lastName", "Inc.")
user.put("age", 17)


<data>
<import type="android.databinding.ObservableMap"/>
<variable name="user" type="ObservableMap<String, Object>"/>
</data>

<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text="@{String.valueOf(1 + (Integer)user.age)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

比如在一个类中定义一些索引
可以通过索引来获取具体的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
val user = ObservableArrayList<Any>()
user.add("Google")
user.add("Inc.")
user.add(17)

<data>
<import type="android.databinding.ObservableList"/>
<import type="com.example.my.app.Fields"/>
<variable name="user" type="ObservableList<Object>"/>
</data>

<TextView
android:text='@{user[Fields.LAST_NAME]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

创建可以通知的binding
Bindable 注解在BR文件中生成一个条目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class User : BaseObservable() {
var firstName: String? = null
@Bindable get() = field
set(firstName) {
field = firstName
notifyPropertyChanged(BR.firstName)
}
var lastName: String? = null
@Bindable get() = field
set(lastName) {
field = lastName
notifyPropertyChanged(BR.lastName)
}
}

自动适配方法

调用settext方法
如果user.name()是String IDE查找并赋值,如果是int则自动查找int参数
可能需要类型转换

1
android:text="@{user.name}"

如果没有方法,可以自动查找方法

1
2
3
4
5
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}">

有些方法的set和名字不匹配,那么用注解@BindingMethods来标识
下面的例子中,android:tint与setImageTintList(ColorStateList) 方法关联而不是setTint() 方法

1
2
3
4
5
@BindingMethods(value = [
BindingMethod(
type = android.widget.ImageView::class,
attribute = "android:tint",
method = "setImageTintList")])

自定义逻辑

例如android:paddingLeft没有这个方法,在android中只有setPadding(left, top, right, bottom)方法,那么BindingAdapter可以允许你自定义setting方法

1
2
3
4
5
6
7
@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, padding: Int) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom())
}

自定义适配器将覆盖原有的适配器
如果加载框架,如加载图片:

1
2
3
4
@BindingAdapter({"imageUrl", "error"})
public static void loadImage(ImageView view, String url, Drawable error) {
Picasso.with(view.getContext()).load(url).error(error).into(view);
}

自定义适配器,如此方法,如果是图片需要标记@{}

1
2
<ImageView  app:imageUrl="@{venue.imageUrl}"
app:error="@{@drawable/venueError}" />

调用只会在imageUrl和error设置的时候调用,如果需要再所有情况下调用,需要指定requireAll = false

1
2
3
4
5
6
7
8
@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false)
fun setImageUrl(imageView: ImageView, url: String, placeHolder: Drawable) {
if (url == null) {
imageView.setImageDrawable(placeholder);
} else {
MyImageLoader.loadInto(imageView, url, placeholder);
}
}

可以指定旧值与新值

1
2
3
4
5
6
7
8
9
@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) {
if (oldPadding != newPadding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom())
}
}

但是只能作用于一个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
@BindingAdapter("android:onLayoutChange")
fun setOnLayoutChangeListener(view: View, oldValue: View.OnLayoutChangeListener, newValue: View.OnLayoutChangeListener) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
oldValue?.let {
view.removeOnLayoutChangeListener(oldValue);
}
newValue?.let {
view.addOnLayoutChangeListener(newValue);
}
}
}

<View android:onLayoutChange="@{() -> handler.layoutChanged()}"/>

如果一个监听器有多个方法,那么必须指定多个方法比如
View.OnAttachStateChangeListener 有onViewAttachedToWindow(View)和onViewDetachedFromWindow(View)
那么必须同时指定

1
2
3
4
5
6
7
8
9
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
void onViewDetachedFromWindow(View v);
}

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
void onViewAttachedToWindow(View v);
}

举个例子,还是不要用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@BindingAdapter(value = ["android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"], requireAll = false)
fun setListener(view:View, detach: ViewBindingAdapter.OnViewDetachedFromWindow?, attach: ViewBindingAdapter.OnViewAttachedToWindow?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
val newListener: OnAttachStateChangeListener? =
if (detach == null && attach == null) {
null
} else {
object: OnAttachStateChangeListener {
override fun onViewDetachedFromWindow(v: View?) {
attach?.let {
attach.onViewAttachedToWindow(v)
}
}

override fun onViewAttachedToWindow(v: View?) {
detach?.let {
detach.onViewDetachedFromWindow(v)
}
}

}
}

newListener?.let {
val oldListener: OnAttachStateChangeListener? = ListenerUtil.trackListener(view, newListener,
R.id.onAttachStateChangeListener)
oldListener?.let {
view.removeOnAttachStateChangeListener(oldListener)
}
view.addOnAttachStateChangeListener(newListener)
}
}
}

@BindingConversion

DataBindg使用的时候
自定义转换类
比如

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

这种情况由于使用了DataBinding那么在使用的时候background模式是设置Drawable的,就需要转换类了,如:

1
2
3
4
@BindingConversion
fun convertColorToDrawable(color: Int): ColorDrawable {
return ColorDrawable(color)
}

架构组件

和架构组件的结合之绑定声明周期

1
2
3
4
5
6
7
8
9
class ViewModelActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Inflate view and obtain an instance of the binding class.
val binding: UserBinding = DataBindingUtil.setContentView(this, R.layout.user)

// Specify the current activity as the lifecycle owner.
binding.setLifecycleOwner(this)
}
}

将ViewModel作为DataBind的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
class ViewModelActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Obtain the ViewModel component.
UserModel userModel = ViewModelProviders.of(getActivity())
.get(UserModel.class)

// Inflate view and obtain an instance of the binding class.
val binding: UserBinding = DataBindingUtil.setContentView(this, R.layout.user)

// Assign the component to a property in the binding class.
binding.viewmodel = userModel
}
}

在xml中可以将属性分配给

1
2
3
4
<CheckBox
android:id="@+id/rememberMeCheckBox"
android:checked="@{viewmodel.rememberMe}"
android:onCheckedChanged="@{() -> viewmodel.rememberMeChanged()}" />

ViewModel继承Observable去接收通知来更新UI,你可以自己继承UI的更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* A ViewModel that is also an Observable,
* to be used with the Data Binding Library.
*/
open class ObservableViewModel : ViewModel(), Observable {
private val callbacks: PropertyChangeRegistry = PropertyChangeRegistry()

override fun addOnPropertyChangedCallback(
callback: Observable.OnPropertyChangedCallback) {
callbacks.add(callback)
}

override fun removeOnPropertyChangedCallback(
callback: Observable.OnPropertyChangedCallback) {
callbacks.remove(callback)
}

/**
* Notifies observers that all properties of this instance have changed.
*/
fun notifyChange() {
callbacks.notifyCallbacks(this, 0, null)
}

/**
* Notifies observers that a specific property has changed. The getter for the
* property that changes should be marked with the @Bindable annotation to
* generate a field in the BR class to be used as the fieldId parameter.
*
* @param fieldId The generated BR id for the Bindable field.
*/
fun notifyPropertyChanged(fieldId: Int) {
callbacks.notifyCallbacks(this, fieldId, null)
}
}

谢谢您的鼓励~