Android Jetpack - DataBinding

279 阅读5分钟

DataBinding 是数据绑定库,使用声明性格式将布局中的界面组件绑定到应用中的数据源。

首先,从 Android SDK 管理器中的支持代码库下载该库

android {
    ...
    buildFeatures {
        dataBinding true
    }
}  

布局与绑定

先定义一个简单的数据类

data class User(
    val name: String,
    val age: Int
)

数据绑定的布局文件以根标记 layout 开头,后跟 data 元素和 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"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="user"
            type="com.example.myapplication.User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        tools:context=".MainActivity">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name}" />

    </LinearLayout>
</layout>

如果你想在 Android Studio XML 预览中显示默认值,你可以这样设置

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name,default=name}" />

系统会为每个布局文件生成一个绑定类,类名称是根据布局文件的名称。例如布局文件的名称是 activity_main.xml,则对应的类为 ActivityMainBinding,此类包含从布局属性到布局视图的所有绑定,并且知道如何为绑定表达式指定值。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding =
            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        binding.user = User("Mike", 25)
    }
}

生成的数据绑定代码会自动检查有没有 Null 值并避免出现空指针异常。例如,在表达式 @{user.name} 中,如果 user 为 Null,则为 user.name 分配默认值 null。如果您引用 user.age,其中 age 的类型为 Int,则数据绑定使用默认值0。

我们也可以通过 LayoutInflater 获取视图,例如,在 Fragment 中,我们可以这样写

class TestFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val binding = FragmentTestBinding.inflate(inflater)
        return binding.root
    }

}

表达式语言

我们可以在表达式语言中使用运算符和关键字,例如算术运算符(+ - / * %),逻辑运算符(&& ||) ,比较运算符( == > < >= <=)等。

<?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>

        <import type="android.view.View" />

        <variable
            name="user"
            type="com.example.myapplication.User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        tools:context=".MainActivity">

        <androidx.appcompat.widget.AppCompatTextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(user.age)}"
            android:visibility="@{user.age > 18 ? View.VISIBLE : View.GONE}" />

        <androidx.appcompat.widget.AppCompatTextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{@string/username + user.name}" />

    </LinearLayout>
</layout>

这里需要注意的是,如上面的示例,user.age 是 Int,设置成 text 时需要转换成 String,不然会报错。 如果你只想使用字符串字面量的话,可以使用反单引号 ` 括起来

        <androidx.appcompat.widget.AppCompatTextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{`username:`+ user.name}" />

在使用集合时,需要转义 < 字符,例如 List<String> 需要写成 List&lt;String>

    <data>
        
        <import type="java.util.List" />

        <variable
            name="list"
            type="List&lt;String>" />
    </data>

如果布局是以 include 引入的,通过使用应用命名空间和特性中的变量名称,变量可以从包含的布局传递到被包含布局的绑定

<?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.example.myapplication.User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        tools:context=".MainActivity">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name}" />

        <include
            layout="@layout/sub_layout"
            app:user="@{user}" />

    </LinearLayout>
</layout>

sub_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="user"
            type="com.example.myapplication.User" />
    </data>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center">

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

    </LinearLayout>
</layout>

事件处理

方法引用:事件可以直接绑定到处理脚本方法

<?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="event"
            type="com.example.myapplication.MainActivity.ClickListener" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        tools:context=".MainActivity">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{event::submitClick}"
            android:text="submit"
            android:textAllCaps="false" />

    </LinearLayout>
</layout>
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding =
            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        binding.event = ClickListener()
    }

    //点击事件处理
    inner class ClickListener {
        fun submitClick(view: View) {
            Toast.makeText(this@MainActivity, "Submitted Successfully", Toast.LENGTH_SHORT).show()
        }
    }
}

监听器绑定:在事件发生时运行,允许您运行任意数据绑定表达式,可以传递相关数据。

例如,我们使用单独的类来集中管理点击事件,此时 context 和 showText 我们需要来自相应的 Activity

class ClickListener {
    fun submitClick(context: Context, showText: String) {
        Toast.makeText(context, showText, Toast.LENGTH_SHORT).show()
    }
}
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding =
            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        binding.run {
            showText = "Submitted Successfully"
            context = this@MainActivity
            event = ClickListener()
        }
    }

}
<?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="event"
            type="com.example.myapplication.ClickListener" />

        <variable
            name="showText"
            type="String" />

        <variable
            name="context"
            type="android.content.Context" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        tools:context=".MainActivity">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="@{()->event.submitClick(context,showText)}"
            android:text="submit"
            android:textAllCaps="false" />

    </LinearLayout>
</layout>

方法引用和监听器绑定之间的主要区别在于实际监听器实现是在绑定数据时创建的,而不是在事件触发时创建的。如果您希望在事件发生时对表达式求值,则应使用监听器绑定。

绑定适配器

通过使用适配器指定为设置值而调用的方法,提供自己的绑定逻辑,以及指定返回对象的类型。

<?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="url"
            type="String" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        tools:context=".MainActivity">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:imageUrl="@{url}" />

    </LinearLayout>
</layout>
@BindingAdapter("imageUrl")
fun loadImageFromNet(imageView: ImageView, imageUrl: String) {
    Glide.with(imageView.context).load(imageUrl).into(imageView)
}

第一个参数用于确定与特性关联的视图类型,第二个参数用于确定在给定特性的绑定表达式中接受的类型,当然也可以接收多个属性

@BindingAdapter("imageUrl", "error")
fun loadImageFromNet(imageView: ImageView, imageUrl: String, error: Drawable) {
    Glide.with(imageView.context).load(imageUrl).error(error).into(imageView)
}
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:error="@{@drawable/error}"
            app:imageUrl="@{url}" />

双向数据绑定

@={} 表示法,可接收属性的数据更改并同时监听用户更新。

<?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>

        <import type="androidx.lifecycle.MutableLiveData" />

        <variable
            name="showText"
            type="MutableLiveData&lt;String>" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        tools:context=".MainActivity">

        <androidx.appcompat.widget.AppCompatEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={showText}" />

    </LinearLayout>
</layout>
class MainActivity : AppCompatActivity() {

    private val showText = MutableLiveData<String>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding =
            DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        binding.showText = showText
        showText.observe(this) {
            //值会随着输入的变化而变化
        }
    }

}