學會自己給RecyclerView添加Header、Footer和加載更多回調

 

我的視頻課程:《FFmpeg打造Android萬能音頻播放器》

我的視頻課程(編碼直播推流):《Android視頻編碼和直播推流》

 

        說來也慚愧,RecyclerView都出來了多麼久了,但我還在listview中暢遊,繼續這樣是不行的,是時候展現新的技術了。對於習慣了listview的我來說,剛開始覺得RecyclerView不就和listview一樣嗎,而且還沒有添加header和footer的方法,真是簡直了,當得知了一些很複雜酷炫的佈局都是用RecyclerView實現的時候,我承認我眼拙了,放着這麼優秀的控件不用,真是悔之晚矣呀。所以現在就都把listview改成了RecyclerView,併爲之添加了添加頭和添加尾的方法和加載更多的回調方法,至於下拉刷新就取用別人寫好的輪子吧。還是老規矩,先上圖,看看勞動成果在開幹:

 

listview類型的線性列表:

gridview類型的多列列表:

 

更新:橫向時通過反射獲取類RecyclerView.LayoutManager的mOrientation屬性值,然後在滑動時判斷方向來求相應的滑動值來判斷加載更多,WapHeaderAndFooterAdapter中添加反射方法

 

private int getOrientation(RecyclerView.LayoutManager layoutManager)
    {
        int mOrientation = 0;
        Class<?> clazz = null;
        try {
            clazz = Class.forName("android.support.v7.widget.LinearLayoutManager");
            Field field = clazz.getDeclaredField("mOrientation");
            field.setAccessible(true);
            mOrientation = field.getInt(layoutManager);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        return mOrientation;
    }

 

然後在onAttachedToRecyclerView方法中獲取mOrientation屬性的值:

 

/**
     * 處理當時Gridview類型的效果時,也把頭部和尾部設置成一整行(這就是RecyclerView的其中一個優秀之處,列表的每行可以不同數量的列)
     * @param recyclerView
     */
    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        mOrientation = getOrientation(layoutManager);//獲取mOrientation值
        if(layoutManager instanceof GridLayoutManager)
        {
            /**
             * getSpanSize的返回值的意思是:position位置的item的寬度佔幾列
             * 比如總的是4列,然後頭部全部顯示的話就應該佔4列,此時就返回4
             * 其他的只佔一列,所以就返回1,剩下的三列就由後面的item來依次填充。
             */
            ((GridLayoutManager) layoutManager).setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    if(headerView != null && footerView != null)
                    {
                        if(position == 0)
                        {
                            return ((GridLayoutManager) layoutManager).getSpanCount();
                        }
                        else if(position == getItemCount() - 1) {
                            return ((GridLayoutManager) layoutManager).getSpanCount();
                        }
                        else
                        {
                            return 1;
                        }
                    }
                    else if(headerView != null) {
                        if (position == 0) {
                            return ((GridLayoutManager) layoutManager).getSpanCount();
                        }
                        return 1;
                    }
                    else if(footerView != null)
                    {
                        if(position == getItemCount() - 1)
                        {
                            return ((GridLayoutManager) layoutManager).getSpanCount();
                        }
                        return 1;
                    }
                    return 1;
                }
            });

        }
    }

 

最後在isSlideToBottom方法中判斷:

 

/**
     * 判斷是否到底部了
     * @param recyclerView
     * @return
     */
    protected boolean isSlideToBottom(RecyclerView recyclerView) {
        if (recyclerView == null) return false;
        if(mOrientation == LinearLayoutManager.VERTICAL) {
            if (recyclerView.computeVerticalScrollExtent() + recyclerView.computeVerticalScrollOffset() >= recyclerView.computeVerticalScrollRange())
                return true;
        }
        else
        {
            if (recyclerView.computeHorizontalScrollExtent() + recyclerView.computeHorizontalScrollOffset() >= recyclerView.computeHorizontalScrollRange())
                return true;
        }
        return false;
    }

-------------------------------------------------------------------------------------------------------------

 

 

 

 

大家是不是覺得這兩種效果應該能滿足大部分需求了吧,其實不然,這裏用了設計模式中的——裝飾者模式,對你們自己的RecyclerViewAdapter沒有任何入侵,只是在外層包裹了一層功能而已,你們的RecyclerViewAdapter的所以功能都完全保留了的,完全非入侵,想怎麼玩就怎麼玩。

