design.jpg

Mvp與Mvvm已經出現很久了,但是還有很多開發者沒有運用到自己的項目中。正好最近工作不是很忙,這两天空餘時間就寫了個demo,來給還沒有運用到實戰項目的小夥伴一個參考,如果有什麼問題可以直接給我留言,Ok我們進入正題~

先看一下效果圖(由於平台上傳gif圖有大小限制,所以就分成兩個gif上傳的)


mvp.gif

mvvm.gif

先簡單介紹一下demo

為了方便,demo中下拉刷新上拉加載直接用的xRecyclerView庫。列表類似於新聞客戶端多布局模式(無圖-1張圖-多圖)。以及app里非常常見的點贊功能,分別在mvp設計模式與mvvm設計模式中實現。無論是mvp還是mvvm或其他的設計模式,建議都不要生搬硬套,靈活的運用和多變的實現才是掌握一種設計思想的最高境界(當然前提是不能脫離所運用的模式)。

包結構


package.png

mvp與mvvm分為兩個包,看代碼的時候可以分開看,common 裡邊是簡單封裝的BaseAdapter,感興趣的可以下載源碼看看,這裏就不貼代碼了。下面的講解中,每一個功能點對應的mvp實現方式與mvvm實現方式我會對照着來講。本篇文章只針對demo中所涉及到的知識點來講。

ok到了我們最重要的部分


mvp.jpg

mvvm.jpg

mvp與mvvm都是非常棒的設計模式,但是都各有利弊,在這裏我就不長篇大論的去墨跡了,相信你們已經多少有點了解了。

先來說一下Mvp

從上面的mvp.jpg圖中我們可以看到,mvp分為三層,分別為Model、View、Presenter。

1.view層調用presenter。
2.presenter收到view的信號,調用具體的業務邏輯model層。
3.model層收到了presenter的信號,開始進行具體的業務邏輯處理。
比如(網絡請求-數據庫存取...)
4.model層 將具體的業務邏輯處理完之後通過接口回調的形式
將結果返回給presenter。
5.presenter收到model返回的結果同樣以接口的形式返回給
view層做相應的显示

Mvvm
從上面的mvvm.jpg圖中我們可以看到,mvvm也是分為三層,分別為Model、View、ViewModel。

說到mvvm設計模式,就要涉及到一個庫 databinding,
在AS中 使用起來也是比較方便的
直接在module:app中的 build.gradle中打開databinding的支持就可以了

android {
    ....
    dataBinding {
        enabled = true
    }
}

具體的詳解可以看google的開發文檔(demo中所涉及到的databinding的知識點我會講解)
Data Binding

1.view層接收到用戶的操作傳遞給viewModel層
(View層與ViewModel 通過dataBinding 
實現數據與view的單向綁定或雙向綁定)
2.ViewModel 層接收到用戶傳來的信號,調用model層。
3.model層收到viewModel的信號進行具體的業務邏輯處理。
4.model層將結果通過接口的形式傳遞給viewModel層 
5.因為viewModel與view通過databinding實現了綁定,viewModel接收到model傳來的結果做出相應的view展示

通過上邊的大白話描述~ 我們可以看到 mvp中 presenter 起到了橋接的作用,view 層 與model層的通訊 通過中間量presenter 以接口的形式進行傳遞。mvvm中 viewModel 和presenter 的作用類似 ,只不過是通過 databinding 將數據與ui進行了綁定。

mvvm中 xml 對應的格式

<layout
 xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="name"
            type="type"/>
    </data>
<ViewGroup></ViewGroup>
</layout>

與以往的xml形式不同。最外層為 layout,裡邊有data標籤 ,data標籤里有variable變量屬性 。ViewGroup標籤中的內容才和我們之前的xml是一樣的。
所有以layout為根標籤的布局都會生成對應的ViewDataBinding 類,比如 MainActivity的xml名字為 activity_main ,databinding就會自動為我們生成 ActivityMainBinding。

獲取ActivityMainBinding的方法

在MainActivity 的oncreate方法中獲取
ActivityMainDatabinding

ActivityMainBinding binding = 
DataBindingUtil.setContentView
(this, R.layout.activity_main);

之前設置布局文件的方式為setContentView(layoutId)

用databinding設置布局文件統一通過DataBindingUtil類來設置

下面看一下demo中具體的代碼實現

mvvm中BaseActivity的實現


mvvm_base_activity.png

mvvm_base_activity_oncreate.png

從圖中可以看出 我們通過泛型 VDB 來指定ViewDatabinding的類型。

