RecyclerView自定義添加Header和Footer

前言: 自從上次忘記了某事之后圆仔,我就決定了在網(wǎng)上記錄下自己學(xué)習(xí)的一些筆記(有錯(cuò)誤請(qǐng)?jiān)u論告訴我,謝謝

今天突然想起來之前寫過的一個(gè)項(xiàng)目产徊,首頁用的是ScrollView的嵌套,其中里面還嵌套了RecyclerView恬汁,在滑動(dòng)到RecyclerView那一部分的時(shí)候有明顯的卡頓,覺得應(yīng)該把整體當(dāng)作一個(gè)RecyclerView不應(yīng)該用嵌套

原項(xiàng)目效果圖:

image

開始自定義一個(gè)可以添加Header和Footer的RecyclerView辜伟,由于RecyclerView沒有自己的addHeaderView方法氓侧,所以參考了ListView的代碼,整體的代碼如下:

CustomRecyclerView 繼承自RecyclerView

public class CustomRecyclerView extends RecyclerView {
    private ArrayList<ViewConfig> mHeadCouListInfo; //保存頭部的view
    private ArrayList<ViewConfig> mFootCouListInfo; //保存尾部的view
    private int headCount;  //記錄head的個(gè)數(shù)
    private int footCount;  //記錄foot的個(gè)數(shù)
    private Adapter mAdapter; //adapter导狡,可能是customadapter约巷, 可能是自定義adapter
    private Context mContext;

    public CustomRecyclerView(@NonNull Context context) {
        this(context, null);
    }

    public CustomRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

    private void init(Context context) {
        mHeadCouListInfo = new ArrayList<>();
        mFootCouListInfo = new ArrayList<>();
        mContext = context;
    }

    /**
     * 添加HeadView的方法
     *
     * @param view
     */
    public void addHeadView(View view) {
        this.addHeadView(view, 0);
    }

    public void addHeadView(View view, int index) {
        headCount++;
        setHeadViewConfig(view, ViewConfig.HEADVIEW, headCount, 100000);
        if (mAdapter != null) {
            if (!(mAdapter instanceof CustomAdapter)) {
                wrapHeadAdapter();
            }
        }
    }

    public void addFootView(View view) {
        this.addFootView(view, 0);
    }

    public void addFootView(View view, int index) {
        footCount++;
        setFootViewConfig(view, ViewConfig.FOOTVIEW, footCount, 100000);
        if (mAdapter != null) {
            if (!(mAdapter instanceof CustomAdapter)) {
                wrapHeadAdapter();
            }
        }
    }

    /**
     * 將adapter構(gòu)建為customadapter用來填充頭部尾部布局
     */
    private void wrapHeadAdapter() {
        mAdapter = new CustomAdapter(mHeadCouListInfo, mFootCouListInfo, mAdapter, mContext);
    }

    @Override
    public void setAdapter(@Nullable Adapter adapter) {
        if (mHeadCouListInfo.size() > 0 || mFootCouListInfo.size() > 0) {
            mAdapter = new CustomAdapter(mHeadCouListInfo, mFootCouListInfo, adapter, mContext);
        } else {
            mAdapter = adapter;
        }
        super.setAdapter(mAdapter);
    }

    /**
     * 配置頭部view的信息
     *
     * @param view
     * @param type
     * @param count
     * @param headCount
     */
    private void setHeadViewConfig(View view, String type, int count, int headCount) {
        ViewConfig viewConfig = new ViewConfig();
        viewConfig.setTag(view.getClass() + type + count);
        viewConfig.setType(headCount);
        viewConfig.setView(R.layout.item_tv);
        ViewGroup parent = (ViewGroup) view.getParent();
        if (parent != null) {
            parent.removeView(view);
        }
        viewConfig.setContentView(view);
        mHeadCouListInfo.add(viewConfig);
    }

    /**
     * 配置尾部view的信息
     *
     * @param view
     * @param type
     * @param count
     * @param headCount
     */
    private void setFootViewConfig(View view, String type, int count, int headCount) {
        ViewConfig viewConfig = new ViewConfig();
        viewConfig.setTag(view.getClass() + type + count);
        viewConfig.setType(headCount);
        viewConfig.setView(R.layout.item_tv);
        ViewGroup parent = (ViewGroup) view.getParent();
        if (parent != null) {
            parent.removeView(view);
        }
        viewConfig.setContentView(view);
        mFootCouListInfo.add(viewConfig);
    }

    public CustomAdapter getHeadAndFootAdapter() {
        return (CustomAdapter) mAdapter;
    }

    public void setCustomClickListener(ICustomClickListener customClickListener) {
        getHeadAndFootAdapter().setCustomClickListener(customClickListener);
    }
}

setHeadViewConfig和setFootdViewConfig兩個(gè)方法作用一致,只是兩個(gè)添加的元素不一樣
wrapHeadAdapter方法的作用是用于構(gòu)造CustomAdapter用來處理頭部尾部的view旱捧,如果處理不了独郎, 將會(huì)自動(dòng)轉(zhuǎn)交給自定義的Adapter處理
類里部分參數(shù)沒用到,但是我感覺后面我會(huì)用到的


