1. 程式人生 > >【Android】快速開發偷懶必備(二) 支援DataBinding啦~爽炸,一行實現花式列表

【Android】快速開發偷懶必備(二) 支援DataBinding啦~爽炸,一行實現花式列表

概述

在前文快速開發偷懶必備(一)中,我們利用Adapter模式封裝了一個庫,能快速為任意ViewGroup新增子View。
有如下特點:
* 快速簡單使用
* 支援任意ViewGroup
* 無耦合
* 無侵入性
* Item支援多種型別

在庫中V1.1.0版本,我也順手加入了RecyclerView、ListView、GridView的通用Adapter功能,庫地址在這裡。
現在V1.2.0版本釋出,我又加入了我最近超愛的一個技術,DataBinding

封裝了一套一行程式碼實現花式列表的Adapter

即利用DataBinding實現RecyclerView中快速使用的Adapter。

以後不管寫多種type還是單type的列表,利用DataBinding本庫,都只需要一行程式碼

這裡也算是安利DataBinding吧,真的超好用。還沒使用的朋友們,在看到本文可以如此簡單寫花式列表後,建議去學習一下。
先看用法吧,簡單粗暴到沒朋友。

用法

使用必讀:

BaseBindingAdapter利用DataBinding提供的動態繫結技術,使用BR.data封裝資料、BR.itemP封裝點選事件。所以對layout有以下要求:

  • layout中 資料name起名data
  • layout中 點選事件Presenter起名 itemP

如:

<layout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools">
<data> <variable name="itemP" type="mcxtzhang.commonviewgroupadapter.databinding.rv.single.DBSingleActivity.SingleItemPresenter"
/>
<variable name="data" type="mcxtzhang.commonviewgroupadapter.databinding.rv.single.DBSingleBean"/> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="1dp" android:background="@color/colorAccent" android:onClick="@{v->itemP.onItemClick(data)}" android:orientation="horizontal"> <ImageView android:id="@+id/ivAvatar" android:layout_width="200dp" android:layout_height="200dp" app:netUrl="@{data.avatar}" tools:src="@mipmap/ic_launcher"/> <TextView android:id="@+id/tvName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{data.name}" tools:text="測試多種"/> </LinearLayout> </layout>

1 單Item列表

效果如圖:

順帶演示了BaseBindingAdapter封裝的一些增刪功能。

單Item

用法:

和其他BaseAdapter用法一致:
* 建構函式只需要傳入context,datas,layout

    mAdapter = new BaseBindingAdapter(this, mDatas, R.layout.item_db_single);

好了,列表已經出來了。我不騙你,就這一句話。

如果需要設定點選事件(點選事件設定所有型別都一樣,下不贅述):

    //★ 設定Item點選事件
    mAdapter.setItemPresenter(new SingleItemPresenter());
    /**
     * ★ Item點選事件P
     */
    public class SingleItemPresenter {
        public void onItemClick(DBSingleBean data) {
            data.setName("修改之後立刻見效");
        }
    }

特殊需求:

如果有特殊需求,可傳入兩個泛型,重寫onBindViewHolder搞事情:

        // ★泛型D:是Bean型別,如果有就傳。  泛型B:是對應的xml Layout的Binding類
        mAdapter = new BaseBindingAdapter<DBSingleBean, ItemDbSingleBinding>(this, mDatas, R.layout.item_db_single) {
            @Override
            public void onBindViewHolder(BaseBindingVH<ItemDbSingleBinding> holder, int position) {
                //★super一定不要刪除
                super.onBindViewHolder(holder, position);
                //如果有特殊需求,可傳入兩個泛型,重寫onBindViewHolder搞事情。
                ItemDbSingleBinding binding = holder.getBinding();
                DBSingleBean data = mDatas.get(position);
            }
        };

2 多Item同種資料型別列表

一般是像IM那種列表,雖然Item不同,但是資料結構是同一個。用法,一句話~

效果如圖:

多Item同資料結構

用法:

  • 資料結構(JavaBean)需實現IBaseMulInterface介面,根據情況返回不同的layout。
  • 建構函式只需要傳入context,datas.
    mAdapter = new BaseMulTypeBindingAdapter(this, mDatas);

複雜列表依然一句話。