mvp中BaseActivity的實現


mvp_base_activity.png

在mvp中我們依然通過泛型來指定IView接口 與 Presenter 的類型(方便在presenter中綁定Activity或Fragment的生命周期),presenter方便做attach 與 detach的操作。

MvvmActivity中的代碼

/**
 * Created by mj
 * on 2017/5/22.
 */
public class MvVmActivity extends BaseMvVmActivity<ActivityMvVmBinding> implements XRecyclerView.LoadingListener {

    /**
     * 新聞列表 adapter
     */
    NewsListAdapter adapter;
    /**
     * view model
     */
    NewsListVm newsListVm;

    @Override
    public int getLayoutId() {
        return R.layout.activity_mv_vm;
    }

    @Override
    public void init(Bundle savedInstanceState) {
        initAdapter();
        initVM();
    }

    /**
     * 初始化adapter
     */

    private void initAdapter() {
        XRecyclerView xRecyclerView = viewDataBinding.activityMvVmList;

        xRecyclerView.setLoadingListener(this);
        xRecyclerView.setLayoutManager(new LinearLayoutManager(context));
        xRecyclerView.setArrowImageView(R.mipmap.pull_down_arrow);
        xRecyclerView.setRefreshProgressStyle(ProgressStyle.BallClipRotate);
        xRecyclerView.setLoadingMoreProgressStyle(ProgressStyle.BallClipRotate);

        adapter = new NewsListAdapter();
        xRecyclerView.setAdapter(adapter);
    }

    /**
     * 初始化viewModel
     */
    private void initVM() {
        newsListVm = new NewsListVm(context,viewDataBinding, adapter);
    }

    @Override
    public void onRefresh() {
        newsListVm.setRefreshData();
    }

    @Override
    public void onLoadMore() {
        newsListVm.loadMoreData();
    }


}

在mvvm中我們把用戶對view的操作以及UI的樣式 放在 activity中或xml中,在上面的代碼中我們可以看到,下拉刷新和上拉加載以及 刷新的樣式 都放到Activity中,在下拉刷新和上拉加載的代碼中通過newsListVm (viewModel的實例)來調用刷新數據和加載數據

MvpActivity中的代碼

/**
 * Created by mj
 * on 2017/5/22.
 */

public class MvpActivity extends BaseMvpActivity<INewsListView, NewsListPresenterImpl> implements INewsListView, XRecyclerView.LoadingListener {
    /**
     * 首次加載
     */
    public static final int FIRST_LOAD = 0;
    /**
     * 刷新
     */
    public static final int REFRESH = 1;
    /**
     * 加載更多
     */
    public static final int LOAD_MORE = 2;
    /**
     * recyclerView
     */
    @BindView(R.id.mvp_list_view)
    XRecyclerView recyclerView;
    /**
     * adapter
     */
    NewsListAdapter adapter;
    /**
     * 頁數
     */
    private int pageNum = 1;

    @Override
    public NewsListPresenterImpl initPresenter() {
        return new NewsListPresenterImpl();
    }

    @Override
    public int getLayoutId() {
        return R.layout.activity_mvp;
    }

    @Override
    public void init(Bundle savedInstanceState) {
        initAdapter();
        firstLoadData();
    }

    /**
     * 初始化adapter
     */
    private void initAdapter() {
        LinearLayoutManager lm = new LinearLayoutManager(context);
        recyclerView.setLoadingListener(this);
        recyclerView.setLayoutManager(lm);
        recyclerView.setArrowImageView(R.mipmap.pull_down_arrow);
        recyclerView.setRefreshProgressStyle(ProgressStyle.BallPulseSync);
        recyclerView.setLoadingMoreProgressStyle(ProgressStyle.BallPulseSync);

        adapter = new NewsListAdapter();
        recyclerView.setAdapter(adapter);
    }

    /**
     * 首次加載數據
     */
    private void firstLoadData() {
        presenter.loadNewsList(FIRST_LOAD, pageNum);
    }

    /**
     * 刷新數據
     */
    private void refreshData() {
        pageNum = 1;
        presenter.loadNewsList(REFRESH, pageNum);
    }

    /**
     * 加載更多
     */
    private void loadMoreData() {
        pageNum++;
        presenter.loadNewsList(LOAD_MORE, pageNum);
    }

    /**
     * 下拉刷新
     */
    @Override
    public void onRefresh() {
        refreshData();
    }

    /**
     * 加載更多
     */

