上拉加载下拉刷新的RecyclerView可添加headerView

来源:互联网 发布:期货数据分析 编辑:程序博客网 时间:2024/04/25 12:07

上拉加载下拉刷新的RecyclerView可添加headerView

这个demo来自 github 我只是对代码重构了一下,支持原作者。

先说一下思路把,上拉和下拉都只是是给RecyclerView添加了一个headerView和footerView。在用listView时添加一个headerView和footerView很简单,只要add一下就可以了。但到RecyclerView上可没有什么add方法那怎么办?
看一下listView是怎么做的,仿着它弄一个。在listView.setAdapter()方法中有这样一段代码

if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {    mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);} else {    mAdapter = adapter;}

是不是一下就明白了!就是说如果你添加了headerView或footerView它都会把你的adapter在封装成另一个HeaderViewListAdapter。
它里面有这样一个方法

public int getItemViewType(int position) {    int numHeaders = getHeadersCount();    if (mAdapter != null && position >= numHeaders) {        int adjPosition = position - numHeaders;        int adapterCount = mAdapter.getCount();        if (adjPosition < adapterCount) {            return mAdapter.getItemViewType(adjPosition);        }     }     return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;         }

在用listView显示不同的item时,我们就会在adapter中重写这个方法return不同的type来实现。看HeaderViewListAdapter的这个方法就是说如果有headerView或footerView就会返回AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER否则返回adapter的getItemViewType
好了有了这些基本就可以了,接下来我们自己来实现一个

public class WrapAdapter extends RecyclerView.Adapter{    private static final int TYPE_REFRESH_HEADER =  -5;    private static final int TYPE_HEADER =  -4;    private static final int TYPE_FOOTER =  -3;    private static final int TYPE_NORMAL =  0;    private RecyclerView.Adapter adapter;    private ArrayList<View> mHeaderViews;    private ArrayList<BaseMoreFooter> mFootViews;    private int headerPosition = 1;    public WrapAdapter(ArrayList<View> headerViews, ArrayList<BaseMoreFooter> footViews, RecyclerView.Adapter adapter) {        this.adapter = adapter;        this.mHeaderViews = headerViews;        this.mFootViews = footViews;    }    public boolean isHeader(int position) {        return position >= 0 && position < mHeaderViews.size();    }    public boolean isFooter(int position) {        return position < getItemCount() && position >= getItemCount() - mFootViews.size();    }    public boolean isRefreshHeader(int position) {        return position == 0 ;    }    public int getHeadersCount() {        return mHeaderViews.size();    }    public int getFootersCount() {        return mFootViews.size();    }    @Override    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {        if (viewType == TYPE_REFRESH_HEADER) {            return new SimpleViewHolder(mHeaderViews.get(0));        } else if (viewType == TYPE_HEADER) {            return new SimpleViewHolder(mHeaderViews.get(headerPosition++ ));        } else if (viewType == TYPE_FOOTER) {            return new SimpleViewHolder((View) mFootViews.get(0));        }        return adapter.onCreateViewHolder(parent, viewType);    }    @Override    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {        if (isHeader(position)) {            return;        }        int adjPosition = position - getHeadersCount();        int adapterCount;        if (adapter != null) {            adapterCount = adapter.getItemCount();            if (adjPosition < adapterCount) {                adapter.onBindViewHolder(holder, adjPosition);            }        }    }    @Override    public int getItemCount() {        if (adapter != null) {            return getHeadersCount() + getFootersCount() + adapter.getItemCount();        } else {            return getHeadersCount() + getFootersCount();        }    }    @Override    public int getItemViewType(int position) {        if(isRefreshHeader(position)){            return TYPE_REFRESH_HEADER;        }        if (isHeader(position)) {            return TYPE_HEADER;        }        if(isFooter(position)){            return TYPE_FOOTER;        }        int adjPosition = position - getHeadersCount();        int adapterCount;        if (adapter != null) {            adapterCount = adapter.getItemCount();            if (adjPosition < adapterCount) {                return adapter.getItemViewType(adjPosition);            }        }        return TYPE_NORMAL;    }    @Override    public long getItemId(int position) {        if (adapter != null && position >= getHeadersCount()) {            int adjPosition = position - getHeadersCount();            int adapterCount = adapter.getItemCount();            if (adjPosition < adapterCount) {                return adapter.getItemId(adjPosition);            }        }        return -1;    }    @Override    public void unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver observer) {        if (adapter != null) {            adapter.unregisterAdapterDataObserver(observer);        }    }    @Override    public void registerAdapterDataObserver(RecyclerView.AdapterDataObserver observer) {        if (adapter != null) {            adapter.registerAdapterDataObserver(observer);        }    }    private class SimpleViewHolder extends RecyclerView.ViewHolder {        public SimpleViewHolder(View itemView) {            super(itemView);        }    }}

这样就可以了,但是这样的话使用RecyclerView时,设置LayoutManager只能是LinearLayoutManager。看一下效果就明白为什么了。

如何解决呢?
如果是GridLayoutManager,重写adapter的onAttachedToRecyclerView