public class MulTypeSingleBean extends BaseObservable implements IBaseMulInterface {
    private String avatar;
    private String name;
    private boolean receive;
    @Override
    public int getItemLayoutId() {
        if (isReceive()) {
            return R.layout.item_db_mul_1;
        } else {
            return R.layout.item_db_mul_2;
        }
    }
}

特殊需求:

如果有特殊需求,可傳入資料結構的泛型,避免強轉,重寫onBindViewHolder()方法,但是Binding類 不可避免的需要強轉了:

        mAdapter = new BaseMulTypeBindingAdapter<MulTypeSingleBean>(this, mDatas) {
            @Override
            public void onBindViewHolder(BaseBindingVH<ViewDataBinding> holder, int position) {
                super.onBindViewHolder(holder, position);
                //如果有特殊需求,可傳入資料結構的泛型,避免強轉
                MulTypeSingleBean data = mDatas.get(position);
                //Binding類 不可避免的需要強轉了
                ViewDataBinding binding = holder.getBinding();
                switch (data.getItemLayoutId()) {
                    case R.layout.item_db_mul_1:
                        ItemDbMul1Binding itemDbMul1Binding = (ItemDbMul1Binding) binding;
                        break;
                    case R.layout.item_db_mul_2:
                        ItemDbMul2Binding itemDbMul2Binding = (ItemDbMul2Binding) binding;
                        break;
                }

            }
        };

3 多Item、多種資料型別列表

各大APP首頁,Banner、列表、推薦混排,資料結構肯定不同,但是依然只要一句程式碼搞定Adapter!

效果如圖:

多Item、多資料結構

用法:

  • 資料結構(JavaBean)需分別實現IBaseMulInterface介面,返回資料結構對應的layout。
  • 建構函式只需要傳入context,datas.
    mAdapter = new BaseMulTypeBindingAdapter(this, mDatas);
public class MulTypeMulBean1 extends BaseObservable implements IBaseMulInterface {
    private String avatar;
    private String name;

    @Override
    public int getItemLayoutId() {
        return R.layout.item_db_mulbean_1;
    }
}
public class MulTypeMulBean2 extends BaseObservable implements IBaseMulInterface {
    private String background;

    @Override
    public int getItemLayoutId() {
        return R.layout.item_db_mulbean_2;
    }
}

特殊需求:

如果有特殊需求,重寫onBindViewHolder()方法,但是資料結構 和 Binding類 都不可避免的需要強轉了:

        mAdapter = new BaseMulTypeBindingAdapter(this, mDatas) {
            @Override
            public void onBindViewHolder(BaseBindingVH holder, int position) {
                super.onBindViewHolder(holder, position);
                //如果有特殊需求 重寫onBindViewHolder方法
                // 資料結構 和 Binding類 都不可避免的需要強轉了
                ViewDataBinding binding = holder.getBinding();
                switch (getItemViewType(position)) {
                    case R.layout.item_db_mul_1:
                        ItemDbMul1Binding itemDbMul1Binding = (ItemDbMul1Binding) binding;
                        MulTypeMulBean1 data1 = (MulTypeMulBean1) mDatas.get(position);
                        break;
                    case R.layout.item_db_mul_2:
                        ItemDbMul2Binding itemDbMul2Binding = (ItemDbMul2Binding) binding;
                        MulTypeMulBean2 data2 = (MulTypeMulBean2) mDatas.get(position);
                        break;
                }
            }
        };

4 不能忘了上文的ViewGroup呀

對上文封裝的ViewGroup型別Adapter也提供DataBinding的支援。

效果如圖:

用法:

和上文快速開發偷懶必備(一)一樣,只是Adapter換成SingleBindingAdapter

    mAdapter = new SingleBindingAdapter<>(this, mDatas = iniDatas(), R.layout.item_db_flow_swipe);

如果需要設定點選事件:

    mAdapter.setItemPresenter(new ItemDelPresenter());

設計思路與實現

使用起來如此爽快,其實寫起來也很簡單。

注意類BaseBindingAdapterBaseMulTypeBindingAdapter都不是abstract的,這說明我們不需要重寫任何方法

利用DataBinding,我們在BasexxxAdapter內部和xml分別做View的建立和資料繫結的工作。

UML類圖

UML類圖