    @Override
    public void onLoadMore() {
        loadMoreData();
    }

    /**
     * 加載成功回調
     *
     * @param data     列表數據
     * @param loadType 加載類型
     */
    @Override
    public void loadSuccess(List<NewsEntity> data, int loadType) {
        switch (loadType) {
            case FIRST_LOAD:
                adapter.refreshData(data);
                recyclerView.refreshComplete();
                break;
            case REFRESH:
                adapter.refreshData(data);
                recyclerView.refreshComplete();
                break;
            case LOAD_MORE:
                adapter.loadMoreData(data);
                recyclerView.loadMoreComplete();
                break;
        }
    }

    /**
     * 加載出錯
     *
     * @param msg 錯誤信息
     */
    @Override
    public void showError(String msg) {
        ToastUtils.show(context, msg);
        if (pageNum > 1) {
            pageNum--;
        }
        recyclerView.refreshComplete();
        recyclerView.loadMoreComplete();
    }

    /**
     * 展示加載進度
     *
     * @param msg 加載信息
     */
    @Override
    public void showLoading(String msg) {
        PromptDialog.getInstance().show(context,msg);
    }

    /**
     * 關閉加載進度
     */
    @Override
    public void hideLoading() {
        PromptDialog.getInstance().close();
    }

}

在mvp的activity中我們通過presenter的實例來調用刷新或加載更多並且處理相應的view显示 (PromptDialog 為Utils中封裝的一個簡單系統加載框)

mvvm中的viewModel 代碼

/**
 * Created by mj
 * on 2017/5/22.
 * viewModel
 */

public class NewsListVm implements INewsListModel.LoadResponse {
    /**
     * 首次加載
     */
    private final int FIRST_LOAD = 0;
    /**
     * 下拉刷新
     */
    private final int REFRESH = 1;
    /**
     * 加載更多
     */
    private final int LOAD_MORE = 2;
    /**
     * binding
     */
    private ActivityMvVmBinding binding;
    /**
     * adapter
     */
    private NewsListAdapter adapter;
    /**
     * 加載列表數據業務邏輯
     */
    private NewsListModelBiz newsListModelBiz;
    /**
     * 頁數
     */
    private int pageNum = 1;
    /**
     * context
     */
    private Context context;

    public NewsListVm(Context context, ActivityMvVmBinding binding, NewsListAdapter adapter) {
        this.context = context;
        this.binding = binding;
        this.adapter = adapter;
        init();
    }

    /**
     * 初始化
     */
    private void init() {
        newsListModelBiz = new NewsListModelBiz();
        firstLoadData();
    }

    /**
     * 首次加載
     */
    private void firstLoadData() {
        PromptDialog.getInstance().show(context, "加載中...");
        newsListModelBiz.load(FIRST_LOAD, pageNum, this);
    }

    /**
     * 刷新數據
     */
    public void setRefreshData() {
        pageNum = 1;
        newsListModelBiz.load(REFRESH, pageNum, this);
    }

    /**
     * 加載更多
     */
    public void loadMoreData() {
        pageNum++;
        newsListModelBiz.load(LOAD_MORE, pageNum, this);
    }

    @Override
    public void loadSuccess(List<NewsEntity> data, int loadType) {
        switch (loadType) {
            case FIRST_LOAD:
                adapter.refreshData(data);
                binding.activityMvVmList.refreshComplete();
                break;
            case REFRESH:
                adapter.refreshData(data);
                binding.activityMvVmList.refreshComplete();
                break;
            case LOAD_MORE:
                adapter.loadMoreData(data);
                binding.activityMvVmList.loadMoreComplete();
                break;
        }
        PromptDialog.getInstance().close();
    }

    @Override
    public void loadFailure(String msg) {
        // 加載失敗后的提示
        if (pageNum > 1) {
            pageNum--;
        }
        PromptDialog.getInstance().close();
        binding.activityMvVmList.refreshComplete();
        binding.activityMvVmList.loadMoreComplete();
    }

}

viewModel中的代碼也比較簡單 主要是調用model層 加載數據 以及對model層返回的結果進行显示。

mvp中的presenter 代碼

/**
 * Created by mj
 * on 2017/5/22.
 */

public class NewsListPresenterImpl extends BasePresenter<INewsListView> implements INewsListPresenterBiz, INewsListModelBiz.LoadResponse {
    /**
     * 加載列表數據的業務邏輯處理
     */
    private INewsListModelBiz iNewsListModelBiz;

