MVVM基本用法

MVC

模型——视图——控制器
View层触发操作通知到业务层完成逻辑处理,业务层完成业务逻辑之后通知Model层更新数据,数据更新完之后通知View层展现。在实际运用中人们发现View和Model之间的依赖还是太强,希望他们可以绝对独立的存在,慢慢的就演化出了MVP。
Presenter 替换掉了Controller,不仅仅处理逻辑部分。而且还控制着View的刷新,监听Model层的数据变化。这样隔离掉View和Model的关系后使得View层变的非常的薄,没有任何的逻辑部分又不用主动监听数据,被称之为“被动视图”。

Data Binding Library

今年的Google IO 大会上,Android 团队发布了一个数据绑定框架(Data Binding Library)。以后可以直接在 layout 布局 xml 文件中绑定数据了,无需再 findViewById 然后手工设置数据了。其语法和使用方式和 JSP 中的 EL 表达式非常类似。 下面就来介绍怎么使用Data Binding Library。
目前,最新版的Android Studio已经内置了该框架的支持,配置起来也很简单,只需要编辑app目录下的build.gradle文件,添加下面的内容就好了

android {
....
dataBinding {
enabled = true
}
}

Data Binding Layout文件

Data Binding layout文件有点不同的是:起始根标签是 layout,接下来一个 data 元素以及一个 view 的根元素。这个 view 元素就是你没有使用Data Binding的layout文件的根元素。举例说明如下:

<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable type="com.example.User">
</variable></data>
<linearlayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
<textview android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}">
<textview android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.lastName}">
</textview></textview></linearlayout></layout>

上面定义了一个com.example.User类型的变量user,然后接着android:text="@{user.firstName}"把变量user的firstName属性的值和TextView的text属性绑定起来。

Data Object

我们来看下上面用到的com.example.User对象。

public class {
public final String firstName;
public final String lastName;

public (String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}

他有两个public的属性firstName,lastName,这和上面layout文件里面的@{user.firstName}和@{user.lastName}对应 或者下面这种形式的对象也是支持的。

public class {
private final String firstName;
private final String lastName;
public (String firstName, String lastName) {
.firstName = firstName;
.lastName = lastName;
}
// getXXX形式
public String getFirstName {
return .firstName;
}
// 或者属性名和方法名相同
public String lastName {
return .lastName;
}
}

添加完<data></data>标签后,Android Studio就会根据xml的文件名自动生成一个继承ViewDataBinding的类。例如: activity_main.xml就会生成ActivityMainBinding, 然后我们在Activity里面添加如下代码:

@Override
protected onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainActivityBinding binding = DataBindingUtil.setContentView(, R.layout.main_activity);
User user = User("Test", "User");
binding.setUser(user);
}

就像你可以在xml文件里面使用属性android:onClick绑定Activity里面的一个方法一样,Data Binding Library扩展了更多的事件可以用来绑定方法,比如View.OnLongClickListener有个方法onLongClick(), 你就可以使用android:onLongClick属性来绑定一个方法,需要注意的是绑定的方法的签名必须和该属性原本对应的方法的签名完全一样,否则编译阶段会报错。 下面举例来说明具体怎么使用,先看用来绑定事件的类:

public class MyHandlers {
public onClickButton(View view) { ... }
public afterFirstNameChanged(Editable s) { ... }
}

然后就是layout文件:

<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable type="com.example.Handlers">
<variable type="com.example.User">
</variable></variable></data>
<linearlayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
<edittext android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}" android:aftertextchanged="@{handlers.afterFirstNameChanged}">
<button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onclick="@{handlers.onClickButton}">
</button></edittext></linearlayout></layout>

表达式语言(Expression Language)
你可以直接在layout文件里面使用常见的表达式:
数学表达式 + – / * %
字符串链接 +
逻辑操作符 && ||
二元操作符 & | ^
一元操作符 + – ! ~
Shift >> >>> <<
比较 == > < >= <=< p="">
instanceof
Grouping ()
Literals – character, String, numeric, null
值域引用(Field access)
通过[]访问数组里面的对象
三元操作符 ?: 示例:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

更多语法可以参考官方文档

有些时候,代码会修改我们绑定的对象的某些属性,那么怎么通知界面刷新呢?下面就给出两种方案。
让你的绑定数据类继承BaseObservable,然后通过调用notifyPropertyChanged方法来通知界面属性改变,如下:

