超強(qiáng)賽亞版RecyclerView(多種布局)的使用封裝

想想還是有必要上一波官話的:

  • RecyclerViewsupport:recyclerview-v7中提供的控件,最低兼容到android 3.0版本痢虹。
  • RecyclerViewListView的更高級和靈活的版本武氓。
  • 提供了LinearLayoutManager(類listview)梯皿、GridLayoutManager(類gridview)、StaggeredGridLayoutManager(瀑布流)三種排列方式县恕。

簡單來說东羹,RecyclerView是控件里的C位也不為過了。

一忠烛、Why And What?

RecyclerView是我平時開發(fā)工作中可以說是最常用的組件了属提,所以做適當(dāng)并且適合的封裝是必須的。
目前關(guān)于RecyclerView的封裝可以說不要太多哦美尸,為什么我還要選擇自己封裝呢冤议?
其實在做這個封裝之前我也瀏覽過很多大神對于RecyclerView的封裝,但是普遍存在兩個基本的問題:

  • 只針對某一方面做封裝
  • 封裝內(nèi)容過多且復(fù)雜师坎,很多都是我用不上的
我自己對于RecyclerView的需求有哪些
1. 萬用型的ViewHolder恕酸,不用再每個界面寫ViewHolder
2. 繼承自RecyclerView.Adapter的基類Adapter胯陋,負(fù)責(zé)管理RecyclerView的數(shù)據(jù)蕊温。
3. 下拉刷新和上拉加載(這個麻煩的地方在于要有一定的特效,雖然很多封裝都實現(xiàn)了惶岭,但是大多簡陋寿弱,優(yōu)美的少,很影響用戶體驗)按灶。
4. 多種布局的實現(xiàn)症革,即列表中存在不同的布局。
5. 簡潔地實現(xiàn)item中數(shù)據(jù)和view的綁定鸯旁。
6. 將一個RecyclerView置于一個BaseFragment中噪矛,通過BaseFragment來實現(xiàn)一個最簡單RecyclerView的頁面。

二铺罢、ViewHolder和RecyclerView.Adapter

public class ViewHolderRocket extends RecyclerView.ViewHolder {
    private SparseArray<View> views;
    private View mItemView;

    public ViewHolderRocket(View itemView) {
        super(itemView);
        views = new SparseArray<>();
        mItemView = itemView;
    }

    public View getmItemView() {
        return mItemView;
    }

    public View getView(int resId) {
        return retrieveView(resId);
    }

    protected <V extends View> V retrieveView(int viewId){
        View view = views.get(viewId);
        if(view == null){
            view = mItemView.findViewById(viewId);
            views.put(viewId,view);
        }
        return (V) view;
    }

    public TextView getTextView(int resId){
        return retrieveView(resId);
    }

    public ImageView getImageView(int resId){
        return retrieveView(resId);
    }

    public Button getButton(int resId){
        return retrieveView(resId);
    }

    public LinearLayout getLinearLayout(int resId){return retrieveView(resId);}

    public void setText(int resId,CharSequence text){
        getTextView(resId).setText(text);
    }

    public void setText(int resId,int strId){
        getTextView(resId).setText(strId);
    }
}

ViewHolderRocket繼承自RecyclerView.ViewHolder艇挨,將itemview存儲在SparseArray數(shù)組中,避免了重復(fù)的findViewById韭赘,提高了效率和性能缩滨。

關(guān)于BaseAdapter,最主要的作用就是對于數(shù)據(jù)的操作,包括初始設(shè)置脉漏、新增苞冯、刪除和清除所有。
一下代碼展示了除了這些之外的另一個重要作用侧巨,那就是用于不同布局的實現(xiàn):

public class RVBaseAdapter<C extends RVCell> extends RecyclerView.Adapter<ViewHolderRocket> {
    private static final String TAG = "RVBaseAdapter";
    protected List<C> mData;

    public RVBaseAdapter() {
        mData = new ArrayList<>();
    }

    public List<C> getData() {
        return mData;
    }

    @Override
    public int getItemViewType(int position) {
        return mData.get(position).getItemType();
    }

