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

Android 绑定 - 入门

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (25投票s)

2011 年 1 月 12 日

LGPL3

7分钟阅读

viewsIcon

163196

一个新的基于 Android-java 的 MVVM 演示框架的介绍。

引言

Android Binding 是一个全新的开源框架,用于 Android-Java,提供 XML 布局视图绑定机制。它通过解耦视图控件和后端 Activity 来帮助 Android 应用程序的开发。它与 MVP 或 MVVM 模式配合效果最佳。

请也从 Market 下载(搜索“Markup Demo”)演示 Android Binding 大部分功能的示例。

一个更基础的入门教程,请访问我的博客 Android MVVM with android-binding 教程系列

更新:v0.2 已发布。请访问项目主页了解详情,因为它可能与之前的版本不兼容。

重大变更(截至 2010/1/30)

如果您是第一次阅读本文,可以跳过此部分。

Android Binding 的 0.11 版本已随此示例应用程序发布。随着项目的不断发展,进行了一些重大的(但可能导致原始代码破坏的)变更。在 0.11 版本发布时,我认为这些变更应该是最终的。

我第一次写这篇文章时,Android Binding 不支持绑定到对象集合,但现在,它可以绑定到 Cursor 或 Array,其中的每个“行”记录都被视为一个 View Model,这意味着 CommandDependentObservables 都将正常工作,这将在本文稍后介绍。

示例应用程序已重写,因为联系人列表不再绑定到原始的 Adapter,而是通过一种更声明式的方式。与操作相关的绑定已重命名为带有“on-”前缀,例如,click -> onClick,使其更具辨识度。

Observable<T> 现在需要将 T 的类作为参数传递:例如

Observable<Boolean>(Boolean.class, true); 

由于这样写代码会非常冗长,所以提供了一些简写形式的原始 Observables。

示例应用

以下将简要介绍其用法。本文中使用的示例应用程序代码可在以下地址获取:

编译后的应用程序可在 Android Market 上找到(在 Market 中搜索“Android Binding”)。

此示例是基于 Google 原始的 联系人管理器示例 进行修改的,目的是展示视图绑定方面的差异以及使用 Android Binding 的优势。

基本配置

要使用 Android Binding,您只需引用该库(在 Eclipse 中,右键单击项目 -> Properties -> Android,将 Android Binding 项目引用为库)。然后,在 Application 类中

public class ContactManagerApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Binder.init(this);
    }
}

Binder.init(Application) 需要在整个应用程序生命周期中运行一次且仅一次。在 init() 过程中,Android Binding 会注册并初始化标记和绑定提供程序,这些提供程序可以支持自定义视图类。

Activity

Activity 不再需要感知 View,甚至 View Model 也不需要。Android Binding 最适合 MVVM 中的 View Model First 开发。因此,Activity 中不再包含表示/用户交互逻辑,最终得到一个干净的 Activity 类。

public final class ContactManager extends Activity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        ContactManagerModel model = new ContactManagerModel(this);
        Binder.setAndBindContentView(this, R.layout.contact_manager, model);
    }
}

您将 Model(或者更准确地说,ViewModel)提供给 Binder,它会自动将 viewViewModel 连接起来。这就是我们标记 contact_manager.xml 的方式。

布局

<?xml version="1.0" encoding="utf-8"?>
<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">
    <ListView android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              binding:itemSource="ContactList"
              binding:itemTemplate="@layout/contact_entry"
              android:layout_weight="1"/>
    <CheckBox android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              binding:checked="ShowInvisible"
              binding:onCheckedChange="PopulateList"
              android:text="@string/showInvisible"/>
    <Button android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            binding:onClick="AddContact"
            android:text="@string/addContactButtonLabel"/>
</LinearLayout>

布局看起来与标准的 Android 布局几乎相同,只是导入了一个额外的 binding 命名空间。AAPT 有个问题,binding 命名空间需要指向“demo”项目而不是库。(希望未来能解决)。 binding 命名空间应指向 http://www.gueei.com/android-binding/

如上布局文件所示,标记是通过自定义命名空间(以 binding 为前缀)完成的,并且该属性几乎反映了大多数原始 View 属性。

目前视图中有两种可绑定的对象。第一种是 Property(如 CheckBox 中的 checked),第二种是 CommandCheckbox 中的 checkedChange),它们都将在本文稍后部分进行解释。

另请注意,对于 ListView,它绑定到 itemSource,该源可以是 Cursor 或 View Model 的 Array(稍后将介绍),而 itemTemplate 是标准的 Android 资源引用格式,用于指示**每个**项目的布局应该是什么样子。

下面是 contact_entry.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
			  xmlns:binding="http://www.gueei.com/android-binding/"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              binding:onClick="ShowContact"
              >
    <TextView android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:textSize="20dip"
              binding:text="Name"
              />
</LinearLayout>

这仍然是一个相当标准的布局 XML 文件,带有自定义的绑定命名空间。请注意,布局根节点(即 LinearLayout)定义了一个动作 onClick

ViewModel