首先講一講“裝飾者模式”是怎樣的:

        比如有個類A,A中有a和b兩個方法,然後新的需求需要給A添加一個c方法,此時我們想到的都是集成A,然後再在裏面添加c方法,這是完全可以的,但是隨後又需要添加d方法,我們就又需要集成A,然後添加d方法,但是需求是在彼變的,可能會加很多,然後我們就寫很多集成類?NO~,我們不能再這麼幹, 不然會瘋掉的。解決這個問題就需要用到“裝飾者模式”了,它是把原來的類進行一次同類型(原來的類集成誰,這個也繼承誰)的封裝,並且原來的類完全不用改變,達到了零入侵。比如:我們現在已經有了A類了,然後需要添加一個c方法,我們可以設計一個類B集成A,然後裏面添加一個A類型的變量,然後把c方法也添加後,構造參數裏面傳入A類型的變量,後面就可以使用B來代替A了,並且裏面添加了c方法,A裏面的功能照舊,這樣就完成了對A的修飾,僞代碼如下:

 

    //A 類
    class A
    {
        void a();
        void b();
    }
    //B類
    class B extends A
    {
        A a;
        B(A a)
        {
            this.a = a;
        }
        void a()
        {
            a.a();
        }
        void c();
    }
    
    //使用
    A a = new A();
    B b = new B(a);
    b.c();

 

我理解的裝飾者模式就是這樣的,有什麼不足之處還請多多包涵。

接下來開始我們的封裝吧:

        先來算法:因爲RecyclerView很靈活,裏面所有的一切對它來說都是一個item,所以我們添加的header和footer也是item,只是這兩個item佈局不一樣並且一個在列表頭和列表尾而已,這大概也就是沒有默認提供header和fooer的原因了吧。然後我們要做的就是給現有的adapter添加這兩個item,根據裝飾者模式我們也需要寫一個adapter類集成Recy.Adapter類,並實現默認的onCreateViewHolder、onBindViewHolder和getItemCount三個方法,然後還需要實現getItemViewType這個方法來爲第一個返回header的type值,和最後一個footer的type值,這樣就可以在onCreateViewHolder方法中根據type來返回不同的viewholder了(headerholder、footerholder、normalholder),其中返回normalholder就是我們傳入的adapter.onCreateViewHolder就可以了,其他方法也是類似的,只有不是header或者footer類型的就直接adapter.方法返回就是了。這樣就完成了封裝。

一、首先用RecyclerView把數據展示出來

(1):導入RecyclerView的庫,根據自己的SDK版本來添加

 

compile 'com.android.support:recyclerview-v7:26.0.0-alpha1'

(2):佈局文件

 

 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

</RelativeLayout>

(3):集成RecyclerView.Adapter類的MyAdapter,並實現默認的onCreateViewHolder、onBindViewHolder和getItemCount三個方法

 

 

package com.ywl5320.recyclerveiwdemo;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.List;

/**
 * Created by ywl on 2017-7-23.
 */

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{

    private Context context;
    private List<String> datas;

    public MyAdapter(Context context, List<String> datas) {
        this.context = context;
        this.datas = datas;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_text, parent, false);
        MyHolder holder = new MyHolder(v);
        return holder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        ((MyHolder)holder).textView.setText(datas.get(position));
    }

    @Override
    public int getItemCount() {
        return datas.size();
    }

    public class MyHolder extends RecyclerView.ViewHolder
    {
        private TextView textView;
        public MyHolder(View itemView) {
            super(itemView);
            textView = (TextView) itemView.findViewById(R.id.tv_title);
        }
    }
}

因爲RecyclerView是強制使用ViewHolder的,並且都是RecyclerView.ViewHolder的子類,所以這裏創建了一個MyHolder的類,裏面主要負責初始化我們的item的佈局。

 

(4):調用

 

setContentView(R.layout.activity_main);
        recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
        datas = new ArrayList<>();
        for(int i = 0; i < 60; i++)
        {
            datas.add(i + "");
        }
        myAdapter = new MyAdapter(this, datas);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(linearLayoutManager);
        recyclerView.setAdapter(myAdapter);

 

這就是使用RecyclerView最基本的方法了,這個是沒有header和footer的。
二、創建我們的封裝類對原來的Adapter進行封裝,注意事項和解釋都寫在代碼註釋上面比較清晰,我們需要知道的是當我們的adapter傳入進來後是怎麼運作的

