Android Jetpack 系列篇(一) Data Binding

从这篇文章开始,就和大家一起来学习下 Android Jetpack 架构组件,这篇是系列篇一 Data Binding,下面就一起来学习下吧。

环境配置

很简单,只需要在app的build.gradle中设为启用即可。

android {
    ...
    dataBinding {
        enabled = true
    }
}

Hello World

布局文件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例子!

布局binding获取绑定

通过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>

处理可观察数据对象-单向绑定

如果需要改变对象值,绑定布局的数据也跟着改变,这时就需要用到可观察数据对象,系统提供了如下几种对象属性:

  • ObservableBoolean
  • ObservableByte
  • ObservableChar
  • ObservableShort
  • ObservableInt
  • ObservableLong
  • ObservableFloat
  • ObservableDouble
  • ObservableParcelable

具体使用

class User {
    var firstName = ObservableField<String>()
    var lastName = ObservableField<String>()
    var age = ObservableInt()
}

这时直接改变绑定的数据值,布局中的数据也会跟着同步

user.firstName = "Google"
user.age = ObservableInt(1)

自定义binding类名

有三种设置class的方式

  1. 直接设置类名,我们的app的包名为com.soaic.hellojetpack, 那么在自动生成的完整类名为com.soaic.hellojetpack.ContactItem
<data class="ContactItem">data>
  1. 类名前面加".", 与直接设置是一样的效果
<data class=".ContactItem">data>
  1. 直接设置完整类名
<data class="com.soaic.hellojetpack.ContactItem">data>

BindingMethod 和 BindingAdapter

首先我们先要了解到,在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:scrimColorapp:drawerListener会自动调用setScrimColorsetDrawerListener方法。这时我们在想,可不可以自己定义调用的方法,答案是可以的。就需要要用到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

有任何疑惑都可以留言,相互学习进步~

你可能感兴趣的:(Android,学习)