    public NewsListPresenterImpl() {
        iNewsListModelBiz = new NewsListModelImpl();
    }

    @Override
    public void loadNewsList(int loadType,int pageNum) {
        if (mView != null) {
            // 首次進入界面展示加載對話框
            if (loadType == MvpActivity.FIRST_LOAD) {
                mView.showLoading("加載中...");
            }
            iNewsListModelBiz.load(loadType,pageNum, this);
        }
    }

    @Override
    public void loadSuccess(List<NewsEntity> data, int loadType) {
        if (mView != null) {
            mView.loadSuccess(data, loadType);
            mView.hideLoading();
        }
    }

    @Override
    public void loadFailure(String msg) {
        if (mView != null) {
            mView.showError(msg);
            mView.hideLoading();
        }
    }


}

與mvvm中的viewModel類似 都是調用 model層 以及對model層返回的結果做UI显示。omg~ 感覺這樣粘上來的代碼使文章變得很長~~~ 我盡量簡化一下,大家可以下載demo具體的看一下實現過程。

model層我就不再粘貼代碼了~ mvp中的model 層 與 mvvm中的model層是一樣的,都是做數據的準備 ,以及通過回調形式將數據返回。接下來我們看一下mvvm中 的adapter 的實現方式

Mvvm Adapter中的代碼

/**
 * Created by mj
 * on 2017/5/22.
 */

public class NewsListAdapter extends BaseAdapter<NewsEntity, BindingVH> {

    /**
     * 沒有圖片的item 類型
     */
    private final int NO_PIC = 0;
    /**
     * 有一張圖片的item 類型
     */
    private final int ONE_PIC = 1;
    /**
     * 更多圖片的item 類型
     */
    private final int MORE_PIC = 2;

    /**
     * 根據圖片數量判斷item 的類型
     *
     * @param position position
     * @return itemType
     */
    @Override
    public int getItemViewType(int position) {
        if (data.get(position).getPicNum() == 0) {
            return NO_PIC;
        } else if (data.get(position).getPicNum() == 1) {
            return ONE_PIC;
        } else {
            return MORE_PIC;
        }
    }

    @Override
    public BindingVH createVH(ViewGroup parent, int viewType) {
        ViewDataBinding viewDataBinding = null;
        switch (viewType) {
            case NO_PIC:
                viewDataBinding = DataBindingUtil.inflate(inflater, R.layout.mv_vm_item_text, parent, false);
                break;
            case ONE_PIC:
                viewDataBinding = DataBindingUtil.inflate(inflater, R.layout.mv_vm_item_one_pic, parent, false);
                break;
            case MORE_PIC:
                viewDataBinding = DataBindingUtil.inflate(inflater, R.layout.mv_vm_item_more_pic, parent, false);
                break;
        }
        return new BindingVH<>(viewDataBinding);
    }

    @Override
    public void bindVH(BindingVH bindingVH, int position) {
        bindingVH.getBinding().setVariable(BR.newsEntity, data.get(position));
        bindingVH.getBinding().setVariable(BR.handle, this);
        bindingVH.getBinding().setVariable(BR.position, position);

        bindingVH.getBinding().executePendingBindings();
    }

    /**
     * 點贊
     *
     * @param newsEntity entity
     */
    public void thumbUpClick(NewsEntity newsEntity, int position) {

        if (newsEntity.isNice()) {
            newsEntity.setNice(false);
            newsEntity.setNiceCount(newsEntity.getNiceCount() - 1);
            ToastUtils.show(context, "取消點贊 position=" + position);

        } else {
            newsEntity.setNice(true);
            newsEntity.setNiceCount(newsEntity.getNiceCount() + 1);
            ToastUtils.show(context, "點贊成功 position=" + position);
        }

    }


}

實現一個3種item形式的adapter,僅僅用了不到100行的代碼(其中還包括空行和註釋) 主要歸功於 databinding 為我們承受了成噸的傷害~ 。
createVH(ViewGroup parent, int viewType)方法與
bindVH(BindingVH bindingVH, int position)方法都是BaseAdapter中的抽象類 和recyclerview.Adapter中的onCreateViewHolder()與onBindViewHolder()是一樣的。

我們先來看一下 createVH方法中 返回了一個BindingVH的對象,並且根據不同的viewType 返回了不同的ViewDataBinding,那麼 這個BindingVH 一定是ViewDataBinding的子類啦 ~ ,繼續上代碼~

/**
 * Created by mj
 * on 2017/5/22.
 * binding view holder
 */

