MVVM基本用法

MVC

模型——視圖——控制器
View層觸發(fā)操作通知到業(yè)務(wù)層完成邏輯處理衣吠,業(yè)務(wù)層完成業(yè)務(wù)邏輯之后通知Model層更新數(shù)據(jù)帆赢,數(shù)據(jù)更新完之后通知View層展現(xiàn)小压。在實(shí)際運(yùn)用中人們發(fā)現(xiàn)View和Model之間的依賴還是太強(qiáng),希望他們可以絕對(duì)獨(dú)立的存在椰于,慢慢的就演化出了MVP怠益。
Presenter 替換掉了Controller,不僅僅處理邏輯部分瘾婿。而且還控制著View的刷新蜻牢,監(jiān)聽(tīng)Model層的數(shù)據(jù)變化。這樣隔離掉View和Model的關(guān)系后使得View層變的非常的薄偏陪,沒(méi)有任何的邏輯部分又不用主動(dòng)監(jiān)聽(tīng)數(shù)據(jù)抢呆,被稱之為“被動(dòng)視圖”。

Data Binding Library

今年的Google IO 大會(huì)上笛谦,Android 團(tuán)隊(duì)發(fā)布了一個(gè)數(shù)據(jù)綁定框架(Data Binding Library)抱虐。以后可以直接在 layout 布局 xml 文件中綁定數(shù)據(jù)了,無(wú)需再 findViewById 然后手工設(shè)置數(shù)據(jù)了揪罕。其語(yǔ)法和使用方式和 JSP 中的 EL 表達(dá)式非常類似梯码。 下面就來(lái)介紹怎么使用Data Binding Library宝泵。
目前,最新版的Android Studio已經(jīng)內(nèi)置了該框架的支持轩娶,配置起來(lái)也很簡(jiǎn)單儿奶,只需要編輯app目錄下的build.gradle文件,添加下面的內(nèi)容就好了

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

Data Binding Layout文件

Data Binding layout文件有點(diǎn)不同的是:起始根標(biāo)簽是 layout鳄抒,接下來(lái)一個(gè) data 元素以及一個(gè) view 的根元素闯捎。這個(gè) view 元素就是你沒(méi)有使用Data Binding的layout文件的根元素。舉例說(shuō)明如下:

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

上面定義了一個(gè)com.example.User類型的變量user许溅,然后接著android:text="@{user.firstName}"把變量user的firstName屬性的值和TextView的text屬性綁定起來(lái)瓤鼻。

Data Object

我們來(lái)看下上面用到的com.example.User對(duì)象。

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

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

他有兩個(gè)public的屬性firstName贤重,lastName茬祷,這和上面layout文件里面的@{user.firstName}和@{user.lastName}對(duì)應(yīng) 或者下面這種形式的對(duì)象也是支持的。

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>標(biāo)簽后并蝗,Android Studio就會(huì)根據(jù)xml的文件名自動(dòng)生成一個(gè)繼承ViewDataBinding的類祭犯。例如: activity_main.xml就會(huì)生成ActivityMainBinding, 然后我們?cè)贏ctivity里面添加如下代碼:

@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里面的一個(gè)方法一樣,Data Binding Library擴(kuò)展了更多的事件可以用來(lái)綁定方法滚停,比如View.OnLongClickListener有個(gè)方法onLongClick(), 你就可以使用android:onLongClick屬性來(lái)綁定一個(gè)方法沃粗,需要注意的是綁定的方法的簽名必須和該屬性原本對(duì)應(yīng)的方法的簽名完全一樣,否則編譯階段會(huì)報(bào)錯(cuò)键畴。 下面舉例來(lái)說(shuō)明具體怎么使用最盅,先看用來(lái)綁定事件的類:

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>

表達(dá)式語(yǔ)言(Expression Language)
你可以直接在layout文件里面使用常見(jiàn)的表達(dá)式:
數(shù)學(xué)表達(dá)式 + – / * %
字符串鏈接 +
邏輯操作符 && ||
二元操作符 & | ^
一元操作符 + – ! ~
Shift >> >>> <<
比較 == > < >= <=< p="">
instanceof
Grouping ()
Literals – character, String, numeric, null
值域引用(Field access)
通過(guò)[]訪問(wèn)數(shù)組里面的對(duì)象
三元操作符 ?: 示例:

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

更多語(yǔ)法可以參考官方文檔

有些時(shí)候,代碼會(huì)修改我們綁定的對(duì)象的某些屬性起惕,那么怎么通知界面刷新呢涡贱?下面就給出兩種方案。
讓你的綁定數(shù)據(jù)類繼承BaseObservable疤祭,然后通過(guò)調(diào)用notifyPropertyChanged方法來(lái)通知界面屬性改變盼产,如下:

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,這樣編譯階段會(huì)生成BR.[property name]勺馆,然后使用這個(gè)調(diào)用方法notifyPropertyChanged就可以通知界面刷新了戏售。如果你的數(shù)據(jù)綁定類不能繼承BaseObservable,那你就只能自己實(shí)現(xiàn)Observable接口草穆,可以參考BaseObservable的實(shí)現(xiàn)灌灾。

