Android MVVM的沉思


大概有一個月的時間,我都在反思移動端應用的架構,嘗試過mvc,這大概是大部分人剛開始入門的時候使用的模式了。然而細思一下,在android裏面這種架構恐怕行不通,比如,我們的activity,要獲得系統服務的話都要通過getService(*)之類的代碼,這無意中就讓activity的責任亂了起來,按理說activity應該就是一個view,它不應該擁有類似controller的職責。
於是,我開始嘗試Mvp。它的確是個好東西,一般我們都是通過dagger2,rxjava,etc等來實現mvp,但是如果,數據改變了,我們的presenter就要提醒view視圖改變了,然而,這似乎又增加了控制層和視圖層的耦合度。所以,在google 的 I/O大會上,google提出了data binding (可用於實現mvvm,其中ViewModel可以理解成是View的數據模型和Presenter的合體,ViewModel和View之間的交互通過Data Binding完成,而Data Binding可以實現雙向的交互,這就使得視圖和控制層之間的耦合程度進一步降低,關注點分離更爲徹底,同時減輕了Activity的壓力), 三者之間的差別:

我摘一個來自知乎的圖片來解釋android 架構之間的區別:
這裏寫圖片描述

OK!

在這裏,我假設所有的讀者都是使用的android studio,如果要使用data binding的話在build.gradle裏面添加:

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    dataBinding {
        enabled = true
    }

    defaultConfig {
        applicationId "com.chan.databindingdemo"
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dataBinding {
enable = true
}
便可以了

在官網上,google介紹了data binding如何結合ListView 和 recycler view,這裏我們只講解recycler view
我們假設,我們要做一個標籤應用,這個應用非常簡單,就是一個recycler view,它其中的項只有各種label,於是我們自定義一個類:


public class DemoItem {
    private String m_label;

    public DemoItem(String label) {
        m_label = label;
    }

    public String getLabel() {
        return m_label;
    }

    public void setLabel(String label) {
        m_label = label;
    }
}

代碼非常簡單,就一個label field,和一些getter & setter
對於recycler中的子項,我們顯然是要自定義佈局的,比如:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="data"
            type="com.chan.databindingdemo.DemoItem"/>
    </data>

    <LinearLayout android:orientation="vertical"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content">
        <TextView
            android:id="@+id/label"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{data.label}"/>
    </LinearLayout>
</layout>

我們可以看到,這個layout的xml文件根節點是layout 並且,還定義了名爲data類型爲com.chan.databindingdemo.DemoItem的數據。它有一個data節點包裹,然後以variable的屬性給出
往下閱讀便是recycler view的內容視圖了,很簡單 就一個TextView,我們可以看到對於TextView的android:text屬性,我們給出了它的值@{data.label}。也就是說這個text view的內容由data得label屬性給出。非常簡單吧,這時候,編譯器便給我們生成了一個ViewDataBinding類,這個類所在的包名是以我們工程包+binding決定的,比如我現在的應用包名是com.chan.databindingdemo,那麼這個編譯器生成的代碼便在com.chan.databindingdemo.databinding下,對於這個類的名字呢是以上文的Layout文件名決定的,比如:我的layout文件名是item_layout.xml,那麼生成的類就爲ItemLayoutBinding。

我假設讀者在讀本文的時候,已經通曉recycler view的使用方法,因爲本文並不對基本的內容做講解

好的,然後我們開始定義view holder了
demo:

/**
 * Created by chan on 16/3/12.
 */
public class RecyclerHolder extends RecyclerView.ViewHolder {
    ItemLayoutBinding m_itemLayoutBinding;

    public RecyclerHolder(View itemView, ViewDataBinding viewDataBinding) {
        super(itemView);
        m_itemLayoutBinding = (ItemLayoutBinding) viewDataBinding;
    }

    public void bind(DemoItem demoItem) {
        m_itemLayoutBinding.setData(demoItem);
    }
}

我們的RecyclerHolder的構造函數多了一個view data binding,並且這個類型的真實類型爲ItemLayouBinding,就是剛剛編譯器自動生成的那個類。一個ViewModel要和view進行綁定,需要調用binding的set方法,具體的set方法名根據你在上文的layout文件的variable的name屬性決定,所以我們這裏是m_itemLayoutBinding.setData(demoItem);

好了現在我們開始實現adapter

/**
 * Created by chan on 16/3/12.
 */
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerHolder> {

    private LayoutInflater m_layoutInflater;
    private List<DemoItem> m_demoItems;

    public RecyclerAdapter(Context context, List<DemoItem> demoItems) {
        m_layoutInflater = LayoutInflater.from(context);
        m_demoItems = demoItems;
    }

    @Override
    public RecyclerHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ViewDataBinding viewDataBinding = DataBindingUtil.inflate(m_layoutInflater, R.layout.item_layout, parent, false);
        return new RecyclerHolder(viewDataBinding.getRoot(), viewDataBinding);
    }

    @Override
    public void onBindViewHolder(RecyclerHolder holder, int position) {
        final int currentPosition = holder.getLayoutPosition();
        holder.bind(m_demoItems.get(currentPosition));
    }

    @Override
    public int getItemCount() {
        return m_demoItems.size();
    }
}

