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

上拉加载下拉刷新的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

@Override
public 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;//里层的adapter

private 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);
}
@Override
public 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()

下拉加载的触发

@Override
public 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的话就调用下拉刷新的回调方法

在说一下上拉加载

@Override
public 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;
@Override
protected 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地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值