Android 数据绑定 codelab(Android Data Binding codelab)

当你停止奋斗的时候,你就真的老了。

本文翻译自 Android Data Binding codelab(干咱们这行需要科xiao上网,累还得爱),主要讲解了 Android Data Binding 库的基本使用方法。本 codelab 所讲项目的 GitHub 地址

目录

1. 介绍

Data Binding(数据绑定) 库

你将构建什么

你需要什么

2. 试试不使用 Data Binding 的 app

3. 启用 Data Binding 并转换布局

4. 创建你的第一个布局表达式

5. 更改填充,移除 Activity 中的 UI 调用

6. 处理用户事件

7. 观察数据

8. 使用 Binding Adapters(绑定适配器)创建自定义属性

10. 练习创建 Binding Adapters

11. 你一定会成功!


1. 介绍

Data Binding(数据绑定) 库

Data Binding 库允许您使用声明的格式而不是编程方式将布局中的 UI 组件绑定到应用程序中的数据源。在本 codelab 中,你将学习如何设置所有内容、使用 布局表达式、使用 可观察对象 以及创建自定义的 Binding Adapters(绑定适配器),以使模板最少化。

你将构建什么

在本 codelab 中,你将把这个 app 转换成(使用)Data Binding(的 app):

这个 app 只有一个展示一些静态数据和可观察数据的屏幕页面,意味着当数据更改时,UI 将 自动更新。

数据是由 ViewModel 提供的。Model-View-ViewModel 是一种展现层模式,它非常适合于数据绑定。下面是一个图表

如果你还不熟悉 Architecture Components 库中的 ViewModel 类,可以查看官方文档。总之,它是一个为视图(Activity、Fragment等)提供 UI 状态的类。(屏幕)方向改变时它依旧存在,并且充当了与应用程序中其他层的接口。

你需要什么

  • Android Studio 3.2 或更高版本。

2. 试试不使用 Data Binding 的 app

在这一步中,你下载完整的 codelab 代码,然后运行一个简单的示例 app。

$ git clone https://github.com/googlecodelabs/android-databinding

或者,您可以将存储库下载为zip文件:DOWNLOAD ZIP

  1. 解压代码
  2. 在 3.2 或更新版本的 Android Studio中打开项目。

运行 app

默认的 activity 打开后像这样:

这个屏幕展示了一些数据,让用户点击按钮来增加一个计数并更新进度条。它使用 SimpleViewModel。打开它看一下。

使用 Ctrl+N 来快速定位 Android Studio 中的类。使用 Ctrl+Shift+N 通过文件名 定位文件。

在 Mac 上,使用 Command+O 来定位类,使用 Command+Shift+O 来定位文件。

它展示了

  • 名字(name)和 姓氏(last name)
  • 一个(代表) likes 的数字
  • 人气

同时,它让用户通过 onLike() 来增加 likes(“喜欢”)的数字。

虽然 SimpleViewModel 没有最有趣的功能,但它仍然有用。另一方面,PlainOldActivity 有许多问题:

  • 它多次调用 findViewById。这不仅慢,而且也不安全,因为它在编译时没有检查。如果你传给 findViewById 的 ID 是错的,那么 app 在运行时将会崩溃。
  • 它在 onCreate 中设置初始值。如果是自动设置好的默认值那会好得多。
  • 它在布局中使用 android:onClick 属性,这也是不安全的:假如 onLike 方法在你的 activity 中没有被实现(或者被重命名),那么 app 在运行时将会崩溃。
  • 它有大量代码。Activities 和 fragments 往往会快速增长,所以我们想从它们中移走尽可能多的代码。同时,activities 和 fragments 中的代码很难测试和维护。

使用 Data Binding 库,我们将解决所有这些问题,将逻辑从 activity 移出到可重用和易于测试的地方。

3. 启用 Data Binding 并转换布局

这个项目已经启用了 Data Binding,但是当你想在自己的项目中使用它时,第一步是在 将使用它的 modules 中 启用库。

build.gradle

android {
...
    dataBinding {
       enabled true
    }
}

现在你将把 layout 转换成 Data Binding layout。

要把一个常规 layout 转换成 Data Binding layout,你需要:

  1.  使用 <layout> 标签包裹你的layout
  2. 添加 layout 变量(可选)
  3. 添加 layout 表达式(可选)

打开 plain_activity.xml它是一个将 Constraint Layout(约束布局) 作为根元素的常规 layout。

为了将 layout 转换成 Data Binding 的,你需要在 < layout > 标签中包裹根元素。你也需要把命名空间定义(以 xmlns: 开头的属性)移到新的根元素中。