    @Override
    public ViewHolderRocket onCreateViewHolder(ViewGroup parent, int viewType) {
        return new ViewHolderRocket(LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false));
    }

    @Override
    public void onBindViewHolder(ViewHolderRocket holder, int position) {
        mData.get(position).bindViewHolder(holder, position);
    }

    @Override
    public void onViewDetachedFromWindow(ViewHolderRocket holder) {
        super.onViewDetachedFromWindow(holder);
        Log.e(TAG, "onViewDetachedFromWindow invoke...");
        //釋放資源
        int position = holder.getAdapterPosition();
        //越界檢查
        if (position < 0 || position >= mData.size()) {
            return;
        }
        mData.get(position).releaseResource();
    }

    @Override
    public int getItemCount() {
        return mData == null ? 0 : mData.size();
    }

    /**
     * add one cell
     *
     * @param cell
     */
    public void add(C cell) {
        mData.add(cell);
        int index = mData.indexOf(cell);
        notifyItemInserted(index);
    }

    public void add(int index, C cell) {
        mData.add(index, cell);
        notifyItemInserted(index);
    }

    /**
     * remove a cell
     *
     * @param cell
     */
    public void remove(C cell) {
        int indexOfCell = mData.indexOf(cell);
        remove(indexOfCell);
    }

    public void remove(int index) {
        mData.remove(index);
        notifyItemRemoved(index);
    }

    /**
     * @param start
     * @param count
     */
    public void remove(int start, int count) {
        if ((start + count) > mData.size()) {
            return;
        }
        mData.subList(start, start + count).clear();
        notifyItemRangeRemoved(start, count);
    }


    /**
     * add a cell list
     *
     * @param cells
     */
    public void addAll(List<C> cells) {
        if (cells == null || cells.size() == 0) {
            return;
        }
        Log.e(TAG, "addAll cell size:" + cells.size());
        mData.addAll(cells);
        notifyItemRangeInserted(mData.size() - cells.size(), mData.size());
    }

    public void addAll(int index, List<C> cells) {
        if (cells == null || cells.size() == 0) {
            return;
        }
        mData.addAll(index, cells);
        notifyItemRangeInserted(index, index + cells.size());
    }

    public void clear() {
        mData.clear();
        notifyDataSetChanged();
    }
}

關(guān)于不同布局的實現(xiàn)舅锄,我們留到后面再講。

三司忱、下拉刷新和上拉加載

想要實現(xiàn)下拉刷新和上拉加載皇忿,說簡單也簡單,Google官方自己提供了SwipeRefreshLayout控件來幫助我們實現(xiàn)下拉刷新坦仍,上拉加載也可以通過監(jiān)控列表中最后一個數(shù)據(jù)的位置來判斷是否要加載鳍烁。
但是效果也就是一般般,對于已經(jīng)習(xí)慣了微博桨踪、微信老翘、支付寶那些強(qiáng)大的刷新加載功能的用戶來說芹啥,他們當(dāng)然不會滿足于這種最原始的方式锻离。
想要做的酷炫,就要做出炫酷的動畫特效墓怀,自己做的話實在是力不從心啊汽纠,沒有扎實的view基礎(chǔ),真的望洋興嘆的感覺傀履。
還有有大神幫我們解決了這個問題:

強(qiáng)大且易用的SmartRefreshLayout

RvFragment中有可以設(shè)置Header和Footer的方法虱朵,以及設(shè)置是否支持下拉刷新和上拉加載的方法:

 /**
     * 是否啟用下拉刷新功能
     *
     * @param enable
     */
    protected void setEnableRefresh(boolean enable) {
        refreshLayout.setEnableRefresh(enable);
    }

    /**
     * 是否啟用上拉加載功能
     *
     * @param enable
     */
    protected void setEnableLoadMore(boolean enable) {
        refreshLayout.setEnableLoadMore(enable);
    }

 /**
     * 設(shè)置下拉刷新Header
     *
     * @param header
     */
    protected void setRefreshHeader(RefreshHeader header) {
        refreshLayout.setRefreshHeader(header);
    }

    /**
     * 設(shè)置上拉加載Footer
     * @param footer
     */
    protected void setRefreshFooter(RefreshFooter footer) {
        refreshLayout.setRefreshFooter(footer);
    }