ViewModel 是连接 ViewModel 的桥梁(在本例中将是联系人提供者)。Android Binding 中的 ViewModel 是一个类,它定义了 View 可以访问的所有“可绑定”数据/命令字段。下面显示了 ViewModel 的一部分。

public class ContactManagerModel {
	private Activity mContext;
	
	public CursorSource<ContactRowModel> ContactList = 
		new CursorSource<ContactRowModel>
			(ContactRowModel.class, new Factory());
	
	public BooleanObservable ShowInvisible = new BooleanObservable(false);

	public Command PopulateList = new Command(){
		public void Invoke(View view, Object... args) {
			populateContactList();
		}
	};
	public Command AddContact = new Command(){
		public void Invoke(View view, Object... args) {
			launchContactAdder();
		}
	};

	private void populateContactList() {
            // Build adapter with contact entries
            Cursor cursor = getContacts();
            ContactList.setCursor(cursor);
        }

BooleanObservable 代表一个可以被其他对象“观察”的对象,因此,对该对象的任何更改都会通知其观察者。Android Binding 中定义了相当多的 Observables,例如 StringObservableIntegerObservable 等,它们都是 IObservable<T> 的子类,实现了以下方法:

  1. set()
  2. get()
  3. notifyChanged()

get()set() 是 Java 中 getter 和 setter 方法的替代,默认情况下,它们会自动通知订阅者对象的变化(这是从 .NET 借鉴的概念)。

Command 是定义“可执行”内容的接口。通常,它们会与用户界面触发的 Event 连接。

最后,由于我们的联系人来源是一个 cursor,我们需要提供 CursorSource<?> 来指示如何使用我们的 Cursor

绑定到 Cursor

在 Android Binding 中,Cursor 中的每一条记录行都被视为一个 View Model。这意味着,您将应用相同的布局,每个布局都带有不同的数据集。Android Binding 最强大的功能之一是它允许您在子 View Model 中定义 Commands 和更复杂的逻辑。如前所述,您不能仅将 Cursor 作为 AdapterViews(包括 ListViewSpinners 等)的 itemSource,而必须是 ArraySourceCursorSource

CursorSource 接受两个构造函数参数:

public CursorSource<ContactRowModel> ContactList = 
		new CursorSource<ContactRowModel>
			(ContactRowModel.class, new Factory()); 

第一个是表示每个“行”光标数据的子视图模型的类(因此称为 rowModel),另一个是实际知道如何“构建”行的工厂。让我们逐一来看。

public class ContactRowModel extends CursorRowModel {
    	public IdField Id = new IdField(0);
    	public StringField Name = new StringField(1); 
        public Command ShowContact = new Command(){
//...

RowModel 基本上是一个标准的 View Model,只是它需要继承自 CursorRowModelIdFieldStringField 只是 Observables,但它们的值将使用 Cursor 自动填充;括号内的数字表示该字段映射到哪个列。

模型验证

模型验证(更准确地说,是 View Model 的验证)也得到了支持。这在此示例应用程序中也有演示,但您可以阅读我的另一篇 文章 以获取详细信息。

此外

Observable 目前相当受限制,因为它要求 View 属性和 Model 的属性必须是相同的类型。也就是说,如果您将 checked(布尔值)属性绑定到 Observable<Integer>,它将不起作用,因为不允许隐式类型转换。因此,提供了另外两个 Observable 的子类:

DependentObservable<?>

这表示一个 observables 的值依赖于其他 Observables。例如,我们可以重写上面的 ViewModel 来添加一个 SelectedContact:

DependentObservable<Contact> SelectedContact = new
      DependentObservable<Contact>(Contact.class, SelectedId){
         @Override
         public Contact calculateValue(Object... args){
            getContactFromDb((Integer)args[0]);
        }
    };

DependentObservable 只要求重写一个方法,即 calculateValue。由于 DependentObservable 可以依赖于多个依赖项,因此 calculateValue 中的参数长度是不固定的,并且需要显式类型转换。上面的示例类似于一个单向转换器,它将 Id 转换为一个真实的联系人。

Android Binding 中实际上有一个 Converter 类,它与 DependentObservable 的唯一区别是 Converter 允许双向绑定。

Converter<?>

public abstract void ConvertBack(T value, Object[] outResult);

确实,ConverterDependentObservable 的子类。它依赖于一组其他 observables,它可以将布尔值 true 转换为数字 1,并在其他更改转换器值时反向转换。

进展和计划

该项目的 Alpha 版本已发布。您可以访问项目主页下载。

未来的计划是增加对更多 POJO(Plain Old Java Object)方式的 View Model 声明的支持。

您可以自由地从 Google Code 项目仓库下载代码,报告问题。请在讨论组中留言:http://groups.google.com/group/androidbinding

结论

本文简要介绍了 Android Binding 的功能。如果您将其与原始的联系人管理器示例进行比较,您会发现使用 Android Binding 和 MVVM 可以使代码更加整洁,从而更有利于单元测试和提高代码质量。项目正在积极开发中,这是我的第一个 OSS 项目。我非常期待对这个框架的评论和建议。

作者博客和项目详情

© . All rights reserved.