@Overridepublic void onAttachedToRecyclerView(RecyclerView recyclerView) {    super.onAttachedToRecyclerView(recyclerView);    RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();    if(manager instanceof GridLayoutManager) {        final GridLayoutManager gridManager = ((GridLayoutManager) manager);        gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {            @Override            public int getSpanSize(int position) {               return (isHeader(position)||  isFooter(position)) ? gridManager.getSpanCount() : 1;            }        });    }   }

解释一下,我们设置了一个SpanSizeLookup,这个类是一个抽象类,而且仅有一个抽象方法getSpanSize,这个方法的返回值决定了我们每个position上的item占据的单元格个数。假如GridLayoutManager设置的每行的个数为2的话,如果当前位置是header的位置,那么该item占据2个单元格,正常情况下占据1个单元格
如果是StaggeredGridLayoutManager,重写adapter的onViewAttachedToWindow方法

public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {   super.onViewAttachedToWindow(holder);   ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();   if(lp != null           && lp instanceof StaggeredGridLayoutManager.LayoutParams           &&  (isHeader( holder.getLayoutPosition()) || isFooter( holder.getLayoutPosition())) ) {       StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;       p.setFullSpan(true);   }}        

StaggeredGridLayoutManager的处理方式是用通过LayoutParams,而且这里更简单,StaggeredGridLayoutManager.LayoutParams为我们提供了一个setFullSpan方法来设置占领全部空间
好了这下就都可以正常显示了

adapter完事了,接下来看一下实现上拉加载下拉刷新的XRecyclerView,它继承RecyclerView。我先把全局变量粘出来,如果不知道是什么意思的话就先往下看吧

private static final float DRAG_RATE = 3;private boolean hasMore = false;// 还有更多private boolean pullRefreshEnabled = true;//下拉刷新private boolean loadingMoreEnabled = true;//上拉加载private ArrayList<View> mHeaderViews = new ArrayList<>();//头部view的集合private ArrayList<BaseMoreFooter> mFootViews   = new ArrayList<>();//尾部view的集合private Adapter mAdapter;//里层的adapterprivate float mLastY = -1;private int pageSize = 10;private int visibleThreshold = 1; // list到达 最后一个item的时候 触发加载private LoadingListener mLoadingListener;private ArrowRefreshHeader mRefreshHeader;//header view

额这个类的代码有点长我就把主要的说一下吧!

在每一个构造方法调用一下init()方法