getItemViewType->onCreateViewHolder->onBindViewHolder,然後我們就需要在相應的方法中根據是否有header或者footer進行相應的操作,沒有的就直接返回傳入的adapter的相應的方法就可以了

 

package com.ywl5320.recyclerveiwdemo;

import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;

/**
 * Created by ywl on 2017-7-23.
 */

public class WapHeaderAndFooterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private int headtype = 0x11111;
    private int normaltype = 0x11112;
    private int foottype = 0x11113;
    private View headerView;
    private View footerView;
    private RecyclerView.Adapter badapter;//目標adapter
    private OnLoadMoreListener onloadMoreListener;


    public WapHeaderAndFooterAdapter(RecyclerView.Adapter badapter) {
        this.badapter = badapter;
    }

    public void addHeader(View header)
    {
        headerView = header;
    }

    public void addFooter(View footer)
    {
        footerView = footer;
    }

    //實現加載更多接口
    public void setOnloadMoreListener(final OnLoadMoreListener onloadMoreListener, RecyclerView recyclerView) {
        this.onloadMoreListener = onloadMoreListener;
        if(recyclerView != null && onloadMoreListener != null)
        {
            recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                    super.onScrollStateChanged(recyclerView, newState);
                }

                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                    super.onScrolled(recyclerView, dx, dy);
                    if(isSlideToBottom(recyclerView))
                    {
                        if(onloadMoreListener != null)
                        {
                            onloadMoreListener.onLoadMore();
                        }
                    }
                }
            });
        }
    }

    /**
     * 根據頭部尾部返回相應的type,這裏if else沒有簡寫,方便看邏輯
     * @param position
     * @return
     */
    @Override
    public int getItemViewType(int position) {

        if(headerView != null && footerView != null)//同時加了頭部和尾部
        {
            if(position == 0)//當position爲0時,展示header
            {
                return headtype;
            }
            else if(position == getItemCount() - 1)//當position爲最後一個時,展示footer
            {
                return foottype;
            }
            else//其他時候就展示原來adapter的
            {
                return normaltype;
            }
        }
        else if(headerView != null) {//只有頭部
            if (position == 0)
                return headtype;
            return normaltype;
        }
        else if(footerView != null)//只有尾部
        {
            if(position == getItemCount() - 1)
            {
                return foottype;
            }
            else
            {
                return normaltype;
            }
        }
        else {
            return normaltype;
        }
    }

    /**
     * 這裏就根據getItemViewType返回的值來返回相應的ViewHolder
     * 頭部和尾部的ViewHolder只是一個集成RecyclerView.ViewHolder的簡單默認類,裏面並沒有任何處理。
     * 這樣就完成了類型的返回了(需注意爲什麼這樣做)
     * @param parent
     * @param viewType
     * @return
     */
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if(viewType == headtype)//返回頭部的ViewHolder
            return new HeaderViewHolder(headerView);
        else if(viewType == foottype)//返回尾部的ViewHolder
            return new FoogerViewHolder(footerView);//其他就直接返回傳入的adapter的ViewHolder
        return badapter.onCreateViewHolder(parent, viewType);
    }

    /**
     * 綁定ViewHolder,當時header或footer時,直接返回,因爲不用綁定,
     * 當是傳入的adapter時,就直接調用adapter.onBindViewHolder就行了
     * @param holder
     * @param position
     */
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

        if(headerView != null && footerView != null)//有頭部和尾部
        {
            if(position == 0)//頭部直接返回,無需綁定
            {
                return;
            }
            else if(position == getItemCount() -1)//尾部直接返回,也無需綁定
            {
                return;
            }
            else
            {
                badapter.onBindViewHolder(holder, position - 1);//其他就調用adapter的綁定方法
            }
        }
        else if(headerView != null)
        {
            if(position == 0)
            {
                return;
            }
            else
            {
                badapter.onBindViewHolder(holder, position - 1);
            }
        }
        else if(footerView != null)
        {
            if(position == getItemCount() - 1)
            {
                return;
            }
            else
            {
                badapter.onBindViewHolder(holder, position);
            }
        }
        else
        {
            badapter.onBindViewHolder(holder, position);
        }
    }

    /**
     * 返回item的數量,
     * @return
     */
    @Override
    public int getItemCount() {

        if(headerView != null && footerView != null)//有頭部和尾部,就多了2
        {
            return badapter.getItemCount() + 2;
        }
        else if(headerView != null)//只有頭部多了1
        {
            return badapter.getItemCount() + 1;
        }
        else if(footerView != null)//只有尾部也多了1
        {
            return badapter.getItemCount() + 1;
        }
        return badapter.getItemCount();//其他就是默認的值, 不多也不少
    }

    /**
     * 頭部的ViewHolder
     */
    private class HeaderViewHolder extends RecyclerView.ViewHolder
    {
        public HeaderViewHolder(View itemView) {
            super(itemView);
        }
    }

    /**
     * 尾部的ViewHolder
     */
    private class FoogerViewHolder extends RecyclerView.ViewHolder
    {
        public FoogerViewHolder(View itemView) {
            super(itemView);
        }
    }

    /**
     * 處理當時Gridview類型的效果時,也把頭部和尾部設置成一整行(這就是RecyclerView的其中一個優秀之處,列表的每行可以不同數量的列)
     * @param recyclerView
     */
    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if(layoutManager instanceof GridLayoutManager)
        {
            /**
             * getSpanSize的返回值的意思是:position位置的item的寬度佔幾列
             * 比如總的是4列,然後頭部全部顯示的話就應該佔4列,此時就返回4
             * 其他的只佔一列,所以就返回1,剩下的三列就由後面的item來依次填充。
             */
            ((GridLayoutManager) layoutManager).setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    if(headerView != null && footerView != null)
                    {
                        if(position == 0)
                        {
                            return ((GridLayoutManager) layoutManager).getSpanCount();
                        }
                        else if(position == getItemCount() - 1) {
                            return ((GridLayoutManager) layoutManager).getSpanCount();
                        }
                        else
                        {
                            return 1;
                        }
                    }
                    else if(headerView != null) {
                        if (position == 0) {
                            return ((GridLayoutManager) layoutManager).getSpanCount();
                        }
                        return 1;
                    }
                    else if(footerView != null)
                    {
                        if(position == getItemCount() - 1)
                        {
                            return ((GridLayoutManager) layoutManager).getSpanCount();
                        }
                        return 1;
                    }
                    return 1;
                }
            });
        }
    }

    /**
     * 判斷是否到底部了
     * @param recyclerView
     * @return
     */
    protected boolean isSlideToBottom(RecyclerView recyclerView) {
        if (recyclerView == null) return false;
        if (recyclerView.computeVerticalScrollExtent() + recyclerView.computeVerticalScrollOffset() >= recyclerView.computeVerticalScrollRange())
            return true;
        return false;
    }

    /**
     * 加載更多回調接口
     */
    public interface OnLoadMoreListener
    {
        void onLoadMore();
    }

}

 

