高效開發 MVVM 和 databinding 你須要使用的工具

喜歡小之的文章的能夠關注公衆號「WeaponZhi」持續關注動態
java


相信很多同窗已經開始使用 MVVM做爲本身 Android 開發架構了,但實際上,我在使用過程當中查閱資料發現,網上有關 MVVM 的資料並非不少,這主要是由於 MVVM 仍是有必定使用門檻的,而且 MVVM 不必定會幫助你提升開發效率,可能你須要寫的代碼更多了,或者說爲了你爲了讓代碼保持 Databinding 的雙向綁定特性,而須要考慮不少業務之外的設計邏輯。咱們使用一個架構或者設計模式,固然是爲了更好的開發體驗嘛,因此我將給你們介紹幾個實用的第三方庫和工具,來幫助你們解決這些問題。

MVVMLight

「MVVMLight」這個第三方庫其實是對 Databinding 工具庫的一些擴展,而且經過ReplyCommandResponseCommand來對全部的 View 的事件進行統一封裝,這是我認爲 MVVMLight 最大的用處android

MVVMLight的官方介紹博客
MVVMLight的源碼地址git

咱們來看一下ReplyCommand怎麼用。咱們用常見的下拉刷新控件PullToRefreshLayout來舉例子。github

咱們知道若是你想自定義一個控件的事件,你須要使用@BindingAdapter註解,好比ImageView經過URL屬性直接根據地址下載圖片並顯示能夠這樣寫:設計模式

@BindingAdapter("bind:urlImage")  
public static void getInternetImage(ImageView iv, String userface) {  
    Picasso.with(iv.getContext()).load(userface).into(iv);  
}複製代碼
<ImageView android:id="@+id/iv" android:layout_width="wrap_content" android:layout_height="wrap_content" app:urlImage="@{user.urlImage}"/>複製代碼

這種狀況每每是比較簡單的,由於只是操做一個屬性,但咱們要自定義某一個事件該怎麼辦呢,好比咱們要自定義onClick事件,那可能就得寫接口了:網絡

@BindingAdapter("setImageOnClick")
    public static void setImageOnClick(ImageView imageView, final ImageOnClickListener listener){
        if (listener != null) {
            imageView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    listener.onClick(v);
                }
            });
        }
    }

interface ImageOnClickListener{
    void onClick(View v);
}複製代碼

使用的時候呢,你得在 VM 中定義一個ImageOnClickListener的成員變量listener,在裏面寫具體的onClick實現方法,而後在 xml 中經過app:setImageOnClick="viewModel.listener"來綁定這個事件。數據結構

固然,你能夠直接經過android:onClick來進行綁定,這裏只是實例。架構

看起來好像也不是很麻煩,可是你可能每個這樣的事件,就得定義一個特殊的接口,咱們能不能封裝一下呢?app

這就是 MVVMLight 中 ReplyCommand 和 ResponseCommand 作的事了。經過這兩個類封裝了各類請求參數數量和返回值參數數量的回調方法,在使用的時候,只要在泛型裏具體指名請求參數和返回值的類型便可,能夠說很方便了。ide

實例,PullToRefreshLayout 是一個刷新列表控件,咱們經過使用ReplyCommand監聽下拉刷新和上拉加載的監聽器是這樣寫的:

@BindView(R.id.refresh_listview)
PullToRefreshLayout pullToRefreshLayout;

...

@BindingAdapter (value = {"onRefreshCommand", "onLoadCommand"}, requireAll = false)
public static void onRefreshLoadCommand( final PullToRefreshLayout pullToRefreshLayout, final ReplyCommand onRefreshCommand, final ReplyCommand onLoadCommand) {

    pullToRefreshLayout.setOnRefreshListener(new PullToRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh(PullToRefreshLayout pullToRefreshLayout) {
            if (onRefreshCommand != null) {
                onRefreshCommand.execute();
            }
        }

        @Override
        public void onLoadMore(PullToRefreshLayout pullToRefreshLayout) {
            if (onLoadCommand != null) {
                onLoadCommand.execute();
            }
        }
    });
}複製代碼

咱們使用統一的ReplyCommand來處理控件的各類事件,這裏使用的是無參無返回值的最簡單的狀況,咱們在 ViewModel 和 xml 中的寫法是和以前的接口差很少的:

public final ReplyCommand onRefreshCommand = new ReplyCommand(() -> getPostData(true));

public final ReplyCommand onLoadCommand = new ReplyCommand(()->getPostData(true));複製代碼
<com.weapon.joker.lib.view.pullrefreshload.PullToRefreshLayout android:id="@+id/pull_refresh_layout" android:layout_width="match_parent" android:layout_height="match_parent" app:onRefreshCommand="@{viewModel.onRefreshCommand}" app:onLoadCommand="@{viewModel.onLoadCommand}"/>複製代碼