感興趣的同學(xué)可以看看,有很強(qiáng)大的HeaderFooter樣式钓账。

四碴犬、多種布局的實現(xiàn)

這本來是產(chǎn)品經(jīng)理提出的需求

  • 用在數(shù)據(jù)頁面,能夠在列表里面根據(jù)某些特殊的數(shù)據(jù)來展示不同的頁面
  • 用在首頁梆暮,一個RecyclerView就展示全復(fù)雜的主頁
public interface Cell {
    /**
     * 回收資源
     */
    void releaseResource();

    /**
     * 獲取viewType
     *
     * @return
     */
    int getItemType();

    /**
     * 綁定ViewHolder
     *
     * @param holder
     * @param position
     * @return
     */
    void bindViewHolder(RVBaseViewHolder holder, int position);

}

public abstract class RVCell<T> implements Cell {
    public T mData;

    public RVCell(T t) {
        mData = t;
    }
}

實現(xiàn):

public class TestCell extends RVCell<PersonBean> {

    public TestCell(PersonBean bean) {
        super(bean);
    }

    @Override
    public void releaseResource() {

    }

    @Override
    public int getItemType() {
        return R.layout.rv_item;
    }

    @Override
    public void bindViewHolder(final ViewHolderRocket holder, final int position) {
        holder.setText(R.id.store_name_tv, mData.getName());
        holder.setText(R.id.store_address_tv, mData.getAge());
        holder.setText(R.id.store_owner_tv, mData.getPhone());
        final View mItemView = holder.getmItemView();
        mItemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText( mItemView.getContext(), position + "", Toast.LENGTH_SHORT).show();
                Intent intent = new Intent(mItemView.getContext(), MainActivity.class);
                mItemView.getContext().startActivity(intent);
            }
        });
    }
}

這時候我們再回頭看上面的RVBaseAdapter

RVBaseAdapter<C extends RVBaseCell> extends RecyclerView.Adapter<RVBaseViewHolder>{
    protected List<C> mData;
    public RVBaseAdapter() {
        mData = new ArrayList<>();
    }
}

RVBaseCell持有數(shù)據(jù)mData和視圖holder服协,即一個Item
接下來我們看RVAdapter的三個重要方法:

  @Override
    public int getItemViewType(int position) {
        return mData.get(position).getItemType();
    }

    @Override
    public ViewHolderRocket onCreateViewHolder(ViewGroup parent, int viewType) {
        return new ViewHolderRocket(LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false));
    }

    @Override
    public void onBindViewHolder(ViewHolderRocket holder, int position) {
        mData.get(position).bindViewHolder(holder, position);
    }
  1. CellgetItemType()方法返回的是item的布局layout啦粹。
  2. RVAdaptergetItemViewType方法偿荷,return “布局的id”。
  3. onCreateViewHolder(ViewGroup parent, int viewType)中的viewType即是getItemViewType方法返回的布局id唠椭,根據(jù)這個id獲取視圖跳纳,并返回ViewHolderRocket
  4. onBindViewHolder中調(diào)用mDatabindViewHolder方法贪嫂,然后在TestCell(實際的實現(xiàn)方式)實現(xiàn)該方法寺庄。
  5. mDatabindViewHolder方法中實現(xiàn)item中數(shù)據(jù)和view的綁定。

五、基于RecyclerViewRvBaseFragment