方便的是,Android Studio 提供了自动完成这一操作的方法。

你的 layout 现在应该看起来像这样:

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

   </data>
   <android.support.constraint.ConstraintLayout
           android:layout_width="match_parent"
           android:layout_height="match_parent">

       <TextView
...

找出 <data> 标签,这就是我们放 布局变量 的地方。

布局变量用于写 布局表达式。布局表达式放在元素属性的值中,它们使用 @{expression} 格式。下面是一些例子:

// 一些复杂的布局表达式的例子
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

布局表达式语言非常强大,但是您应该只将其用于基本连接,因为更复杂的表达式将使读取和维护更加困难。

// 将 viewmodel 的 name 属性 绑定到 text 标签上。
android:text="@{viewmodel.name}"
// 将 viewmodel 的 nameVisible 属性 绑定到 visibility 标签上。
android:visibility="@{viewmodel.nameVisible}"
// 当 View 被点击时调用 viewmodel 的 onLike() 方法。
android:onClick="@{() -> viewmodel.onLike()}"

在此查看该语言的完整描述。

让我们绑定一些数据!

4. 创建你的第一个布局表达式

现在让我们从一些静态数据绑定开始。

  • 在 < data > 标签里创建两个 String 类型的 布局变量
<data>
    <variable name="name" type="String"/>
    <variable name="lastName" type="String"/>
</data>
  • 查找 ID 为 plain_name 的 TextView,并用 布局表达式 添加 android:text 属性:
<TextView
    android:id="@+id/plain_name"
    android:text="@{name}" 
... />

布局表达式以 @ 符号开始,并包裹在花括号 { } 中。

因为 name 是一个 String,所以 Data Binding 将知道如何在 TextView 中设置该值。稍后您将学习如何处理不同的布局表达式类型和属性。

  • 对 plain_lastName text view 做同样的事(操作)。
            <TextView
                android:id="@+id/plain_lastname"
                android:text="@{lastName}"
            ... />

你可以在 plain_activity_solution_2.xml中找到这些操作的结果。

现在我们需要修改 Activity,以便它正确填充 Data Binding 布局。

5. 更改填充,移除 Activity 中的 UI 调用

布局已经准备好了,但是我们需要在 activity 中做一些改变。打开 PlainOldActivity

因为我们使用的是 Data Binding 布局,所以填充是以不同的方式完成的。

在 onCreate 中,将:

setContentView(R.layout.plain_activity)

替换为:

val binding : PlainActivityBinding =
    DataBindingUtil.setContentView(this, R.layout.plain_activity)

为什么我们要创建一个变量?因为我们需要一种方法来设置我们在 <data> 块中声明的那些布局变量。这就是绑定对象的目的。绑定类是由库自动生成的。如果你想要的话,打开 PlainActivitySolutionBinding,然后四处看看。它是简单的 Java 代码。

  • 现在我们只需要设置变量值:
    binding.name = "Your name"
    binding.lastName = "Your last name"

就是这样。您只需使用库绑定数据即可。

我们能开始移除旧代码了:

  • 移除 updateName( ) 方法,Data Binding 已经找到了 ID 并 设置了文本值。
  • 在 onCreate( ) 移除 updateName( ) 调用。

你可以在  PlainOldActivitySolution2 中找到这些操作的结果。

现在你能运行 app 了。你会发现你的名字取代了 Ada 的。

6. 处理用户事件

到目前为止,我们已经探索了如何向用户展示数据,但是使用 Data Binding 库,您还可以处理用户事件和调用布局变量上的操作。

在我们做这些事之前,我们会稍微清理一下我们的布局。

  • 首先,将两个变量替换为单个 ViewModel。大多数情况下都这样做,为的是你的表示代码和状态包含在一个地方。
<data>
    <variable
        name="viewmodel"
        type="com.example.android.databinding.basicsample.data.SimpleViewModel"/>
</data>

我们将调用 viewmodel 属性,而不是直接访问变量。

  • Change the layout expressions in both TextViews在两个 TextView 中更改布局表达式
        <TextView
                android:id="@+id/plain_name"
                android:text="@{viewmodel.name}"
... />
        <TextView
                android:id="@+id/plain_lastname"
                android:text="@{viewmodel.lastName}"
... />

此外,我们会对点击 "Like" 按钮作出反应。

  • 寻找 like_button Button 并将
android:onClick="onLike"

替换为

android:onClick="@{() -> viewmodel.onLike()}"