這樣,咱們全部事件的接口就統一了。ResponseCommand 和 ReplyCommand 的區別主要在,ResponseCommand 是用來定義那種有返回值的參數的,而 ReplyCommand 是沒有返回值的,具體的使用方法,你們能夠參考上面的連接,做者本身講的最詳細。

binding-collection-adapter

「binding-collection-adapter」對全部須要adapter的控件進行了封裝,好比一些經常使用的:ListViewRecyclerViewViewPager等,經過使用這個庫,咱們就不須要再寫 adapter 了,經過 databinding 的方式,在 xml 綁定一些屬性,並在 ViewModel 中對這些屬性進行處理便可完成這些控件的處理,邏輯清晰,代碼簡單。

GitHub 地址

下面舉一個 RecyclerView 的例子。咱們如今 xml 中定義一個 RecyclerView 控件。

<android.support.v7.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" app:layoutManager="@{LayoutManagers.linear()}" app:items="@{viewModel.items}" app:itemBinding="@{viewModel.itemBinding}"/>複製代碼

咱們看到有三個特殊的屬性:layoutManageritemsitemBinding,這裏的layoutManager你們都比較熟悉了,參數是在開頭的import導入的,傳入相關的類名便可。

<data>
    <variable name="viewModel" type="com.weaponzhi.test.ViewModel"/>
    <import type="com.weaponzhi.test.LayoutManagers"/>
</data>複製代碼

咱們先來看一下itemBinding是幹什麼用的,咱們知道有時候列表項是可能多佈局的,那麼這個itemBinding就是用來處理每種佈局和對應 item 的 ViewModel 的綁定關係的。上述代碼的 ViewModel 中,定義了該itemBinding

public final OnItemBindClass<Object> itemBinding =
    new OnItemBindClass<>
    .map(NoDataViewModel.class,BR.noData,R.layout.listitem_no_data)
    .map(ItemViewModel.class,BR.itemVM,R.layout.listitem_page);複製代碼

map方法中有三個參數,第一個參數是這個佈局的 ViewModel,第三個參數是這個佈局的 xml 文件,第二個參數這個 xml 中引入的 ViewModel 的 BR 文件 id。這樣咱們就綁定好了這個列表控件的多佈局邏輯了。一個空數據時候的佈局,一個正常返回數據時候的佈局。

那麼咱們的數據是如何刷新的呢,這就要用到上面的items這個屬性了,在咱們這個例子裏,它是這樣定義的:

public final ObservableList<Object> viewModels = new ObservableArrayList<>();複製代碼

當咱們網絡請求返回的時候,咱們在數據回調裏,經過對數據類型的處理,進行ItemViewModel的構造,最後只須要將構造好的對象一個個添加到這個ObservableList數據結構中去,界面的刷新工做都在對應的ItemViewModel裏中進行處理,咱們剛剛設置的itemBinding在這時候就起做用了,當新增數據的時候,它會先判斷這個更新數據的ItemViewModel的數據類型,NoDataViewModel.class類型的,那麼就使用R.layout.listitem_no_dataItemViewModel.class類型的,就使用R.layout.listitem_page。固然,其餘的數據更新和刪除操做,也會由於雙向綁定而同步刷新。

咱們徹底從 Adapter 的繁瑣中解放出來了!

Databinding support

這是一個 Android Studio 插件,咱們寫 xml 中的一些 Databind 代碼好比<layout><data><variable><import>等標籤的使用仍是比較多的,並且寫起來也比較繁瑣,這個插件就是能夠幫助你解放雙手,只須要在適當的地方按⌥+⏎(Windows 是 Alt+Enter)便可,從官網盜幾張 Gif 圖給你們感覺一下吧。

Wrap with <layout></layout>

Add <data> tag

Wrap with @{}

Wrap with @={}

Switch @{} and @={}

Add <import>

Add <variable>

MVVM 自動代碼生成

MVVM 和 MVP 這種架構並不必定會讓咱們代碼量減小,每個界面可能都要以一種固定的模式建立不少類,那咱們爲何不經過一種自動代碼生成工具來經過簡單的配置就完成這些類的建立呢,Java 徹底就能夠實現這些功能。網上有不少用 Java 實現的自動生成代碼的方式,但每一個人實現的 MVP 和 MVVM 架構方式都不一樣,因此自動化代碼也會不一樣,我來展現下我這邊使用的過程吧。

我使用的 MVVM 代碼生成工具的主要思路是比較簡單粗暴的,經過一個 xml 文件配置一些屬性,好比起一個名字,設置一下文件輸出的路徑,而後在 Java 裏用字符串拼接和文件流讀取的方式來生成模板代碼。


我如今維護的一個項目中,使用了 MVVMLight 和 binding-collection-adapter 你們能夠參考下。
GitHub 連接


期待您關注個人公衆號:WeaponZhi

相關文章
相關標籤/搜索