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地址