三:最後的調用方式爲

 

setContentView(R.layout.activity_main);
        recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
        datas = new ArrayList<>();
        for(int i = 0; i < 60; i++)
        {
            datas.add(i + "");
        }
        myAdapter = new MyAdapter(this, datas);
        headerAndFooterAdapter = new WapHeaderAndFooterAdapter(myAdapter);

//        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
//        linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
//        recyclerView.setLayoutManager(linearLayoutManager);

        GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 3);
        recyclerView.setLayoutManager(gridLayoutManager);

        View header = LayoutInflater.from(this).inflate(R.layout.header_view, recyclerView, false);
        View footer = LayoutInflater.from(this).inflate(R.layout.footer_view, recyclerView, false);

        headerAndFooterAdapter.addHeader(header);
        headerAndFooterAdapter.addFooter(footer);

        recyclerView.setAdapter(headerAndFooterAdapter);
        headerAndFooterAdapter.setOnloadMoreListener(new WapHeaderAndFooterAdapter.OnLoadMoreListener() {
            @Override
            public void onLoadMore() {
                Toast.makeText(MainActivity.this, "加載更多", Toast.LENGTH_SHORT).show();
            }
        }, recyclerView);

        這裏recyclerView.setAdapter中的adapter就是我們封裝的adapter 了,這裏有一點需要注意就是創建headerview的時候Inflate時,parent需要傳入recyclerView對象才能時佈局matchparent起效果,具體原因在Inflate源碼裏面能找到的,這裏我就不細說了。

 

        好了外面已經是風雲雷電交加,中國之聲的《千里共良宵》我也已經聽第二遍了,也該睡了。

 

Demo下載地址:GitHub:https://github.com/wanliyang1990/RecyclerViewHeaderAndFooter


 

 

 

發佈了64 篇原創文章 · 獲贊 154 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章