Android MVVM到底是啥?看完就明白了 [復制鏈接]

2018-12-7 10:21
sergiochanTest 閱讀:500 評論:0 贊:0
Tag:  

什么是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 &lt; 13 ? View.GONE : View.VISIBLE}"
    android:transitionName='@{"image_" + id}'

更多語法可以參考官網文檔:http://developer.android.com/tools/data-binding/guide.html#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.[property name],然后使用這個調用方法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>

app:emptyView="@{@id/empty_view}"這個代碼就用來指定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來設置。


我來說兩句
您需要登錄后才可以評論 登錄 | 立即注冊
facelist
所有評論(0)
領先的中文移動開發者社區
18620764416
7*24全天服務
意見反饋:[email protected]

掃一掃關注我們

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粵ICP備15117877號 )

两码中特期期