1. 程式人生 > >RecyclerView新增頭部和底部

RecyclerView新增頭部和底部

定義:

裝飾設計模式也稱包裝設計模式,用來動態的擴充套件物件的功能,也是繼承關係的的一種替代方案之一。
說個大白話就是,在不使用的繼承的方式下,採用裝飾設計模式可以擴充套件一個物件的功能,可以使一個物件變得越來越強大。

我們首先看下效果圖

效果圖.gif

我們都知道listview是可以新增頭部和尾部的,我們大概看下,具體的可以自己去看下,原始碼並不難。
public void setAdapter(ListAdapter adapter) {
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }

        resetList();
        mRecycler.clear();

        if
(mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) { //實際上呢,listview是將adapter添加了一個包裹類 mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, adapter); } else { mAdapter = adapter; } //程式碼省略 }

我們可以模仿listview進行adapter的封裝

首先WrapRecyclerAdapter類是繼承於RecylerView的adapter,引數如下

   private final static String TAG = "WrapRecyclerAdapter";
    // 用來存放底部和頭部View的集合  比Map要高效一些
    private SparseArray<View> mHeaderViews;
    private SparseArray<View> mFooterViews;

    // 基本的頭部型別開始位置  用於viewType
    private static int
BASE_ITEM_TYPE_HEADER = 10000000; // 基本的底部型別開始位置 用於viewType private static int BASE_ITEM_TYPE_FOOTER = 20000000; // 列表的Adapter private RecyclerView.Adapter mAdapter; //通過建構函式的方法 public WrapRecyclerAdapter(RecyclerView.Adapter adapter) { this.mAdapter = adapter; mHeaderViews = new SparseArray<>(); mFooterViews = new SparseArray<>(); }

我們首先定義四個方法,新增頭部,尾部,刪除頭部,尾部

  /**
     * 新增頭部
     */
    public void addHeaderView(View view) {
        int position = mHeaderViews.indexOfValue(view);
        if (position < 0) {
            mHeaderViews.put(BASE_ITEM_TYPE_HEADER++, view);
        }
        notifyDataSetChanged();
    }

    /**
     * 新增底部
     */
    public void addFooterView(View view) {
        int position = mFooterViews.indexOfValue(view);
        if (position < 0) {
            mFooterViews.put(BASE_ITEM_TYPE_FOOTER++, view);
        }
        notifyDataSetChanged();
    }

    /**
     * 移除頭部
     */
    public void removeHeaderView(View view) {
        int index = mHeaderViews.indexOfValue(view);
        if (index < 0) return;
        mHeaderViews.removeAt(index);
        notifyDataSetChanged();
    }

    /**
     * 移除底部
     */
    public void removeFooterView(View view) {
        int index = mFooterViews.indexOfValue(view);
        if (index < 0) return;
        mFooterViews.removeAt(index);
        notifyDataSetChanged();
    }

然後我們看下我們複寫的第一個方法 getItemCount()很簡單: 底部條數 + 頭部條數 + Adapter的條數

    @Override
    public int getItemCount() {
        // 條數三者相加 = 底部條數 + 頭部條數 + Adapter的條數
        return mAdapter.getItemCount() + mHeaderViews.size() + mFooterViews.size();
    }

然後看下我們複寫的第二個方法onCreateViewHolder()方法

 @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        // viewType 可能就是 SparseArray 的key
        if (isHeaderViewType(viewType)) {
            View headerView = mHeaderViews.get(viewType);
            return createHeaderFooterViewHolder(headerView);
        }

        if (isFooterViewType(viewType)) {
            View footerView = mFooterViews.get(viewType);
            return createHeaderFooterViewHolder(footerView);
        }
        return mAdapter.onCreateViewHolder(parent, viewType);
    }

判斷是不是頭部或者底部型別並建立ViewHolder

/**
     * 是不是底部型別
     */
    private boolean isFooterViewType(int viewType) {
        int position = mFooterViews.indexOfKey(viewType);
        return position >= 0;
    }

    /**
     * 建立頭部或者底部的ViewHolder
     */
    private RecyclerView.ViewHolder createHeaderFooterViewHolder(View view) {
        return new RecyclerView.ViewHolder(view) {

        };
    }

    /**
     * 是不是頭部型別
     */
    private boolean isHeaderViewType(int viewType) {
        int position = mHeaderViews.indexOfKey(viewType);
        return position >= 0;
    }