先簡要概括

  • BaseBindingVH繼承自RecyclerView.ViewHolder,持有T extends ViewDataBinding型別的mBinding變數。利用ViewDataBinding我們將不用再寫任何ViewHolder
  • BaseBindingAdapter,繼承自RecyclerView.Adapter,依賴BaseBindingVHonCreateViewHolder(ViewGroup parent, int viewType)方法返回BaseBindingVH作為ViewHolder
    內部持有三個重要變數:資料對應layout,資料集,Item點選事件處理類。資料對應layout會在onCreateViewHolder(ViewGroup parent, int viewType)用到。剩下兩個變數在onBindViewHolder()用到。對外暴漏setItemPresenter(Object itemPresenter)供設定點選事件處理類。
  • IBaseMulInterface介面和快速開發偷懶必備(一)提到的一樣,返回某個資料結構對應的layout,除此之外,本文還有一個十分tricky之處,利用返回的R.layout.itemxxxx作為ItemViewType,在BaseMulTypeBindingAdapter會用到。
  • BaseMulTypeBindingAdapter繼承自BaseBindingAdapter,但是它不再關心mLayoutId變數,它利用IBaseMulInterface介面返回的R.layout.itemxxxx作為ItemViewType,這樣在onCreateViewHolder(ViewGroup parent, int viewType)的時候,就可以直接用viewType構造出ItemView。不再依賴mLayoutId變數。這是一個我很得意的設計,我在優雅為RecyclerView增加HeaderView一文中,也曾用過這個方法。

BaseBindingVH

BaseBindingVH算是一個核心類,但是又十分簡單。它繼承自RecyclerView.ViewHolder,持有由泛型傳入的T extends ViewDataBinding型別的mBinding變數。
唯一建構函式,需要一個T t變數,然後呼叫super()傳入t.getRoot()完成itemView的賦值。同時對mBinding變數賦值。
對外暴漏getBinding()返回mBinding變數。

利用ViewDataBinding我們將不用再寫任何ViewHolder

public class BaseBindingVH<T extends ViewDataBinding> extends RecyclerView.ViewHolder {
    protected final T mBinding;

    public BaseBindingVH(T t) {
        super(t.getRoot());
        mBinding = t;
    }

    public T getBinding() {
        return mBinding;
    }
}

BaseBindingAdapter

BaseBindingAdapter,繼承自RecyclerView.Adapter,依賴BaseBindingVH,將BaseBindingVH作為泛型傳給RecyclerView.Adapter
同時BaseBindingAdapter本身接受兩個泛型,<D, B extends ViewDataBinding>

  • 泛型沒有特殊需求可以不傳
  • 泛型D:是Bean型別,如果有就傳。
  • 泛型B:是對應的xml Layout的Binding類

傳入不傳入泛型的區別已經在第二節具體用法裡進行了演示,不再贅述。
內部持有三個重要變數:

  • 資料對應layout int mLayoutId;
  • 資料集 List<D> mDatas;
  • Item點選事件處理類。Object ItemPresenter;

mLayoutIdmDatas都由建構函式傳入,沒啥好說的。

對外暴漏setItemPresenter(Object itemPresenter)供設定點選事件處理類ItemPresenter
ItemPresenterObject型別,這樣才不care你set的Item點選事件處理類是什麼鬼。

onCreateViewHolder(ViewGroup parent, int viewType)方法返回BaseBindingVH作為ViewHolder
mLayoutId會在onCreateViewHolder(ViewGroup parent, int viewType)用到,再根據泛型B強轉成對應的ViewDataBinding

BaseBindingVH<B> holder = new BaseBindingVH<B>((B) DataBindingUtil.inflate(mInfalter, mLayoutId, parent, false));

會在onBindViewHolder()方法裡,利用DataBinding動態繫結ViewDataBinding.setVariable(BR.itemP, ItemPresenter);為每個Item設定點選事件。
同時,資料也是同樣在裡面繫結的:setVariable(BR.data, mDatas.get(position))

重點程式碼如下:


public class BaseBindingAdapter<D, B extends ViewDataBinding> extends RecyclerView.Adapter<BaseBindingVH<B>> {
    protected Context mContext;
    protected int mLayoutId;
    protected List<D> mDatas;
    protected LayoutInflater mInfalter;
    //用於設定Item的事件Presenter
    protected Object ItemPresenter;

    public BaseBindingAdapter(Context mContext, List mDatas, int mLayoutId) {
        this.mContext = mContext;
        this.mLayoutId = mLayoutId;
        this.mDatas = mDatas;
        this.mInfalter = LayoutInflater.from(mContext);
    }