public abstract class RvFragment<T> extends Fragment {
    public static final String TAG = "RvFragment";
    private FrameLayout titleLayout;
    protected RecyclerView mRecyclerView;
    protected RVAdapter mBaseAdapter;
    protected SmartRefreshLayout refreshLayout;
    protected int pageNumber = 1;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.rv_base_fragment_layout, null);
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        initView(view);
        initRefreshListener();
        initRecycleView();
        onRecyclerViewInitialized();
    }

    private void initView(View view) {
        titleLayout = view.findViewById(R.id.title_layout);
        refreshLayout = view.findViewById(R.id.refreshLayout);
        mRecyclerView = view.findViewById(R.id.base_fragment_rv);
        mRecyclerView.setLayoutManager(initLayoutManger());
    }

    private void initRefreshListener() {
        refreshLayout.setOnRefreshListener(new OnRefreshListener() {
            @Override
            public void onRefresh(@NonNull RefreshLayout refreshlayout) { //下拉刷新
                refreshlayout.finishRefresh(500);
                pageNumber = 1;
                RvFragment.this.onRefresh();
            }
        });
        refreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore(@NonNull RefreshLayout refreshlayout) { //上拉加載
                refreshlayout.finishLoadMore(500);
                pageNumber++;
                RvFragment.this.onLoadMore();
            }
        });
        refreshLayout.setRefreshHeader(new ClassicsHeader(this.getActivity()).setPrimaryColor(colorUtil.getColorPrimary(this.getActivity())));
        refreshLayout.setRefreshFooter(new ClassicsFooter(this.getActivity()).setPrimaryColor(getResources().getColor(R.color.material_gray_300)));
    }

    /**
     * 設(shè)置下拉刷新Header
     *
     * @param header
     */
    protected void setRefreshHeader(RefreshHeader header) {
        refreshLayout.setRefreshHeader(header);
    }

    /**
     * 設(shè)置上拉加載Footer
     * @param footer
     */
    protected void setRefreshFooter(RefreshFooter footer) {
        refreshLayout.setRefreshFooter(footer);
    }

    private void initRecycleView() {
        mBaseAdapter = initAdapter();
        mRecyclerView.setAdapter(mBaseAdapter);
    }

    /**
     * 設(shè)置頭布局斗塘,不在列表中
     *
     * @param view
     */
    protected void addTitleView(View view) {
        if (view == null) {
            return;
        }
        titleLayout.addView(view);
    }

    /**
     * 子類可以自己指定Adapter,如果不指定默認(rèn)RVSimpleAdapter
     *
     * @return
     */
    protected RVAdapter initAdapter() {
        return new RVAdapter();
    }

    /**
     * 子類自己指定RecyclerView的LayoutManager,如果不指定饶深,默認(rèn)為LinearLayoutManager,VERTICAL 方向
     *
     * @return
     */
    protected RecyclerView.LayoutManager initLayoutManger() {
        LinearLayoutManager manager = new LinearLayoutManager(getContext());
        manager.setOrientation(LinearLayoutManager.VERTICAL);
        return manager;
    }

    /**
     * 是否啟用下拉刷新功能
     *
     * @param enable
     */
    protected void setEnableRefresh(boolean enable) {
        refreshLayout.setEnableRefresh(enable);
    }

    /**
     * 是否啟用上拉加載功能
     *
     * @param enable
     */
    protected void setEnableLoadMore(boolean enable) {
        refreshLayout.setEnableLoadMore(enable);
    }

    /**
     * RecyclerView 初始化完畢,可以在這個方法里綁定數(shù)據(jù)
     */
    public abstract void onRecyclerViewInitialized();

    /**
     * 下拉刷新
     */
    public abstract void onRefresh();

    /**
     * 上拉加載更多
     */
    public abstract void onLoadMore();

    /**
     * 根據(jù)實體生成對應(yīng)的Cell
     *
     * @param list 實體列表
     * @return cell列表
     */
    protected abstract List<Cell> getCells(List<T> list);

}

實際使用:

public class RecyclerViewFragment extends RvFragment {

    public static RecyclerViewFragment newInstance() {
        RecyclerViewFragment fragment = new RecyclerViewFragment();
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public void onRecyclerViewInitialized() {
        mBaseAdapter.showLoading();
        ArrayList<PersonBean> dataList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            PersonBean bean = new PersonBean();
            bean.setName("姓名" + i + "");
            bean.setAge("20");
            bean.setPhone("18956321458");
            dataList.add(bean);
        }
        mBaseAdapter.removeLoading();
        mBaseAdapter.addAll(getCells(dataList));
    }