注意這裡viewType不是position所以我們一定要複寫getItemViewType將返回的值賦值給viewType

 @Override
    public int getItemViewType(int position) {
        if (isHeaderPosition(position)) {
            // 直接返回position位置的key
            return mHeaderViews.keyAt(position);
        }
        if (isFooterPosition(position)) {
            // 直接返回position位置的key
            position = position - mHeaderViews.size() - mAdapter.getItemCount();
            return mFooterViews.keyAt(position);
        }
        // 返回列表Adapter的getItemViewType
        position = position - mHeaderViews.size();
        return mAdapter.getItemViewType(position);
    }

看最後一個複寫的方法onBindViewHolder()

@Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (isHeaderPosition(position) || isFooterPosition(position)) {
            return;
        }
        // 計算一下位置
        final int adapterPosition = position - mHeaderViews.size();
        mAdapter.onBindViewHolder(holder, adapterPosition);

        // 設定點選和長按事件
        if (mItemClickListener != null) {
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mItemClickListener.onItemClick(adapterPosition);
                }
            });
        }
        if (mLongClickListener != null) {
            holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    return mLongClickListener.onLongClick(adapterPosition);
                }
            });
        }
    }

/判斷是不是頭部或者底部的位置

  /**
     * 是不是底部位置
     */
    private boolean isFooterPosition(int position) {
        return position >= (mHeaderViews.size() + mAdapter.getItemCount());
    }

    /**
     * 是不是頭部位置
     */
    private boolean isHeaderPosition(int position) {
        return position < mHeaderViews.size();
    }

好了,這時候我們的裝飾的adapter已經寫好了,接下來我們寫下RecylerView的包裹類,不然我們這樣使用

   RecyclerAdapter mRealAdapter = new RecyclerAdapter();
        WrapRecyclerAdapter wrapRecyclerAdapter = new WrapRecyclerAdapter(mRealAdapter);
        // setAdapter
        mRecyclerView.setAdapter(wrapRecyclerAdapter);
    //新增頭尾部

這這樣使用感覺很尷尬啊

WrapRecyclerView繼承於RecyclerView內部

  // 包裹了一層的頭部底部Adapter
    private WrapRecyclerAdapter mWrapRecyclerAdapter;
    // 這個是列表資料的Adapter
    private Adapter mAdapter;

    // 增加一些通用功能
    // 空列表資料應該顯示的空View
    // 正在載入資料頁面,也就是正在獲取後臺介面頁面
    private View mEmptyView, mLoadingView;
public WrapRecyclerView(Context context) {
        super(context);
    }

    public WrapRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public WrapRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

還是先看我們新增和刪除的的四個方法

  // 新增頭部
    public void addHeaderView(View view) {
        // 如果沒有Adapter那麼就不新增,也可以選擇拋異常提示
        // 讓他必須先設定Adapter然後才能新增,這裡是仿照ListView的處理方式
        if (mWrapRecyclerAdapter != null) {
            mWrapRecyclerAdapter.addHeaderView(view);
        }
    }

    // 新增底部
    public void addFooterView(View view) {
        if (mWrapRecyclerAdapter != null) {
            mWrapRecyclerAdapter.addFooterView(view);
        }
    }

    // 移除頭部
    public void removeHeaderView(View view) {
        if (mWrapRecyclerAdapter != null) {
            mWrapRecyclerAdapter.removeHeaderView(view);
        }
    }

    // 移除底部
    public void removeFooterView(View view) {
        if (mWrapRecyclerAdapter != null) {
            mWrapRecyclerAdapter.removeFooterView(view);
        }
    }

複寫setAdapter的方法

  @Override
    public void setAdapter(Adapter adapter) {
        // 為了防止多次設定Adapter
        if (mAdapter != null) {
            mAdapter.unregisterAdapterDataObserver(mDataObserver);
            mAdapter = null;
        }

        this.mAdapter = adapter;

        if (adapter instanceof WrapRecyclerAdapter) {
            mWrapRecyclerAdapter = (WrapRecyclerAdapter) adapter;
        } else {
            mWrapRecyclerAdapter = new WrapRecyclerAdapter(adapter);
        }

        super.setAdapter(mWrapRecyclerAdapter);

        // 註冊一個觀察者
        mAdapter.registerAdapterDataObserver(mDataObserver);

        // 解決GridLayout新增頭部和底部也要佔據一行
        mWrapRecyclerAdapter.adjustSpanSize(this);

        // 載入資料頁面
        if (mLoadingView != null && mLoadingView.getVisibility() == View.VISIBLE) {
            mLoadingView.setVisibility(View.GONE);
        }

        if (mItemClickListener != null) {
            mWrapRecyclerAdapter.setOnItemClickListener(mItemClickListener);
        }

        if (mLongClickListener != null) {
            mWrapRecyclerAdapter.setOnLongClickListener(mLongClickListener);
        }
    }

