从这篇文章开始,就和大家一起来学习下 Android Jetpack 架构组件,这篇是系列篇一 Data Binding,下面就一起来学习下吧。
很简单,只需要在app的build.gradle中设为启用即可。
android {
...
dataBinding {
enabled = true
}
}
布局文件activity_main.xml代码如下, data里定义的绑定数据可以在activity中获取和赋值,布局中可以通过@{}
表达式来使用,layout下层最多只能包含一个data和一个View布局。
<layout xmlns:android="http://schemas.android.com/apk/res/android" >
<data>
<variable name="hello" type="String" />
data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/helloTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{hello}"
android:textAllCaps="false"/>
LinearLayout>
layout>
MainActivity代码如下,这里Activity使用的是kotlin语言写的, 与java变化其实也不大,不太熟悉kotlin的朋友,可以慢慢熟悉一下。这里通过DataBindingUtil.setContentView
来设置contentView以及获取Binding对象,通过Binding对象就可以对布局文件里的data和view进行操作了。
package com.soaic.hellojetpack
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import com.soaic.hellojetpack.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var mBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
mBinding.hello = "Hello World!"
}
}
上面我们只需要对data里的hello属性赋值,因为helloTv的text绑定了hello, 所以text就能同步hello的值,以上就完成了一个Hello World例子!
通过DataBindingUtil对Activity设置ContentView并获取
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
通过layout自动生成的Binding类来获取实例
val binding: ActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater())
Fragment、ListView 或 RecycleView 适配器获取Binding实例
val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false)
// or
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)
在表达式中可以使用以下运算符和关键字:
基本运算 + - / * %
字符串连接 +
逻辑 && ||
二进制 & | ^
一元 + - ! ~
转移 >> >>> <<
比较 == > < >= <=(注意<需要转义为<)
instanceof
分组 ()
文字 - 字符,字符串,数字, null
强转
方法调用
属性访问
数组访问 []
三元运算符 ?:
As Follows:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
空指针合并操作符: (??) 如果左边不为null则选择左边,否则选择右边。
android:text="@{user.displayName ?? user.lastName}"
or
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
数据类型
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
资源表达式
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
android:text="@{@string/nameFormat(firstName, lastName)}"
对应string.xml
Full Name: %1$s:%2$s
事件处理
添加事件MyHandlers类, 也可以直接用系统自带类,不需要创建,如android.view.View.OnClickListener
class MyHandlers {
fun onClickFriend(view: View) { ... }
}
在xml中使用
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.MyHandlers"/>
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:onClick="@{handlers::onClickFriend}"/>
LinearLayout>
layout>
在activity中设置
var mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
mBinding.handlers = object : MyHandlers {
override fun onClickFriend(view: View) {
...
}
}
监听处理
添加Presenter
class Presenter {
fun onSaveClick(task: Task){}
}
在xml中使用
<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>
上面例子中onClick代码省略了view参数,实际上可以写成
android:onClick="@{(view) -> presenter.onSaveClick(task)}"
如果需要使用view这个参数,可以更改代码 As Follows
class Presenter {
fun onSaveClick(view: View, task: Task){}
}
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
如果需要传递多个参数,可以使用lambda表达式 As Follows:
class Presenter {
fun onCompletedChanged(task: Task, completed: Boolean){}
}
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />
导入class
<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}"/>
类型的别名, 如果设置了别名就不能使用类名
<import type="android.view.View"/>
<import type="com.example.User"
alias="Vista"/>
<variable name="user" type="Vista" />
导入其它class
<data>
<import type="com.example.User"/>
<import type="java.util.List"/>
<variable name="user" type="User"/>
<variable name="userList" type="List<User>"/>
data>
强转
<TextView
android:text="@{((User)(user.connection)).lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
使用静态方法
<data>
<import type="com.example.MyStringUtils"/>
<variable name="user" type="com.example.User"/>
data>
<TextView
android:text="@{MyStringUtils.capitalize(user.lastName)}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
变量
<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>
导入,注意LinearLayout层级节点不能是merge
<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>
如果需要改变对象值,绑定布局的数据也跟着改变,这时就需要用到可观察数据对象,系统提供了如下几种对象属性:
具体使用
class User {
var firstName = ObservableField<String>()
var lastName = ObservableField<String>()
var age = ObservableInt()
}
这时直接改变绑定的数据值,布局中的数据也会跟着同步
user.firstName = "Google"
user.age = ObservableInt(1)
有三种设置class的方式
com.soaic.hellojetpack
, 那么在自动生成的完整类名为com.soaic.hellojetpack.ContactItem
<data class="ContactItem">
…
data>
<data class=".ContactItem">
…
data>
<data class="com.soaic.hellojetpack.ContactItem">
…
data>
首先我们先要了解到,在layout中某个属性绑定Data数据后,每当数据改变时,都会调用默认的一个set方法,如:当绑定的是android:text
,则会调用setText()
方法,我们可以看下官方的一段代码,As Follows:
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}">
上面代码中app:scrimColor
和app:drawerListener
会自动调用setScrimColor
和setDrawerListener
方法。这时我们在想,可不可以自己定义调用的方法,答案是可以的。就需要要用到BindingMethod注解了。
自定义XImageView,这里在类作用域上声明BindingMethods注解,接收的参数是一个数组,说明可以定义多个BindingMethod,type为class类型,attribute为layout中属性,method为在类中被调用的方法名,As Follows:
@BindingMethods(value = [BindingMethod(type = ImageView::class, attribute = "android:tint", method = "setImageTintList")])
class XImageView : ImageView {
constructor(context: Context?): this(context, null)
constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int): super(context, attrs, defStyleAttr)
fun setImageTintList(tint: Int) {
Log.d("Tag", "tint=$tint")
}
}
在Layout中使用
<data>
<variable name="hello" type="Integer"/>
data>
<com.soaic.hellojetpack.view.XImageView
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:tint="@{hello}"/>
这时我们又想,能不能自定义方法并且不在自定义类中,在Activity中呢,或者一个方法能不能接收多个参数,这时就要用到BindAdapter注解了,As Follows:
companion object {
@JvmStatic @BindingAdapter(value = ["imageUrl", "error"], requireAll = false)
fun loadImage(view: ImageView, url: String, error: Drawable) {
Log.d("Tag","url=$url")
}
@JvmStatic @BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) {
Log.d("Tag","$oldPadding=oldPadding---newPadding=$newPadding")
}
}
companion object
为静态区域块,@JvmStatic
标记为静态方法,requireAll = false
为value中的属性都是可选的,另外setPaddingLeft
方法中,oldPadding
为旧数据,newPadding
为新数据,名称可以自定义
Tip: 上面imageUrl和error属性都是新自定义的,所以需要在attrs.xml中声明,否则会找不到属性
<resources>
<attr name="imageUrl" format="reference|string"/>
<attr name="error" format="reference"/>
resources>
双向绑定可以理解为当改变了布局layout中的值,绑定的Data属性值也会同一时间改变,实现 AS Follows:
class LoginViewModel : BaseObservable() {
private var rememberMe: String = ""
@Bindable
fun getRememberMe(): String {
return rememberMe
}
fun setRememberMe(value: String) {
if (rememberMe != value) {
rememberMe = value
// Notify observers of a new value.
notifyPropertyChanged(BR.rememberMe)
}
}
}
以上代码继承了BaseObservable
, 在get方法上添加@Bindable
注解,set方法中添加notifyPropertyChanged
方法通知新值改变,在layout布局代码中实现 AS Follows:
<data>
<variable name="loginViewModel" type="com.soaic.hellojetpack.model.LoginViewModel"/>
data>
<EditText
android:layout_width="wrap_content"
android:text="@={loginViewModel.rememberMe}"
android:layout_height="wrap_content"/>
最后效果就是,当EditText的text值改变了,data中的loginViewModel.rememberMe
值也同一时间改变,当loginViewModel.rememberMe
值改变时,EditText的text值也同一时间改变。
好了,DataBinding基本内容就是这些,以上内容参考自官网:https://developer.android.com/topic/libraries/data-binding
有任何疑惑都可以留言,相互学习进步~