public class BindingVH<B extends ViewDataBinding> extends RecyclerView.ViewHolder {
    /**
     * viewDataBinding
     */
    private B mBinding;

    /**
     * constructor
     *
     * @param binding viewDataBinding
     */
    public BindingVH(B binding) {
        super(binding.getRoot());
        mBinding = binding;
    }

    /**
     * @return viewDataBinding
     */
    public B getBinding() {
        return mBinding;
    }

}

BindingVH繼承自RecyclerView.ViewHolder 這個類,並且提供了獲取binding的方法 。

我們繼續回到createVH這個方法中。在這裏 我們設置布局的方式是通過 DatabindingUtil.inflate(…) 方法獲取的ViewDataBinding,我們在看一下 多圖布局的xml布局(另外兩個xml都類似 我們就針對一個來說吧~)

<?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>

        <import type="com.mj.design_patterns.R"/>

        <import type="android.view.View"/>

        <variable
            name="newsEntity"
            type="com.mj.design_patterns.mv_vm.bean.NewsEntity"/>

        <variable
            name="handle"
            type="com.mj.design_patterns.mv_vm.adapter.NewsListAdapter"/>

        <variable
            name="position"
            type="int"/>

    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/white"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dip"
            android:layout_marginRight="10dip"
            android:layout_marginTop="10dip"
            android:ellipsize="end"
            android:maxLines="1"
            android:text="@{newsEntity.content}"
            android:textColor="@color/c3"
            android:textSize="15sp"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="60dip"
            android:layout_margin="10dip">

            <ImageView
                android:layout_width="0dip"
                android:layout_height="match_parent"
                android:layout_marginRight="5dip"
                android:layout_weight="1"
                android:scaleType="centerCrop"
                app:imageUrl="@{newsEntity.imageUrls[0]}"/>

            <ImageView
                android:layout_width="0dip"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:scaleType="centerCrop"
                app:imageUrl="@{newsEntity.imageUrls[1]}"/>

            <ImageView
                android:layout_width="0dip"
                android:layout_height="match_parent"
                android:layout_marginLeft="5dip"
                android:layout_weight="1"
                android:scaleType="centerCrop"
                android:visibility="@{newsEntity.getPicNum == 2 ? View.GONE : View.VISIBLE}"
                app:imageUrl="@{newsEntity.imageUrls[2]}"/>

        </LinearLayout>


        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dip"
            android:layout_marginLeft="10dip"
            android:layout_marginRight="10dip"
            android:gravity="center_vertical"
            android:orientation="horizontal">

            <TextView
                android:layout_width="0dip"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="@{newsEntity.getDateSplicingPageNum}"
                android:textColor="@color/c6"
                android:textSize="12sp"/>

            <!--android:src="@{newsEntity.isNice ? @drawable/nice_press :@drawable/nice_normal}"-->

            <ImageView
                android:layout_width="15dip"
                android:layout_height="15dip"
                android:onClick="@{()->handle.thumbUpClick(newsEntity,position)}"
                app:resId="@{newsEntity.isNice ? R.mipmap.nice_press : R.mipmap.nice_normal}"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dip"
                android:text="@{String.valueOf(newsEntity.niceCount)}"
                android:textColor="@{newsEntity.isNice ? @color/appColor : @color/c6}"
                android:textSize="12sp"/>


        </LinearLayout>

        <View
            android:layout_width="match_parent"
            android:layout_height="1px"
            android:background="@color/listBgC"/>
    </LinearLayout>
</layout>

在這個xml布局中我們在結合adapter類一起來說一下demo中的數據以及事件是如何綁定到xml中的。其實在adapter中每一個item都相當於一個ViewModel

我們就從布局中的信息從上往下說吧~ xml中的格式我前面已經說過了 ,就不在贅述了。

在data標籤中 我們看到了import 沒錯在這裏我們可以引入包~ (在使用databinding時除了java.lang的包不需要import 其他xml中用到的都需要import進來)

variable 標籤中 name屬性的值是你隨意定義的(前提是你要知道定義這個名字的意義) type屬性就是我們需要導入的包了~ 比如我們用到了 NewsEntity這個實體類 我們就需要導入這個包,可以這個實體是怎麼設置進來的呢? 我們來看adapter中的bindVH方法


bind_vh.jpg

我們通過viewDataBinding 把數據或事件傳遞進去。因為我們沒有 獲取具體的binding類型,所以我們通過調用setVariable(a,b)來設置。
a代表:通過BR類來查找xml中variable標籤中屬性name定義的名字
b代表:事件或數據