值得注意的是,如果我們要inflate視圖,必須要通過DataBindingUtil來實現!!否則會拋出一個非捕獲異常!!
也就是這裏的:

   ViewDataBinding viewDataBinding = DataBindingUtil.inflate(m_layoutInflater, R.layout.item_layout, parent, false);

這裏寫圖片描述

一個簡單的demo出現了 ,不過我們剛剛講過,如果數據改變了,view能自動刷新那就好了,哎呦,我們可以引入觀察者模式哦(最近這個比較火!!!)

還記得剛剛的model嗎?我們只需要一個簡單的修改就可以完成了

/**
 * Created by chan on 16/3/12.
 */
public class DemoItem extends BaseObservable {
    private String m_label;

    public DemoItem(String label) {
        m_label = label;
    }

    @Bindable
    public String getLabel() {
        return m_label;
    }

    public void setLabel(String label) {
        m_label = label;
        notifyPropertyChanged(com.chan.databindingdemo.BR.label);
    }
}

如果一個應用要實現可觀察,只要實現 Observable這個藉口就好了,原文:A class implementing the Observable interface will allow the binding to attach a single listener to a bound object to listen for changes of all properties on that object.
不過庫給我們實現了一個簡單的對象BaseObservable,它可以滿足我們的簡單需求。
我們在getter上加了一個bindable註解,並且在setter裏面,我們手動提醒了變化

有一些謎團,比如 com.chan.databindingdemo.BR.label是啥,我引入一下原文:
The Bindable annotation generates an entry in the BR class file during compilation. The BR class file will be generated in the module package. If the base class for data classes cannot be changed, the Observable interface may be implemented using the convenient PropertyChangeRegistry to store and notify listeners efficiently.

在編譯時期,bindable註解會在BR裏面生成一個項,這個項就是針對上文的getter。並且這個br類生成在項目的包下。

我們看下完整的activity代碼:

public class MainActivity extends AppCompatActivity {
    private RecyclerView m_recyclerView;
    private List<DemoItem> m_demoItems = new ArrayList<>();
    private int m_index = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        m_recyclerView = (RecyclerView) findViewById(R.id.id_recycler);
        m_recyclerView.setLayoutManager(new LinearLayoutManager(this));

        for(int i = 0; i < 10; ++i) {
            m_demoItems.add(new DemoItem("標籤" + i));
        }

        RecyclerAdapter adapter = new RecyclerAdapter(this, m_demoItems);
        m_recyclerView.setAdapter(adapter);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                m_demoItems.get(m_index).setLabel(System.currentTimeMillis() + "");
                m_index = (m_index + 1) % m_demoItems.size();
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

效果圖:
這裏寫圖片描述

然而 csdn爲啥不讓我上傳代碼?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章