private void init(Context context) {    //添加一个header view 用于下拉刷新    ArrowRefreshHeader refreshHeader = new ArrowRefreshHeader(context);    mHeaderViews.add(0, refreshHeader);    mRefreshHeader = refreshHeader;    //添加一个footer view 用于上拉加载    LoadingMoreFooter footView = new LoadingMoreFooter(context);    footView.setLoadingMoreFooterClickCallback(this);    addFootView(footView);    mFootViews.get(0).setViewVisibility(GONE);}
@Overridepublic void setAdapter(Adapter adapter) {    mAdapter = adapter;    WrapAdapter wrapAdapter = new WrapAdapter(mHeaderViews, mFootViews, mAdapter);    mAdapter.registerAdapterDataObserver(new AdapterDataObserverImpl(wrapAdapter));    super.setAdapter(wrapAdapter);}

这个方法set了刚刚写的WrapAdapter。为传进来的adapter注册了一个观察者,如果不设置这个观察者,那么在调用adapter.notifyDataSetChanged()方法不会起任何作用,因为在setAdapter,set的是wrapAdapter而不是传进来的adapter。当然你也可以这样mRecyclerView.getAdapter().notifyDataSetChanged()

下拉加载的触发

@Overridepublic boolean onTouchEvent(MotionEvent ev) {    public boolean onTouchEvent(MotionEvent ev) {        //下拉刷新的实现        if (pullRefreshEnabled) {            if (mLastY == -1) {                mLastY = ev.getRawY();            }            switch (ev.getAction()) {                case MotionEvent.ACTION_DOWN:                    mLastY = ev.getRawY();                    break;                case MotionEvent.ACTION_MOVE:                    final float deltaY = ev.getRawY() - mLastY;                    mLastY = ev.getRawY();                    if (isOnTop()) {                        mRefreshHeader.onMove(deltaY / DRAG_RATE);                    }                    break;                default:                    mLastY = -1;                    if (isOnTop()) {                        if (mRefreshHeader.releaseAction()) {                            if (mLoadingListener != null) {                                mLoadingListener.onRefresh();                                hasMore = false;                            }                        }                    }                    break;            }        }        return super.onTouchEvent(ev);    }}

一个onTouchEvent搞定,在拖动recyclerViewd的时候,改变headerView的高度,在松手的时候mRefreshHeader.releaseAction()会判断当前headerView的是否全部显示出来了,返回true的话就调用下拉刷新的回调方法

在说一下上拉加载

@Overridepublic void onScrollStateChanged(int state) {    super.onScrollStateChanged(state);    //上拉加载的实现    if (loadingMoreEnabled) {        BaseMoreFooter footView = mFootViews.get(0);        //如果回调的监听不等与null,并且footView没有在加载中        if (mLoadingListener != null && !footView.isLoading()) {            LayoutManager layoutManager = getLayoutManager();            int lastVisibleItemPosition = getLastVisibleItemPosition(layoutManager);            //item大于0,并且到最后一个item,并且还有更多数据,并且没有在下拉刷新中,            // 并且LoadingMoreFooter的状态不是click加载的状态            if (layoutManager.getChildCount() > 0                    && lastVisibleItemPosition >= layoutManager.getItemCount() - visibleThreshold                    && !hasMore                    && !isRefreshing()                    && !footView.isClickLoadMore()) {                footView.loading();                mLoadingListener.onLoadMore();            }        }    }    }

上拉加载核心方法是getLastVisibleItemPosition

/** * 返回显示的最后一个view的position */public int getLastVisibleItemPosition(LayoutManager layoutManager){    int lastVisibleItemPosition;    if (layoutManager instanceof GridLayoutManager) {        lastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();    } else if (layoutManager instanceof StaggeredGridLayoutManager) {        int[] into = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()];        ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(into);        lastVisibleItemPosition = findMax(into);    } else {        lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();    }    return lastVisibleItemPosition;}private int findMax(int[] lastPositions) {    int max = lastPositions[0];    for (int value : lastPositions) {        if (value > max) {            max = value;        }    }    return max;}

接下来看一下如果使用

private MyAdapter mAdapter;private ArrayList<String> listData;private int refreshTime = 0;private int times = 0;@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_recyclerview);    mRecyclerView = (XRecyclerView)this.findViewById(R.id.recyclerview);    LinearLayoutManager layoutManager = new LinearLayoutManager(this);    layoutManager.setOrientation(LinearLayoutManager.VERTICAL);    mRecyclerView.setLayoutManager(layoutManager);    mRecyclerView.setArrowImageView(R.drawable.iconfont_downgrey);    View header =   LayoutInflater.from(this).inflate(R.layout.recyclerview_header, (ViewGroup)findViewById(android.R.id.content),false);    mRecyclerView.addHeaderView(header);    mRecyclerView.setLoadingListener(new XRecyclerView.LoadingListener() {        @Override        public void onRefresh() {            refreshTime ++;            times = 0;            new Handler().postDelayed(new Runnable(){                public void run() {                    listData.clear();                    for(int i = 0; i < 5 ;i++){                        listData.add("item" + i + "after " + refreshTime + " times of refresh");                    }                    mAdapter.notifyDataSetChanged();                    mRecyclerView.refreshComplete();                }            }, 1000);            //refresh data here        }        @Override        public void onLoadMore() {            if(times < 2){                new Handler().postDelayed(new Runnable(){                    public void run() {                        mRecyclerView.stopLoadMore();                        for(int i = 0; i < 15 ;i++){                            listData.add("item" + (i + listData.size()) );                        }                        mAdapter.notifyDataSetChanged();                        mRecyclerView.restoreFooter();                    }                }, 1000);            } else {                new Handler().postDelayed(new Runnable() {                    public void run() {                        mAdapter.notifyDataSetChanged();                        mRecyclerView.noMoreLoading();                    }                }, 1000);            }            times ++;        }    });    listData = new  ArrayList<String>();    for(int i = 0; i < 5 ;i++){        listData.add("item" + (i + listData.size()) );    }    mAdapter = new MyAdapter(listData);    mRecyclerView.setAdapter(mAdapter);    mRecyclerView.clickLoadMore();}

LoadingListener有上拉和下拉的回调,回调里面模仿了一下网络请求。在最后一行我调用的mRecyclerView.clickLoadMore()方法,这个方法是让recyclerView的footerView显示点击加载,因为有这种情况:比如一页的数据不满一屏或网络请求失败都可以显示这个。
下拉刷新完会调用mRecyclerView.refreshComplete(),这个方法没什么可说的,就是刷新完成
上拉加载完可以调用两个方法mRecyclerView.restoreFooter()和mRecyclerView.noMoreLoading(),noMoreLoading()就是显示没有更多了,然后怎么拖动recyclerView都不会进行上拉加载了。restoreFooter()方法看一下代码吧

/** * 重置footer.如果当前 itemCount > {@link #pageSize} 调用 {@link #stopLoadMore()} 否则调用 {@link #clickLoadMore()} */public void restoreFooter(){    if (loadingMoreEnabled){        int itemCount = mAdapter.getItemCount();        if (itemCount >= pageSize){            stopLoadMore();        }else {            clickLoadMore();        }    }}

这个一开始我本来是想计算所有item的高度,如果大于一屏就让他自动加载,否则就显示点击加载。但是后来想想,每调用一下这个方法都要for循环一次,不太好,所以就简单一点如果所有item(不包含headerView和footerView)大于pageSize就让他自动加载。

如果有任何意见或建议请@我,我会及时更改。群号–284568173,我叫键盘

github地址

0 0
原创粉丝点击