干嘛鴨.jpg

下面是CustomAdapter的代碼

CustomAdapter 繼承自 RecyclerView.Adapter<? extends ViewHolder>

public class CustomAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements View.OnClickListener {
    private List<ViewConfig> headConfig;
    private List<ViewConfig> footConfig;
    private ArrayList<ViewConfig> EMPTY_LIST = new ArrayList<>();
    private LayoutInflater inflater;
    private RecyclerView.Adapter mAdapter;
    private int headcount = 0;
    private int footcount = 0;
    private Context mContext;
    private ICustomClickListener customClickListener;

    public CustomAdapter(List<ViewConfig> headConfig, List<ViewConfig> footConfig, RecyclerView.Adapter mAdapter, Context mContext) {
        this.mAdapter = mAdapter;
        this.inflater = LayoutInflater.from(mContext);
        this.mContext = mContext;
        if (headConfig == null) {
            this.headConfig = EMPTY_LIST;
        } else {
            this.headConfig = headConfig;
        }
        if (footConfig == null) {
            this.footConfig = EMPTY_LIST;
        } else {
            this.footConfig = footConfig;
        }
    }

    /**
     * 設(shè)置監(jiān)聽事件
     *
     * @param customClickListener
     */
    public void setCustomClickListener(ICustomClickListener customClickListener) {
        this.customClickListener = customClickListener;
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int index) {
        LogUtils.e("CustomAdapter", "index:" + index);
        if (index == ViewConfig.HEADVIEW_TYPE) {
            FrameLayout contentView = (FrameLayout) inflater.inflate(R.layout.item_head_foot_parent, viewGroup, false);
            View cView = headConfig.get(headcount++).getContentView();
            ViewGroup vg = (ViewGroup) cView.getParent();
            if (vg != null) {
                vg.removeView(cView);
            }
            contentView.addView(cView);
            return new CustomViewHolder(contentView);
        } else if (index == ViewConfig.FOOTVIEW_TYPE) {
            FrameLayout contentView = (FrameLayout) inflater.inflate(R.layout.item_head_foot_parent, viewGroup, false);
            View cView = footConfig.get(footcount++).getContentView();
            ViewGroup vg = (ViewGroup) cView.getParent();
            if (vg != null) {
                vg.removeView(cView);
            }
            contentView.addView(cView);
            return new CustomViewHolder(contentView);
        } else {
            return mAdapter.onCreateViewHolder(viewGroup, index);
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, final int position) {
        CustomViewHolder customViewHolder;
        if (viewHolder instanceof CustomViewHolder) {
            customViewHolder = (CustomViewHolder) viewHolder;
            View mParent = customViewHolder.mParent;
            //設(shè)置view的點(diǎn)擊事件
            if (mParent instanceof ViewGroup) {
                ViewGroup parentGroup = (ViewGroup) mParent;
                int childCount = parentGroup.getChildCount();
                for (int i = 0; i < childCount; i++) {
                    parentGroup.getChildAt(i).setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            if (customClickListener != null) {
                                if (position < getHeadSize()) {
                                    customClickListener.onClick(v, position, 0);
                                } else {
                                    customClickListener.onClick(v, position - getHeadSize() - mAdapter.getItemCount(), 1);
                                }
                            }
                        }
                    });
                }
            }
            mParent.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (position < getHeadSize()) {
                        customClickListener.onClick(v, position, 0);
                    } else {
                        customClickListener.onClick(v, position - getHeadSize() - mAdapter.getItemCount(), 1);
                    }
                }
            });
        } else {
            mAdapter.onBindViewHolder(viewHolder, position - getHeadSize());
        }
    }

    @Override
    public int getItemCount() {
        return mAdapter != null
                ? headConfig.size() + mAdapter.getItemCount() + footConfig.size()
                : headConfig.size() + footConfig.size();
    }

    @Override
    public void onClick(View v) {
        LogUtils.e("CustomAdapter", "點(diǎn)擊");
    }

    public class CustomViewHolder extends RecyclerView.ViewHolder {
        View mParent;

        public CustomViewHolder(@NonNull View itemView) {
            super(itemView);
            mParent = itemView;
        }
    }

    @Override
    public int getItemViewType(int position) {
        int headSize = getHeadSize();
        int adapterCount = getItemCount();//獲取實(shí)際的個(gè)數(shù)
        if (position < headSize) {
            return ViewConfig.HEADVIEW_TYPE;
        } else if (position >= headSize + mAdapter.getItemCount() && position < adapterCount) {
            return ViewConfig.FOOTVIEW_TYPE;
        }
        return -1;
    }

    public int getHeadSize() {
        return headConfig.size();
    }

    public int getFootSize() {
        return footConfig.size();
    }
}

getItemViewType中根據(jù)position當(dāng)前的值返回類型