    @Override
    public void onRefresh() {
        mBaseAdapter.clear();
        ArrayList<PersonBean> dataList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            PersonBean bean = new PersonBean();
            bean.setName("姓名" + i + "");
            bean.setAge("20");
            bean.setPhone("18956321458");
            dataList.add(bean);
        }
        mBaseAdapter.addAll(getCells(dataList));
        Toast.makeText(this.getActivity(), "更新啦", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onLoadMore() {
        ArrayList<PersonBean> dataList = new ArrayList<>();
        for (int i = 10 * (pageNumber - 1); i < pageNumber * 10; i++) {
            PersonBean bean = new PersonBean();
            bean.setName("姓名" + i + "");
            bean.setAge("20");
            bean.setPhone("18956321458");
            dataList.add(bean);
        }
        mBaseAdapter.addAll(getCells(dataList));
    }

    @Override
    protected List<Cell> getCells(List list) {
        List<Cell> cells = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            PersonBean bean = (PersonBean) list.get(i);
            cells.add(new TestCell(bean));
        }
        return cells;
    }
}

源碼GitHub地址:https://github.com/agula5610/RecyclerViewUtils

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末逛拱,一起剝皮案震驚了整個濱河市敌厘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌朽合,老刑警劉巖俱两,帶你破解...
    沈念sama閱讀 211,561評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異曹步,居然都是意外死亡宪彩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評論 3 385
  • 文/潘曉璐 我一進(jìn)店門讲婚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來尿孔,“玉大人,你說我怎么就攤上這事筹麸』詈希” “怎么了?”我有些...
    開封第一講書人閱讀 157,162評論 0 348
  • 文/不壞的土叔 我叫張陵物赶,是天一觀的道長白指。 經(jīng)常有香客問我,道長酵紫,這世上最難降的妖魔是什么告嘲? 我笑而不...
    開封第一講書人閱讀 56,470評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮奖地,結(jié)果婚禮上橄唬,老公的妹妹穿的比我還像新娘。我一直安慰自己参歹,他們只是感情好仰楚,可當(dāng)我...
    茶點故事閱讀 65,550評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著泽示,像睡著了一般缸血。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上械筛,一...
    開封第一講書人閱讀 49,806評論 1 290
  • 那天捎泻,我揣著相機(jī)與錄音,去河邊找鬼埋哟。 笑死笆豁,一個胖子當(dāng)著我的面吹牛郎汪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播闯狱,決...
    沈念sama閱讀 38,951評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼煞赢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了哄孤?” 一聲冷哼從身側(cè)響起照筑,我...
    開封第一講書人閱讀 37,712評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瘦陈,沒想到半個月后凝危,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,166評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡晨逝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,510評論 2 327
  • 正文 我和宋清朗相戀三年蛾默,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捉貌。...
    茶點故事閱讀 38,643評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡支鸡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出趁窃,到底是詐尸還是另有隱情牧挣,我是刑警寧澤,帶...
    沈念sama閱讀 34,306評論 4 330
  • 正文 年R本政府宣布棚菊,位于F島的核電站浸踩,受9級特大地震影響叔汁,放射性物質(zhì)發(fā)生泄漏统求。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,930評論 3 313
  • 文/蒙蒙 一据块、第九天 我趴在偏房一處隱蔽的房頂上張望码邻。 院中可真熱鬧,春花似錦另假、人聲如沸像屋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽己莺。三九已至,卻和暖如春戈轿,著一層夾襖步出監(jiān)牢的瞬間凌受,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評論 1 266
  • 我被黑心中介騙來泰國打工思杯, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留胜蛉,地道東北人。 一個月前我還...
    沈念sama閱讀 46,351評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像誊册,于是被迫代替她去往敵國和親领突。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,509評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 大家還記得Spinner的效果嗎案怯。ContextMenu的效果很像我們Spinner的dialog使用效果君旦,彈出一...
    季白zy閱讀 466評論 0 0
  • 凌晨12點半于宙,夜已深,在陌生城市的旅館里悍汛,越躺越清醒捞魁。 失眠的時候總是會有莫名的恐慌感!害怕夜太長离咐,害怕太陽久久難...
    沁垣閱讀 301評論 0 0
  • 最近谱俭,一些創(chuàng)意公司和科技公司,如Google宵蛀、Facebook在創(chuàng)新過程中開始廣泛使用一種“問正確問...
    西西教練閱讀 778評論 0 1