    @Override
    public BaseBindingVH<B> onCreateViewHolder(ViewGroup parent, int viewType) {
        BaseBindingVH<B> holder = new BaseBindingVH<B>((B) DataBindingUtil.inflate(mInfalter, mLayoutId, parent, false));
        onCreateViewHolder(holder);
        return holder;
    }

    /**
     * 如果需要給Vh設定監聽器啥的 可以在這裡
     *
     * @param holder
     */
    public void onCreateViewHolder(BaseBindingVH<B> holder) {

    }

    /**
     * 子類除了繫結資料,還要設定監聽器等其他操作。
     * 可以重寫這個方法,不要刪掉super.onBindViewHolder(holder, position);
     *
     * @param holder
     * @param position
     */
    @Override
    public void onBindViewHolder(BaseBindingVH<B> holder, int position) {
        holder.getBinding().setVariable(BR.data, mDatas.get(position));
        holder.getBinding().setVariable(BR.itemP, ItemPresenter);
        holder.getBinding().executePendingBindings();
    }
    /**
     * 用於設定Item的事件Presenter
     *
     * @param itemPresenter
     * @return
     */
    public BaseBindingAdapter setItemPresenter(Object itemPresenter) {
        ItemPresenter = itemPresenter;
        return this;
    }
}

BaseBindingAdapter內部也封裝瞭如下方法,方便資料重新整理,增刪(定向重新整理)呼叫:

    /**
     * 重新整理資料,初始化資料
     *
     * @param list
     */
    public void setDatas(List<D> list) {
        if (this.mDatas != null) {
            if (null != list) {
                List<D> temp = new ArrayList<D>();
                temp.addAll(list);
                this.mDatas.clear();
                this.mDatas.addAll(temp);
            } else {
                this.mDatas.clear();
            }
        } else {
            this.mDatas = list;
        }
        notifyDataSetChanged();
    }

    /**
     * 刪除一條資料
     * 會自動定向重新整理
     *
     * @param i
     */
    public void remove(int i) {
        if (null != mDatas && mDatas.size() > i && i > -1) {
            mDatas.remove(i);
            notifyItemRemoved(i);
        }
    }

    /**
     * 新增一條資料 至隊尾
     * 會自動定向重新整理
     *
     * @param data
     */
    public void add(D data) {
        if (data != null && mDatas != null) {
            mDatas.add(data);
            notifyItemInserted(mDatas.size());
        }
    }

    /**
     * 在指定位置新增一條資料
     * 會自動定向重新整理
     * <p>
     * 如果指定位置越界,則新增在隊尾
     *
     * @param position
     * @param data
     */
    public void add(int position, D data) {
        if (data != null && mDatas != null) {
            if (mDatas.size() > position && position > -1) {
                mDatas.add(position, data);
                notifyItemInserted(position);
            } else {
                add(data);
            }
        }
    }


    /**
     * 載入更多資料
     *
     * @param list
     */
    public void addDatas(List<D> list) {
        if (null != list) {
            List<D> temp = new ArrayList<D>();
            temp.addAll(list);
            if (this.mDatas != null) {
                this.mDatas.addAll(temp);
            } else {
                this.mDatas = temp;
            }
            notifyDataSetChanged();
        }

    }

IBaseMulInterface介面

來點簡單的.

IBaseMulInterface介面和快速開發偷懶必備(一)提到的一樣,返回某個資料結構對應的layout.

除此之外,本文還有一個十分tricky之處,利用返回的R.layout.itemxxxx作為ItemViewType,在BaseMulTypeBindingAdapter會用到。
因為不同的R.layout.itemxxxx對於RecyclerView來說一定是不同的Item,

BaseMulTypeBindingAdapter

多種ItemType的Base類

BaseMulTypeBindingAdapter繼承自BaseBindingAdapter,但是它不再關心mLayoutId變數。因此它傳給父類的泛型B就是ViewDataBinding類本身。解釋如下:
* 基類的泛型B:不用傳,因為多種ItemType 肯定Layout長得不一樣,那麼Binding類也不一樣,傳入沒有任何意義

  • 泛型T:多Item多Bean情況可以不傳。如果只有一種Bean型別,可以傳入Bean,實現IBaseMulInterface介面。
    或者傳入IBaseMulInterface介面,可以拿到 getItemLayoutId(),
    但是通過getItemViewType(int position),一樣。所以多Item多Bean建議不傳。
    傳入不傳入泛型的區別已經在第二節具體用法裡進行了演示,不再贅述。