position < headsize (那么證明現(xiàn)在就是頭部
position >= headsize && position < headSize + mAdapter.getItemCount (證明這個(gè)時(shí)候是實(shí)際的itemview
position >= headSize + mAdapter.getItemCount() && position < getItemCount (這個(gè)時(shí)候就是尾部了
在代碼中 我們只要判斷頭部和尾部就行了枚赡, 其他一律返回-1 (其他數(shù)也可以)

onCreateViewHolder()
在這個(gè)方法中根據(jù)index判斷view的類型氓癌,當(dāng)然這個(gè)index只是在我的電腦上顯示, 在你的上面顯示可能是i也可能type贫橙,等等贪婉。

起先我用了

View contentView =  inflater.inflate(headConfig.get(0).getView(), viewGroup, false);
contentView.addView(contentView);

這樣的代碼, 但是拋出了一個(gè)異常卢肃,大體意思就是復(fù)用的錯(cuò)誤 后來改用了這樣的寫法

FrameLayout contentView = (FrameLayout) inflater.inflate(R.layout.item_head_foot_parent, viewGroup, false);
            View cView = headConfig.get(headcount++).getContentView();
            ViewGroup vg = (ViewGroup) cView.getParent();
            if (vg != null) {
                vg.removeView(cView);
            }
            contentView.addView(cView);

先得到一個(gè)FrameLayout疲迂,再將View添加進(jìn)去星压, 但是在添加進(jìn)去之前需要先判斷view有沒有parent,有的話要先用parent移除掉view鬼譬, 不然會(huì)拋出

You must call removeView() on the child's parent first

這個(gè)異常

onBindViewHolder
這個(gè)方法中會(huì)先判斷ViewHolder, 如果ViewHolder是CustomViewHolder的話會(huì)得到里面唯一的view逊脯, 再去判斷該view是普通View還是ViewGroup优质。(正常情況來說都是ViewGroup,因?yàn)閂iewholder里面的View實(shí)際就是FrameLayout
在得出FrameLayout中的子view军洼, 為子view添加了點(diǎn)擊事件和長按事件

這次的代碼大概就這么多巩螃,還沒來得及好好測(cè)一測(cè),后面會(huì)在添加頭部尾部的基礎(chǔ)上添加上拉加載下拉刷新的邏輯匕争。
部分demo調(diào)用代碼和效果圖如下

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.mData = new ArrayList<>();
        this.mCustomRv = findViewById(R.id.m_customrv);
        mCustomRv.setLayoutManager(new LinearLayoutManager(this));
        RvAdapter rvAdapter = new RvAdapter(mData, this);
        mCustomRv.addHeadView(getTextView("頭部9527", Color.parseColor("#CA66F0")));
        mCustomRv.addHeadView(getTextView("頭部17131", Color.parseColor("#A6A5F1")));
        mCustomRv.addFootView(getTextView("尾部9527", Color.parseColor("#05F066")));
        mCustomRv.addFootView(getTextView("尾部17131", Color.parseColor("#90C56F")));
        mCustomRv.setAdapter(rvAdapter);
        mCustomRv.setCustomClickListener(new ICustomClickListener() {
            @Override
            public void onClick(View view, int position,int type,  Object... data) {
                LogUtils.e("MainActivity", "position:" + position);
            }

            @Override
            public void onLongClick(View view, int position,int type, Object... data) {

            }
        });
        /**
         * 添加數(shù)據(jù)源
         */
        for (int i = 0; i < 5; i++) {
            mData.add("mData --- " + i);
        }
    }
2個(gè)頭部2個(gè)尾部.png

多個(gè)頭部和多個(gè)尾部.png

嘻嘻嘻.jpg

項(xiàng)目已經(jīng)發(fā)布到ghithub 并有g(shù)radle依賴
CustomRecyclerView

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末避乏,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子甘桑,更是在濱河造成了極大的恐慌拍皮,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件跑杭,死亡現(xiàn)場(chǎng)離奇詭異铆帽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)德谅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門爹橱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人窄做,你說我怎么就攤上這事愧驱。” “怎么了椭盏?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵组砚,是天一觀的道長。 經(jīng)常有香客問我庸汗,道長惫确,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任蚯舱,我火速辦了婚禮改化,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘枉昏。我一直安慰自己陈肛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布兄裂。 她就那樣靜靜地躺著句旱,像睡著了一般阳藻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谈撒,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天腥泥,我揣著相機(jī)與錄音,去河邊找鬼啃匿。 笑死蛔外,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的溯乒。 我是一名探鬼主播夹厌,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼裆悄!你這毒婦竟也來了矛纹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤光稼,失蹤者是張志新(化名)和其女友劉穎或南,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钟哥,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡迎献,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腻贰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吁恍。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖播演,靈堂內(nèi)的尸體忽然破棺而出冀瓦,到底是詐尸還是另有隱情,我是刑警寧澤写烤,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布翼闽,位于F島的核電站,受9級(jí)特大地震影響洲炊,放射性物質(zhì)發(fā)生泄漏感局。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一暂衡、第九天 我趴在偏房一處隱蔽的房頂上張望询微。 院中可真熱鬧,春花似錦狂巢、人聲如沸撑毛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽藻雌。三九已至雌续,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胯杭,已是汗流浹背驯杜。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留做个,地道東北人艇肴。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像叁温,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子核畴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

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