否則可以通過具體的binding類型實例來設置,比如


set_var.jpg

最後通過viewDatabinding.executePendingBindings(); 來實現綁定。
看上邊xml布局 文本的綁定方式為:


binding_text.jpg

所有xml中無論是事件綁定還是數據綁定都要這種格式@{…}
因為我們已經設置了newsEntity 所以在我畫圈的地方可以直接使用

圖片的綁定:


binding_image.jpg

可是我們 實體裡邊肯定是存的一個圖片地址啊~~ 這怎麼實現綁定呢! 沒關係 databinding已經給我們提供了方法:通過添加@BindingAdapter註解 databinding 就會為我們生成一個 attr對應的屬性

/**
 * Created by mj
 * on 2017/5/22.
 */

public class BindImageAdapter {
    /**
     * mv_vm xml 傳入url 加載圖片
     * imageUrl 為xml中 的命名
     *
     * @param iv   imageView
     * @param path 圖片路徑
     */
    @BindingAdapter({"imageUrl"})
    public static void loadImage(ImageView iv, String path) {
        Glide.with(iv.getContext()).load(path).into(iv);
    }

    /**
     * mv_vm xml 設置 mipmap Resource
     *
     * @param iv    imageView
     * @param resId resource id
     */
    @BindingAdapter({"resId"})
    public static void loadMipmapResource(ImageView iv, int resId) {
        iv.setImageResource(resId);
    }

}

我們將圖片地址傳進來 我們在用圖片加載框架對其進行加載。
@BindingAdapter({“imageUrl”}) imageUrl就是 對應的xml中 自定義的屬性名字。這樣就實現了圖片的綁定。我們實體類中是 imageUrls 是一個string數組類型 所以 我們在xml中就直接可以使用下標的方式獲取圖片的url(順便提一下 ,在xml實現綁定時 我們根本不用擔心數組越界 對象為空這些~~ 因為 databinding已經為我們做好了處理)

接下來我們看一下點贊的事件綁定:


event_binding.jpg

喔~ 這裏支持lambda 寫法 前面已經寫過了 在bindVH方法中設置handle 所以我們這裏可以直接用。看handle 所指向的方法在adapter中的實現


event_method.jpg

在xml中的onClick方法直接指向了adapter的thumbUpClick方法~ ,以及參數也是在xml中傳遞過來的~~ 那麼看看我們的點贊 到底是如何實現雙向綁定的呢~~ 相信很多同學剛才已經看到了 上圖中ImageView中的resId屬性了~ 。裡邊寫了一個三元表達式,也就是說 如果 newEntity中的 isNice為true 就显示 聚焦狀態的點贊圖片,否則显示正常的點贊圖片
在newsEntity 類里繼承了 dataBinding 的 BaseObservable,並且看下圖:


entity.jpg

isNice方法添加了Bindable 註釋 並且 setNice方法裡邊調用了notifyChange()方法 。這樣就實現了雙向綁定 數據改變UI改變 UI改變數據也隨之改變的邏輯~~ 。(還有其他的方式 我就不贅述了,請看google文檔,前面已經給了傳送門)。

吐槽一下 databinding 居然不支持在xml中設置@mipmap,只支持@drawable。 所以有心的同學在上面已經看到了 我們是通過@BindingAdapter方式來實現的(可以上滑看一下)

看上面的gif圖 日期後邊我拼接了一個當前頁數。這裡在啰嗦一下 在xml中使用字符串的拼接~ (xml不允許寫入中文,否則報錯)
我們可以這樣實現拼接

android:text="@{@string/listPageNumDisplay(newsEntity.dateStr,newsEntity.pageNum)}"

在strings.xml中定義替換
<string name="listPageNumDisplay">%1$s--##第%2$d頁數據</string>

但是在我們的項目中 沒有使用這種方式
而是直接在實體類裡邊(NewsEntity)單提出一個方法在xml方便調用

/**
     * 獲取日期拼接頁數的字符串
     * @return 格式--> 2017年5月23日--##第1頁數據
     */
    public String getDateSplicingPageNum(){
        return dateStr+"--##第"+pageNum+"頁數據";
    }

實現方式有很多種~  大家可以動手去嘗試一下

mvp中的adapter實現我就不再粘貼了~ 建議還是直接下載代碼來看,比較直觀方便(有什麼問題可以給我留言)我就不再多啰嗦了 ~~~

<br>github demo下載地址<br>

csdn demo下載地址