getItemViewType()直接返回 IBaseMulInterface介面的返回值。

onCreateViewHolder(ViewGroup parent, int viewType)的時候,直接用viewType構建ViewDataBindingItemView)。不再依賴mLayoutId變數。
這是一個我很得意的設計,我在優雅為RecyclerView增加HeaderView一文中,也曾用過這個方法新增頭部。

完整程式碼如下:

public class BaseMulTypeBindingAdapter<T extends IBaseMulInterface> extends BaseBindingAdapter<T, ViewDataBinding> {

    public BaseMulTypeBindingAdapter(Context mContext, List<T> mDatas) {
        super(mContext, mDatas);
    }

    @Override
    public int getItemViewType(int position) {
        return mDatas.get(position).getItemLayoutId();
    }

    @Override
    public BaseBindingVH<ViewDataBinding> onCreateViewHolder(ViewGroup parent, int viewType) {
        BaseBindingVH<ViewDataBinding> holder = new BaseBindingVH<ViewDataBinding>(DataBindingUtil.inflate(mInfalter, viewType, parent, false));
        onCreateViewHolder(holder);
        return holder;
    }
}

ViewGroup Adapter的實現

單item

繼承SingleAdapter,增加ItemPresenter,在getView()完成View建立和繫結。

public class SingleBindingAdapter<D, B extends ViewDataBinding> extends SingleAdapter<D> {
    //用於設定Item的事件Presenter
    protected Object ItemPresenter;
    /**
     * 用於設定Item的事件Presenter
     *
     * @param itemPresenter
     * @return
     */
    public SingleBindingAdapter setItemPresenter(Object itemPresenter) {
        ItemPresenter = itemPresenter;
        return this;
    }

    public SingleBindingAdapter(Context context, List<D> datas, int itemLayoutId) {
        super(context, datas, itemLayoutId);
    }

    //重寫利用DataBinding做
    @Override
    public View getView(ViewGroup parent, int pos, D data) {
        ViewDataBinding binding = DataBindingUtil.inflate(mInflater, mItemLayoutId, parent, false);
        View itemView = binding.getRoot();
        onBindView(parent, itemView, data, pos);
        binding.setVariable(BR.data, data);
        binding.setVariable(BR.itemP, ItemPresenter);
        return itemView;
    }

    //空實現即可,因為DataBinding的實現都是在xml裡做
    @Override
    public void onBindView(ViewGroup parent, View itemView, D data, int pos) {

    }
}

多Item:

更簡單了,繼承SingleBindingAdapter。重寫getView()即可。

public class MulTypeBindngAdapter<T extends IMulTypeHelper> extends SingleBindingAdapter<T, ViewDataBinding> {

    public MulTypeBindngAdapter(Context context, List<T> datas) {
        super(context, datas, -1);
    }

    //重寫利用DataBinding做
    @Override
    public View getView(ViewGroup parent, int pos, T data) {
        ViewDataBinding binding = DataBindingUtil.inflate(mInflater, data.getItemLayoutId(), parent, false);
        View itemView = binding.getRoot();
        onBindView(parent, itemView, data, pos);
        binding.setVariable(BR.data, data);
        binding.setVariable(BR.itemP, ItemPresenter);
        return itemView;
    }
}

總結

本文利用DataBindingViewDataBinding直接略去寫ViewHolder

利用Object型別的ItemPresenter,相容解決了點選事件的設定。

最得意的設計,還是利用R.layout.xxxx這些佈局檔案int型別的的RID,作為ItemViewType,一箭雙鵰。

DataBinding很強,希望大家快點擁抱它。

to do list

  • ViewGroup Adapter 考慮加入複用快取池
  • ViewGroup Adapter ,考慮替換onBindView()ItemView->通用的ViewHolder,這樣可以少寫一些findViewById()程式碼
  • 整合DataBinding 的通用Adapter入庫。
  • 完善 RecyclerView、ListView的通用Adapter,支援多種ItemViewType。
  • 加入一些自定義ViewGroup入庫,例如流式佈局,九宮格,Banner輪播圖。