Android MVVM到底是什麼?看完就會懂了
什麼是MVVM我們一步步來,從MVC開始。MVC我們都知道,模型——視圖——控制器。為了使得程序的各個部分分離降低耦合性,我們對代碼的結構進行了劃分。
他們的通信方式也如上圖所示,即View層觸發操作通知到業務層完成邏輯處理,業務層完成業務邏輯之後通知Model層更新數據,數據更新完之後通知View層展現。在實際運用中人們發現View和Model之間的依賴還是太強,希望他們可以絕對獨立的存在,慢慢的就演化出了MVP。
Presenter替換掉了Controller,不僅僅處理邏輯部分。而且還控制著View的刷新,監聽Model層的數據變化。這樣隔離掉View和Model的關係後使得View層變的非常的薄,沒有任何的邏輯部分又不用主動監聽數據,被稱之為“被動視圖”。
至於MVVM基本上和MVP一模一樣,感覺只是名字替換了一下。他的關鍵技術就是今天的主題(Data Binding)。View的變化可以自動的反應在ViewModel,ViewModel的數據變化也會自動反應到View上。這樣開發者就不用處理接收事件和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 name="user" type="com.example.User"/>
</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}"/>
</LinearLayout></layout>
上面定義了一個com.example.User類型的變量user,然後接著android:text="@{user.firstName}"把變量user的firstName屬性的值和TextView的text屬性綁定起來。
Data Object我們來看下上面用到的com.example.User對象。public class User {
public final String firstName;
public final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
他有兩個public的屬性firstName,lastName,這和上面layout文件裡面的@{user.firstName}和@ {user.lastName}對應
或者下面這種形式的對像也是支持的。public class User {
private final String firstName;
private final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// getXXX形式
public String getFirstName() {
return this.firstName;
}
// 或者属性名和方法名相同
public String lastName() {
return this.lastName;
}
}
綁定數據添加完<data>標籤後,Android Studio就會根據xml的文件名自動生成一個繼承ViewDataBinding的類。例如: activity_main.xml就會生成ActivityMainBinding,然後我們在Activity裡面添加如下代碼:@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
User user = new User("Test", "User");
binding.setUser(user);
}
綁定事件就像你可以在xml文件裡面使用屬性android:onClick綁定Activity裡面的一個方法一樣,Data Binding Library擴展了更多的事件可以用來綁定方法,比如View.OnLongClickListener有個方法onLongClick(),你就可以使用android:onLongClick屬性來綁定一個方法,需要注意的是綁定的方法的簽名必須和該屬性原本對應的方法的簽名完全一樣,否則編譯階段會報錯。
下面舉例來說明具體怎麼使用,先看用來綁定事件的類:public class MyHandlers {
public void onClickButton(View view) { ... }
public void afterFirstNameChanged(Editable s) { ... }
}
然後就是layout文件:<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.Handlers"/>
<variable name="user" type="com.example.User"/>
</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}"/>
</LinearLayout></layout>
表達式語言(Expression Language)你可以直接在layout文件裡面使用常見的表達式:
[*]數學表達式+ – / * %
[*]字符串鏈接 +
[*]邏輯操作符&& ||
[*]二元操作符& | ^
[*]一元操作符+ – ! ~
[*]Shift >> >>> <<
[*]比較== > < >= <=
[*]instanceof
[*]Grouping ()
[*]Literals – character, String, numeric, null
[*]Cast
[*]函數調用
[*]值域引用(Field access)
[*]通過[]訪問數組裡面的對象
[*]三元操作符?:
示例:android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
更多語法可以參考官網文檔:http://developer.android.com/too ... expression_language
更新界面有些時候,代碼會修改我們綁定的對象的某些屬性,那麼怎麼通知界面刷新呢?下面就給出兩種方案。
方案一讓你的綁定數據類繼承BaseObservable,然後通過調用notifyPropertyChanged方法來通知界面屬性改變,如下:
private static class User extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return this.firstName;
}
@Bindable
public String getLastName() {
return this.lastName;
}
public void setFirstName(String firstName){
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) { this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
在需要通知的屬性的get方法上加上@Bindable,這樣編譯階段會生成BR.,然後使用這個調用方法notifyPropertyChanged就可以通知界面刷新了。如果你的數據綁定類不能繼承BaseObservable,那你就只能自己實現Observable接口,可以參考BaseObservable的實現。
方案二Data Binding Library提供了很便利的類ObservableField,還有ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, 和ObservableParcelable,基本上涵蓋了各種我們需要的類型。用法很簡單,如下:
private static class User {
public final ObservableField<String> firstName = new ObservableField<>();
public final ObservableField<String> lastName = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
然後使用下面的代碼來訪問:
user.firstName.set("Google");int age = user.age.get();
調用set方法時,Data Binding Library就會自動的幫我們通知界面刷新了。
綁定AdapterView在一個實際的項目中,相信AdapterView是使用得很多的,使用官方提供給的API來進行AdapterView的綁定需要寫很多代碼,使用起來不方便,但是由於Data Binding Library提供豐富的擴展功能,所以出現了很多第三方的庫來擴展它,下面就來介紹一個比較好用的庫binding-collection-adapter,Github地址:https://github.com/evant/binding-collection-adapter
使用的時候在你的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 = new ObservableArrayList<>();
public final ItemView itemView = ItemView.of(BR.item, R.layout.item);
}
這裡用到了ObservableList,他會在items變化的時候自動刷新界面
然後下面是layout文件:
<!-- layout.xml --><layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="viewModel" type="com.example.ViewModel"/>
<import type="me.tatarka.bindingcollectionadapter.LayoutManagers" />
</data>
<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}"/></layout>
然後是item layout:
<!-- item.xml --><layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="item" type="String"/>
</data>
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{item}"/></layout>
如果有多種樣式的佈局,那麼就需要把ItemView換成ItemViewSelector,如下:public final ItemViewSelector<String> itemView = new BaseItemViewSelector<String>() {
@Override
public void select(ItemView itemView, int position, String item) {
itemView.set(BR.item, position == 0 ? R.layout.item_header : R.layout.item);
}
// This is only needed if you are using a BindingListViewAdapter
@Override
public int viewTypeCount() {
return 2;
}
};
自定義綁定正常情況下,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">
<data>
<variable
name="viewModel"
type="com.example.databinding.viewmodel.ViewAlbumsViewModel"/>
</data>
<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}"/>
</LinearLayout></layout>
這個代碼就用來指定emptyView,下面來看下實現的代碼:
@BindingAdapter("emptyView")
public static <T> void setEmptyView(AdapterView adapterView, int viewId) {
View rootView = adapterView.getRootView();
View emptyView = rootView.findViewById(viewId);
if (emptyView != null) {
adapterView.setEmptyView(emptyView);
}
}
下面我們來分析上面的代碼,@{@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-sample,Github地址:https://github.com/derron/DataBinding-album-sample
它基本上包含了開發一個app常用到的東西,大家有興趣可以通過閱讀原文去看看。
文章來源
頁:
[1]