65.9K
CodeProject 正在变化。 阅读更多。
Home

Android 中的 MVVM

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.54/5 (13投票s)

2011年3月10日

LGPL3

5分钟阅读

viewsIcon

106173

downloadIcon

2290

在 Android 应用程序中使用 Android Binding 实现 MVVM 模式。

Android 中的 MVVM

MVVM 代表 Model-View-ViewModel,这是一种在 Microsoft WPF 和 Silverlight 中广泛采用的模式。在本文中,我将借助 Android-Binding 框架,讨论 MVVM 模式在 Android (Java) 中的实现。

本文的源代码可以在 这里 获取。

为 Android 重新定义的 MVVM

Model:Android 中的 Model 可以是来自应用程序内部的数据(包括 Shared Preferences)、数据库(以 Cursor 形式,或通过其他 Data Access Object)或外部数据(通过 Cursor 到其他 Data Contract)。

View:GUI 中显示的所有元素,包括 android.widget.* 系列和自定义视图。

ViewModelViewModel View 公开属性和命令,它还负责 View Model 之间的数据绑定。在 Android 中,大部分这项工作是在 Activity 中完成的。

实现

我们以一个简单的计算器为例。如下图所示,计算器包含一些用于输入和操作的按钮,以及一个用于显示结果的文本框。

一旦 ViewModel 准备就绪,我们就需要将 ViewModel View 进行绑定,这里我们使用 Android-Binding。Android-Binding 可以通过 XML 标记的绑定语法来帮助解耦 View ViewModel。我们只需要包含该库,并在 XML 标记中添加一个额外的命名空间,以便 Android-Binding 能够识别。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:binding="http://www.gueei.com/android-binding/"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
    <TextView android:layout_width="fill_parent"
    android:layout_height="fill_parent" android:textSize="45dip"
    android:gravity="right|bottom" 
	binding:text="com.gueei.tutorials.calculator.FormatDisplay(Display)"
    android:layout_weight="1"/>

本文不会深入介绍如何使用 Android-Binding,但您可以查阅以下文章以及源代码以了解更多信息。

为了使 View 能够感知 ViewModel 属性的变化,我们在 Android-Binding 中有两种方式来公开它们。

  1. Observable-Command 模式:View 可以绑定的所有属性都包装在 Observable<T>; 中,而命令则实现 Command 接口。这是 Android-Binding 中主要支持的方法。
  2. POJOViewModel 定义了 Java 风格的 getter-setter 属性,并且 ViewModel 实现了 PojoViewModel 接口。这是一个 Android-Binding 的插件接口,该插件会将 ViewModel 转换为基于 Observable 的对象。
    这里,我们的示例将采用 Observable-Command 模式。(POJO 版本也可在源代码中找到。)

Observable<T>

<T> 是任何 Object,而 Observable<T> 表示类型为 T 的属性是 Observable,当对其进行更改时,它会通知其订阅者。

因此,我们必须将上述属性包装在 Observables 中。

Observable<Double> Result = new Observable<Double>(Double.class, 0d);

命令

Command 类似于 ViewModelpublic 方法,它们可以被 UI 小部件调用。

绑定 View 与 ViewModel

一旦实现了 ViewModel,并声明了我们的布局,我们就需要将 ViewModel 绑定到 View。通过一点努力,Android-Binding 会为您自动完成绑定。在 Activity

Binder.setAndBindContentView(this, R.layout.main, new CalculatorViewModel());

DependentObservable/Converter

有时,我们希望在呈现数据之前对其进行格式化。例如,我们在计算器中进行‘double’计算,但我们不希望向用户显示原始 double 值——答案可能过于冗长,不适合实际使用(例如超过 15 位有效数字),我们希望将其限制在 10 位有效数字以内。

当然,我们可以在将显示值发送到 View 之前,在 ViewModel 中对其进行处理;我们通过使用 DependentObservable/Converter 来实现这一点。

DependentObservable 是一个 `Observes` 其他 observables 的 Observable,一旦其中任何一个 observable 发生变化,它就会相应地计算其值;Converter DependentObservable 相同,只是 DependentObservable 是只读的,而 Converter 可以执行反向转换。

由于我们的显示数字纯粹用于显示目的,我们只需要 DependentObservable。我们可以显式地在 ViewModel 中声明 dependentObservable,但是,我认为视图的外观格式化不应该是 ViewModel 的职责,而应该由 View 本身来完成。

Android-Binding 支持自定义 Converters(以及一些内置的),可以在 XML 布局中声明。首先,我们必须创建一个扩展自 DependentObservable 的类。

public class FormatDisplay extends DependentObservable<CharSequence> {
    public FormatDisplay(IObservable<?>[] dependents) {
        super(CharSequence.class, dependents);
    }
    @Override
    public CharSequence calculateValue(Object... args) throws Exception {
        Double display = (Double)args[0];
        DecimalFormat format = new DecimalFormat();
        format.applyPattern("#.######");
        String output = format.format(display);
        if (output.length() <= 10) return output;
        format.applyPattern("0.########E00");
        return format.format(display);
    }
}

现在,我们修改布局以

<TextView android:layout_width="fill_parent"
    android:layout_height="fill_parent" android:textSize="45dip"
    android:gravity="right|bottom" 
    binding:text="com.gueei.tutorials.calculator.FormatDisplay(Display)"
    android:layout_weight="1"/>

这样,Display 的绑定将变为

使用 MVVM 的优点

与其他任何 MVVM 平台一样,在 Android 中使用 MVVM 有助于将后端代码与 UI 解耦;在上述计算器示例中,我们可以为其连接不同的布局,而无需更改 ViewModel 中的任何内容(例如,以不同的方式格式化显示)。

使用 MVVM 的另一个巨大优势是,您可以轻松地对 ViewModel 进行单元测试。您无需真正“单击”按钮即可查看计算是否正确,只需单元测试 ViewModel 的相应 Command 即可。

借助 Android-Binding 框架,代码通常会更整洁。想象一下在传统的事件模型中为那 15 个按钮实现事件处理程序,它将导致保留对控件的 15 个引用,注册 15 次‘setOnClickListener()’,以及一个巨大的 switch-case-default 块用于 15 个事件处理分支。但有了 MVVM,Command 接口可以更清晰地描述它们的用途。

结束语

我尝试构建了几个 Android MVVM 应用程序,并发现有时 ViewModel Activity 之间的角色可能非常模糊。由于 Android 应用程序中的许多地方都需要使用 Context(例如获取 res、cursor、调用其他 Activity),最终 ViewModel 需要将调用 Activity 传递给它,或者将 ViewModel 嵌套在 Activity 类中,甚至 Activity 类本身就是 ViewModel (对于非常简单的 ViewModel 来说是可以的)。这种方法使得 ViewModel Activity 并行工作:Activity 决定 ViewModel 的创建和哪个 View 将被渲染,并管理 ViewModel 的生命周期;ViewModel 使用 Activity 来分派请求到外部世界。

历史

  • 2011 年 3 月 9 日:初次发布
© . All rights reserved.