Android 绑定 - 入门
一个新的基于 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,这意味着 Command
和 DependentObservables
都将正常工作,这将在本文稍后介绍。
示例应用程序已重写,因为联系人列表不再绑定到原始的 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,它会自动将 view
与 ViewModel
连接起来。这就是我们标记 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
),第二种是 Command
(Checkbox
中的 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
是连接 View
和 Model
的桥梁(在本例中将是联系人提供者)。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
,例如 StringObservable
、IntegerObservable
等,它们都是 IObservable<T>
的子类,实现了以下方法:
set()
get()
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
(包括 ListView
、Spinners
等)的 itemSource
,而必须是 ArraySource
或 CursorSource
。
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,只是它需要继承自 CursorRowModel
。IdField
、StringField
只是 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);
确实,Converter
是 DependentObservable
的子类。它依赖于一组其他 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 项目。我非常期待对这个框架的评论和建议。