mDataObserver註冊器的使用呢

private AdapterDataObserver mDataObserver = new AdapterDataObserver() {
        @Override
        public void onChanged() {
            if (mAdapter == null) {
                return;
            }
            // 觀察者  列表Adapter更新 包裹的也需要更新不然列表的notifyDataSetChanged沒效果
            if (mWrapRecyclerAdapter != mAdapter) {
                mWrapRecyclerAdapter.notifyDataSetChanged();
            }
            dataChanged();
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            if (mAdapter == null) return;
            // 觀察者  列表Adapter更新 包裹的也需要更新不然列表的notifyDataSetChanged沒效果
            if (mWrapRecyclerAdapter != mAdapter)
                mWrapRecyclerAdapter.notifyItemRemoved(positionStart);
            dataChanged();
        }

        @Override
        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
            if (mAdapter == null) {
                return;
            }
            // 觀察者  列表Adapter更新 包裹的也需要更新不然列表的notifyItemMoved沒效果
            if (mWrapRecyclerAdapter != mAdapter) {
                mWrapRecyclerAdapter.notifyItemMoved(fromPosition, toPosition);
            }
            dataChanged();
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount) {
            if (mAdapter == null) {
                return;
            }
            // 觀察者  列表Adapter更新 包裹的也需要更新不然列表的notifyItemChanged沒效果
            if (mWrapRecyclerAdapter != mAdapter) {
                mWrapRecyclerAdapter.notifyItemChanged(positionStart);
            }
            dataChanged();
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
            if (mAdapter == null) {
                return;
            }
            // 觀察者  列表Adapter更新 包裹的也需要更新不然列表的notifyItemChanged沒效果
            if (mWrapRecyclerAdapter != mAdapter) {
                mWrapRecyclerAdapter.notifyItemChanged(positionStart, payload);
            }
            dataChanged();
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            if (mAdapter == null) {
                return;
            }
            // 觀察者  列表Adapter更新 包裹的也需要更新不然列表的notifyItemInserted沒效果
            if (mWrapRecyclerAdapter != mAdapter) {
                mWrapRecyclerAdapter.notifyItemInserted(positionStart);
            }
            dataChanged();
        }
    };

Adapter資料改變的方法

  private void dataChanged() {
        if (mAdapter.getItemCount() == 0) {
            // 沒有資料
            if (mEmptyView != null) {
                mEmptyView.setVisibility(VISIBLE);
            } else {
                mEmptyView.setVisibility(GONE);
            }
        }
    }

新增一個空列表資料頁面和新增一個正在載入資料的頁面

   /**
     * 新增一個空列表資料頁面
     */
    public void addEmptyView(View emptyView) {
        this.mEmptyView = emptyView;
    }

    /**
     * 新增一個正在載入資料的頁面
     */
    public void addLoadingView(View loadingView) {
        this.mLoadingView = loadingView;
        mLoadingView.setVisibility(View.VISIBLE);
    }

相關推薦

RecyclerView新增頭部底部

定義: 裝飾設計模式也稱包裝設計模式,用來動態的擴充套件物件的功能,也是繼承關係的的一種替代方案之一。 說個大白話就是,在不使用的繼承的方式下,採用裝飾設計模式可以擴充套件一個物件的功能,可以使一個物件變得越來越強大。 我們首先看下效果圖 我

Android開發之RecyclerView新增頭部底部

前言:我們在使用RecyclerView的過程中其實還是有很多問題的,前面提到的沒有預設的分割線,很是讓人蛋疼啊,但現在有出了個問題,不能新增頭部和底部,這一度讓我認為這貨還是沒有最愛的ListView好用啊,哈哈,既然谷歌出來了,我們就要使用,沒有頭和腳那我們就給他造出頭

RecyclerView新增頭部尾部最簡單的實現方式

@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType ==

RecyclerView新增頭部腳部的UI控制元件:Bookends