private static class extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName {
return .firstName;
}
@Bindable
public String getLastName {
return .lastName;
}
public setFirstName(String firstName){
.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public setLastName(String lastName) { .lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}

在需要通知的属性的get方法上加上@Bindable,这样编译阶段会生成BR.[property name],然后使用这个调用方法notifyPropertyChanged就可以通知界面刷新了。如果你的数据绑定类不能继承BaseObservable,那你就只能自己实现Observable接口,可以参考BaseObservable的实现。

Data Binding Library提供了很便利的类ObservableField,还有ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, 和 ObservableParcelable,基本上涵盖了各种我们需要的类型。用法很简单,如下:

private static class {
public final ObservableField<string> firstName = ObservableField<>();
public final ObservableField<string> lastName = ObservableField<>();
public final ObservableInt age = ObservableInt();
}</string></string>

然后使用下面的代码来访问:

user.firstName.set("Google"); age = user.age.get();

调用set方法时,Data Binding Library就会自动的帮我们通知界面刷新了。
绑定AdapterView,在一个实际的项目中,相信AdapterView是使用得很多的,使用官方提供给的API来进行AdapterView的绑定需要写很多代码,使用起来不方便,但是由于Data Binding Library提供丰富的扩展功能,所以出现了很多第三方的库来扩展它,下面就来介绍一个比较好用的库binding-collection-adapter Github地址
使用的时候在你的build.gradle文件里面添加

compile 'me.tatarka:bindingcollectionadapter:0.16'

如果你要是用RecyclerView,还需要添加

compile 'me.tatarka:bindingcollectionadapter-recyclerview:0.16'

下面就是ViewModel的写法:

public class ViewModel {
public final ObservableList<string> items = ObservableArrayList<>();
public final ItemView itemView = ItemView.of(BR.item, R.layout.item);
}

这里用到了ObservableList, 他会在items变化的时候自动刷新界面 然后下面是layout文件:

<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<>
<variable ="viewmodel"="com.example.ViewModel">
<import ="me.tatarka.bindingcollectionadapter.layoutmanagers"="">
</>
<listview android:layout_width="match_parent" android:layout_height="match_parent" app:items="@{viewModel.items}" app:itemview="@{viewModel.itemView}">
<android.support.v7.widget.recyclerview android:layout_width="match_parent" android:layout_height="match_parent" app:layoutmanager="@{LayoutManagers.linear()}" app:items="@{viewModel.items}" app:itemview="@{viewModel.itemView}">
<android.support.v4.view.viewpager android:layout_width="match_parent" android:layout_height="match_parent" app:items="@{viewModel.items}" app:itemview="@{viewModel.itemView}">
<spinner android:layout_width="match_parent" android:layout_height="match_parent" app:items="@{viewModel.items}" app:itemview="@{viewModel.itemView}" app:dropdownitemview="@{viewModel.dropDownItemView}"></spinner></android.support.v4.view.viewpager></android.support.v7.widget.recyclerview></listview></import></variable></layout>

然后是item layout:
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<>
<variable ="item"="String">
</>
<textview android:id="@+id/text" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{item}"></textview></variable></layout>

如果有多种样式的布局,那么就需要把ItemView换成ItemViewSelector, 如下:

public final ItemViewSelector<string> itemView = BaseItemViewSelector<string>() {
@Override
public select(ItemView itemView, position, String item) {
itemView.set(BR.item, position == ? R.layout.item_header : R.layout.item);
}
// This is only needed if you are using a BindingListViewAdapter
@Override
public viewTypeCount {
return ;
}
};</string></string>

自定义绑定
正常情况下,Data Binding Library会根据属性名去找对应的set方法,但是我们有时候需要自定义一些属性,Data Binding Library也提供了很便利的方法让我们来实现。 比如我们想在layout文件里面设置ListView的emptyView,以前这个是无法做到的,只能在代码里面通过调用setEmptyView来做; 但是现在借助Data Binding Library,我们可以很容易的实现这个功能了。先看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">
<>
<variable ="viewmodel"="com.example.databinding.viewmodel.ViewAlbumsViewModel">
</>
<linearlayout android:layout_width="match_parent" android:layout_height="match_parent" android:paddingleft="10dp" android:paddingright="10dp" android:orientation="vertical">
<listview android:layout_width="fill_parent" android:layout_height="0px" android:layout_weight="1.0" app:items="@{viewModel.albums}" app:itemview="@{viewModel.itemView}" app:emptyview="@{@id/empty_view}" android:onitemclick="@{viewModel.viewAlbum}" android:id="@+id/albumListView">
<textview android:id="@+id/empty_view" android:layout_width="fill_parent" android:layout_height="0px" android:layout_weight="1.0" android:gravity="center" android:text="@string/albums_list_empty">
<button android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/create" android:onclick="@{viewModel.createAlbum}">
</button></textview></listview></linearlayout></variable></layout>
app:emptyView="@{@id/empty_view}"这个代码就用来指定emptyView,

下面来看下实现的代码:

@BindingAdapter("emptyView")
public static <t> setEmptyView(AdapterView adapterView, viewId) {
View rootView = adapterView.getRootView();
View emptyView = rootView.findViewById(viewId);
(emptyView != ) {
adapterView.setEmptyView(emptyView);
}
}</t>

下面我们来分析上面的代码,@{@id/empty_view}表示引用了@id/empty_view这个id,所以它的值就是int,再看上面的setEmptyView方法,第一个参数AdapterView adapterView表示使用emptyView这个属性的控件,而第二个参数int viewId则是emptyView属性传进来的值,上面的layout可以看出来它就是R.id.empty_view,然后通过id找到控件,然后调用原始的setEmptyView来设置。
上面的代码来自我写的一个Data Binding Library的示例项目DataBinding-album-sampleGithub地址

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,884评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,212评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,351评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,412评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,438评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,127评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,714评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,636评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,173评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,264评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,402评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,073评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,763评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,253评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,382评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,749评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,403评论 2 358

推荐阅读更多精彩内容