Data Binding Library提供了很便利的類ObservableField,還有ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, 和 ObservableParcelable悲柱,基本上涵蓋了各種我們需要的類型锋喜。用法很簡(jiǎn)單,如下:

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

然后使用下面的代碼來(lái)訪問(wèn):

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

調(diào)用set方法時(shí),Data Binding Library就會(huì)自動(dòng)的幫我們通知界面刷新了嘿般。
綁定AdapterView段标,在一個(gè)實(shí)際的項(xiàng)目中,相信AdapterView是使用得很多的炉奴,使用官方提供給的API來(lái)進(jìn)行AdapterView的綁定需要寫(xiě)很多代碼逼庞,使用起來(lái)不方便,但是由于Data Binding Library提供豐富的擴(kuò)展功能瞻赶,所以出現(xiàn)了很多第三方的庫(kù)來(lái)擴(kuò)展它赛糟,下面就來(lái)介紹一個(gè)比較好用的庫(kù)binding-collection-adapter Github地址
使用的時(shí)候在你的build.gradle文件里面添加

compile 'me.tatarka:bindingcollectionadapter:0.16'

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

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

下面就是ViewModel的寫(xiě)法:

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

這里用到了ObservableList, 他會(huì)在items變化的時(shí)候自動(dòng)刷新界面 然后下面是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會(huì)根據(jù)屬性名去找對(duì)應(yīng)的set方法师逸,但是我們有時(shí)候需要自定義一些屬性司倚,Data Binding Library也提供了很便利的方法讓我們來(lái)實(shí)現(xiàn)。 比如我們想在layout文件里面設(shè)置ListView的emptyView篓像,以前這個(gè)是無(wú)法做到的对湃,只能在代碼里面通過(guò)調(diào)用setEmptyView來(lái)做; 但是現(xiàn)在借助Data Binding Library遗淳,我們可以很容易的實(shí)現(xiàn)這個(gè)功能了。先看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}"這個(gè)代碼就用來(lái)指定emptyView心傀,

下面來(lái)看下實(shí)現(xiàn)的代碼:

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

下面我們來(lái)分析上面的代碼屈暗,@{@id/empty_view}表示引用了@id/empty_view這個(gè)id,所以它的值就是int脂男,再看上面的setEmptyView方法养叛,第一個(gè)參數(shù)AdapterView adapterView表示使用emptyView這個(gè)屬性的控件,而第二個(gè)參數(shù)int viewId則是emptyView屬性傳進(jìn)來(lái)的值宰翅,上面的layout可以看出來(lái)它就是R.id.empty_view弃甥,然后通過(guò)id找到控件,然后調(diào)用原始的setEmptyView來(lái)設(shè)置汁讼。
上面的代碼來(lái)自我寫(xiě)的一個(gè)Data Binding Library的示例項(xiàng)目DataBinding-album-sampleGithub地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末淆攻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子嘿架,更是在濱河造成了極大的恐慌瓶珊,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件耸彪,死亡現(xiàn)場(chǎng)離奇詭異伞芹,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)唱较,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)扎唾,“玉大人,你說(shuō)我怎么就攤上這事南缓⌒赜觯” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵西乖,是天一觀的道長(zhǎng)狐榔。 經(jīng)常有香客問(wèn)我,道長(zhǎng)获雕,這世上最難降的妖魔是什么薄腻? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮届案,結(jié)果婚禮上庵楷,老公的妹妹穿的比我還像新娘。我一直安慰自己楣颠,他們只是感情好尽纽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著童漩,像睡著了一般弄贿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上矫膨,一...
    開(kāi)封第一講書(shū)人閱讀 51,718評(píng)論 1 305
  • 那天差凹,我揣著相機(jī)與錄音,去河邊找鬼侧馅。 笑死危尿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的馁痴。 我是一名探鬼主播谊娇,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼罗晕!你這毒婦竟也來(lái)了济欢?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤小渊,失蹤者是張志新(化名)和其女友劉穎船逮,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體粤铭,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡挖胃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酱鸭。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吗垮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出凹髓,到底是詐尸還是另有隱情烁登,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布蔚舀,位于F島的核電站饵沧,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏赌躺。R本人自食惡果不足惜狼牺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望礼患。 院中可真熱鬧是钥,春花似錦、人聲如沸缅叠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)肤粱。三九已至弹囚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間领曼,已是汗流浹背余寥。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悯森,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓绪撵,卻偏偏與公主長(zhǎng)得像瓢姻,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子音诈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容