之前的 onClick 属性使用了一种不安全的机制,在该机制中,当 view 被点击时 activtiy 和 fragment 中的 onLike( ) 方法被调用。如果具有这个精确签名的方法不存在,app 就会崩溃。

新方法更安全,因为它在编译时会被检查,并使用 lambda 表达式来调用 view model 的 onLike( ) 方法。

单击 Android Studio中 的 Build 菜单中的 "Make Project",检查 Data Binding 错误。问题将阻止 app 构建,错误将显示在构建日志中。

你能在 plain_activity_solution_3.xml 中找到这些操作的结果。

现在让我们从 activity 中移除我们不需要的东西:

1. 将

binding.name = "Your name"
binding.lastName = "Your last name"

替换为

binding.viewmodel = viewModel

2. 移除 activity 中的 onLike 方法,因为它现在以已经被绕开了。

你能在 PlainOldActivitySolution3 中找到这些操作的结果。

如果你运行这个 app,你会发现按钮没有任何作用。这是因为我们不再调用 updateLikes( ) 了。让我们恰当地实现它。

7. 观察数据

我们在前面的步骤中创建了一个静态绑定。如果打开视图模型,你会发现 name 和 lastName 只是字符串,这很好,因为它们不会改变。但是,likes 是被用户所修改的。

var likes =  0

当这个值改变时,我们不需要显式地更新 UI,而是要使它变得可观察

使用数据绑定,当可观察值改变时,它绑定到的UI元素将自动更新。

有多种实现可观察性的方法。您可以使用可观察的类可观察的变量,或者首选的方式,LiveData。关于这方面的完整文件是在这里

我们将使用 ObservableFields ,因为他们比较简单。

var likes = 0 // 在这个 codeLab 中这是一个严格的强类型,因此我建议不要使用 “非常” private 的设置,这样式为了防止外部修改变量。

替换为新的可观察变量:

    val likes = ObservableInt()
    val popularity = ObservableField<Popularity>(NORMAL)

同时,将

fun onLike() {
        likes++
    }

    /**
     * 按量返回受欢迎度: [Popularity.NORMAL],
     * [Popularity.POPULAR] 或者 [Popularity.STAR]
     */
    val popularity: Popularity
        get() {
            return when {
                likes > 9 -> Popularity.STAR
                likes > 4 -> Popularity.POPULAR
                else -> Popularity.NORMAL
            }
        }

替换为

fun onLike() {
        likes.set(likes.get() + 1)

        popularity.set(likes.get().let {
            when {
                it > 9 -> Popularity.STAR
                it > 4 -> Popularity.POPULAR
                else -> Popularity.NORMAL
            }
        })
    }

.如你所见, 可观察变量的值必须用 set() 设置,并用 get() 读取。此机制允许库在值更改时更新 UI。

如果你重新构建项目,你将发现该 activity 没有编译。我们直接从 activity 中获取 likes,我们不再需要这样做:

private fun updateLikes() {
        findViewById<TextView>(R.id.likes).text = viewModel.likes.toString()
        findViewById<ProgressBar>(R.id.progressBar).progress =
            (viewModel.likes * 100 / 5).coerceAtMost(100)
...

打开 PlainOldActivity 删除 activity 中的所有私有方法及其调用。现在的 activity 变得很简洁。

class PlainOldActivity : AppCompatActivity() {

    // 从 ViewModelProviders 中获取 ViewModel
    private val viewModel by lazy { ViewModelProviders.of(this).get(SimpleViewModel::class.java) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding : PlainActivityBinding =
            DataBindingUtil.setContentView(this, R.layout.plain_activity)

        binding.viewmodel = viewModel
    }
}

你能从 SolutionActivity 中找到这些操作的结果。

一般来说,将代码从 activity 中移出对于可维护性和可测试性而言是非常重要的。

让我们将显示 likes 次数的TextView 绑定到可观察的整数。在 plain_activity.xml 中:

            <TextView
                android:id="@+id/likes"
                android:text="@{Integer.toString(viewmodel.likes)}"
...

如果你现在运行 app,likes 的数量会按预期增加。

到目前为止,我们已经使用了像 android:onClick 和 android:text 这样的属性。让我们更探索其它属性并创造我们自己的(属性):

8. 使用 Binding Adapters(绑定适配器)创建自定义属性

当你将一个字符串(或者一个可观察的字符串)绑定到 android:text 属性时,会发生什么是很明显的,但是它是如何发生的呢?

使用 Data Binding 库,几乎所有 UI 调用都是在被称为 Binding Adapters 的静态方法中完成的。

该库提供了大量的 Binding Adapters。在这里查看它们。下面是一个关于 android:test 属性的例子:

    @BindingAdapter("android:text")
    public static void setText(TextView view, CharSequence text) {
        // 为了清晰,删除了一些检查。

        view.setText(text);
    }

或者 android:background 的一个例子:

    @BindingAdapter("android:background")
    public static void setBackground(View view, Drawable drawable) {
        if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
            view.setBackground(drawable);
        } else {
            view.setBackgroundDrawable(drawable);
        }
    }