Bookends會封裝傳遞給它的adapter。 其工作原理是在 getItemViewType()的返回值中新增額外的view item型別,將addHeader() 和 addFooter()提供的view對映為頭部和腳部。 使用這個類有如下的限制: 只對單列的列表有效(比如使用Linear

RecycleView新增頭部底部

實現思路 在Adapter中定義不同的佈局和item狀態 判斷item的狀態,載入對應的佈局檔案 例項程式碼 在gradle中新增下面的依賴 compile 'com.android.support:recyclerview-v7:23.1.

RecyclerView新增頭部尾部

為RecyclerView新增頭部和底部 第一步1 需要定義一個Adapter,繼承自RecyclerView.Adapter HeaderAndFooterWrapper<

Android 給RecyclerView新增頭部尾部

之前我在GitHub上開源了一個可以實現RecyclerView列表分組的通用Adapter: 。也在部落格上寫了一篇專門介紹它的實現和使用的文章:《Android 可分組的RecyclerViewAdapter》。有一些朋友在看了我的博文和使用我的開源庫後,會

RecyclerView新增頭部(addHeaderView)腳部(addFooterView)的封裝

RecyclerView的功能強大之處就不用說了,但是相比於listview來說,它也有些小缺點,比如:沒有了間隔線divider,沒有addHeaderView、addFooterView等方法,所以用起來不是那麼方便。今天主要完成RecyclerView的a

Material Design RecyclerView新增頭部底部佈局(五)

前言:相信很多人都知道,現在的日常開發中,已經逐漸由RecyclerView代替ListView、GridView,但相對於後者來說,RecyclerView存在著一些缺點,比如這篇文章要講的,RecyclerView沒有新增頭部佈局和底部佈局的方法。 想到這裡,我就想著那

HTML5 開發APP(頭部底部選項卡)

技術 開發 方法 eat 激活 default 底部 top doc 我們開發app有一定固定的樣式,比如頭部和底部選項卡部分就是公共部分就比如我在做的app進來的主頁面就像圖片顯示的那樣 我們該怎麽實現呢,實現我們應該建一個主頁面index.html,然後建五個子頁面

7.如何給RecyclerView新增ClickLongClick事件

/** * 作者:Pich * 原文連結:http://me.woblog.cn/ * QQ群:129961195 * 微信公眾號:woblog * Github:https://github.com/lifengsofts */ 詳解RecyclerView系列文章目錄

html 引入一個公共的頭部底部

一、asp語言和PHP語言 首先製作一個頭部檔案head.asp,或者一個底部檔案foot.asp。如主頁是index.asp,呼叫頭部程式碼是在index.asp檔案程式碼的開始位置(第一個標記後面,<head>標記前面)增加如下程式碼: <!– #include

html 如何引入一個公共的頭部底部

原文轉自https://yq.aliyun.com/articles/100662?t=t1# html 靜態頁面中引用外部頁面沒那麼方便,主要方法有: 1.asp語言和PHP語言 首先製作一個頭部檔案head.asp,或者一個底部檔案foot.asp。如主頁是index.asp,呼叫頭

css 頭部底部固定,中間高度自適應,出滾動條 css程式碼

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initia

RecyclerView新增頭部

一、Adapter中資料 package com.example.jiangshuncongykcs.adapter; import android.content.Context; import android.support.v7.widget.RecyclerVie

Android 簡捷地為RecyclerView新增HeadViewFootView

之前自己嘗試用ViewType的思路去實現為RecyclerView新增HeadView和FootView,總感覺自己寫的程式碼太囉嗦了,向兩位大神學習了一下他們的編碼思路寫了個簡單了些的,分享給大家。 廢話不多說,上圖上程式碼。 public class Recy

android_為recyclerView新增headerViewfooterView以及recyclerview的重新整理

緊接著上一篇 新增head和foot headerAndFooterWrapper = new HeaderAndFooterWrapper(adapter); TextView t1 = new TextView(this); t1.setText("Heade

頭部底部固定,中間內容可滾動

html+css的程式碼的展示<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><

Android 為RecyclerView新增HeaderViewFooterView

對於新增headerView或者footerView其實HeaderView實際上也是Item的一種,只不過顯示在頂部的位置,那麼我們完全可以通過為其設定ItemType來完成。有了思路以後,接下來考慮一些細節。介面卡public class TimeTablesAdapte

Android RecyclerView新增頭部與尾部

RecyclerView一個藝術般的控制元件,完全取代了之前的ListView與GridView,並且有著比ListView與GridView更加強大的功能,可是RecyclerView缺有個非誠明顯的瑕疵,竟然沒有封裝addHeaderView和addFoot