Data Binding 并没有什么神奇之处。所有问题都在编译时被解决,你可以在生成的代码中阅读。

让我们来研究进度条。我们希望它:

  • 若果没有 likes,就隐藏
  • (被)5个 likes 填满
  • 填满后改变颜色

我们将为此创建自定义 Binding Adapters:

在 utils 包中打开 BindingAdapters.kt 文件。不管你在哪里创建它们,库都会找到它们。在 Kotlin 中,可以通过向 Kotlin 文件的顶层添加函数来创建静态方法。

为第一条要求寻找 Binding Adapter,即 hideIfZero:

    @BindingAdapter("app:hideIfZero")
    @JvmStatic fun hideIfZero(view: View, number: Int) {
        view.visibility = if (number == 0) View.GONE else View.VISIBLE
    }

这个绑定适配器:

  • 应用于 app:hideIfZero 属性。
  • 可以应用于每个 View(因为第一个参数是 View;你可以通过更改此类型来限制某些类)。
  • 取一个应该是布局表达式返回的整数。
  • 如果数字是零的话就让 View GONE。

在 plain_activity 布局中,寻找进度条并添加 hideIfZero 属性:

    <ProgressBar
            android:id="@+id/progressBar"
            app:hideIfZero="@{viewmodel.likes}"
...

运行 app,当你第一次点击按钮时,你将看到进度条。然而,我们仍然需要改变它的值和颜色:

对于进度值,我们将使用Binding Adapter,它获取 likes 数量的最大值。打开BindingAdapters 文件并查找这个(内容):

    /**
     *  设置进度条的值以使5个 likes 填满它。
     * 
     *  具有多个属性的 Binding Adapters 示例。
     *  请注意,每当任何属性更改时,都会调用该适配器。
     */
    @BindingAdapter("app:progressScaled", "android:max", requireAll = true)
    @JvmStatic fun setProgress(progressBar: ProgressBar, likes: Int, max: Int) {
        progressBar.progress = (likes * max / 5).coerceAtMost(max)
    }

如果缺少任何属性,则不使用此 Binding Adapter。这在编译时发生。该方法现在接收3个参数(它所应用于的视图 + 在 annotation 中定义的属性的数量)。

当绑定适配器被使用时,requireAll 参数定义了:

  • 当为 true 时,所有元素必须在 XML 定义中展现。
  • 当为 false 时,缺失的属性将为null,如果为boolean类型,则为false,如果为基本类型,则为0。

接下来,将属性添加到 XML:

        <ProgressBar
                android:id="@+id/progressBar"
                app:hideIfZero="@{viewmodel.likes}"
                app:progressScaled="@{viewmodel.likes}"
                android:max="@{100}"
...

我们将 progressScaled 属性绑定到 likes 的数量,并且只向 max 属性传递一个文本整数。如果不添加 @ {} 格式,Data Binding 将无法找到正确的 Binding Adapter。

你可以在 plain_activity_solution_5.xml 中找到这些步骤的结果。

如果运行应用程序,你将看到进度条如何按预期填充。

 

10. 练习创建 Binding Adapters

孰能生巧。创建:

  • 一个根据 likes 的值对进度条的颜色进行着色,并添加相应的属性的 Binding Adapter。
  • 一个根据受欢迎度来显示不同的图标的 Binding Adapter:
  • 黑色的 ic_person_black_96dp
  • 浅粉红色的 ic_whatshot_black_96dp
  • 深粉红色的 ic_whatshot_black_96dp

您将在 BindingAdapters.kt 文件、SolutionActivity 和 solution.xml 布局中找到解决方案。

 

11. 你一定会成功!

祝贺你!你完成了 codeLab,因此你应该知道如何创建 Data Binding 布局、添加变量和表达式,使用可观察数据,并通过自定义 Binding Adapter 来自定义属性,让你的 XML 布局更有意义。

现在查看例子以获得更高级的用法和完整图片的文档

猜你喜欢

转载自blog.csdn.net/qq_33404903/article/details/84557991