XRecyclerview的XListView式的下拉刷新和上拉加载(改自XListView,修改比率低于30%,效果很好)

来源:互联网 发布:千里走单骑知乎 编辑:程序博客网 时间:2024/06/05 10:03
       自从RecyclerView出现之后,由于其灵活性受到越来越多人的青睐,其主要的作用就是显示类型一致的大量数据,这跟之前的ListView、GridView的作用是一致的。当数据量很大时,此时需要实现分页显示。针对ListView,有人开发了XListView来实现下拉刷新和上拉加载框架,效果不错,很受人欢迎。那么,RecyclerView是如何实现刷新功能呢?RecyclerView与SwipeRefreshLayout组合,可以实现下拉刷新,这是谷歌支持的方法,但是上拉加载谷歌就没有对应的控件支持。通过对XListView的原理分析之后,我觉得可以完全按照XListView的原理来实现RecyclerView的下拉刷新和上拉加载,而且效果还是完全一致的。
       我们先来看一下效果图。

                  下拉刷新                                              上拉加载更多                                         点击加载更多
       首先,我们要实现上图的效果,前提要先理解XListView的原理。在这里我们感谢赵凯强博主给的原理分析:http://blog.csdn.net/zhaokaiqiang1992/article/details/42392731。在这里我们只对XListView的原理进行简单的概括。
XListView的主要原理实现:
1、XListView主要包含了三部分:XListView、XListViewFooter、XListViewHeader。
2、XListViewHeader包含了正常、准备刷新、正在加载三种状态。手在屏幕上滑动时根据手滑动的距离通过setVisiableHeight()方法设置Header的布局高度属性来达到拉伸和收缩的效果。
3、XListViewFooter跟XListView的差不多,只不过它是设置BottomMargin来实现而已。
4、XListViewHeader添加到头部,XlistViewFooter添加到尾部,总的item数目为内容主体的数目加上2(例如:原来item为20条,加上Header和Footer之后就是22)。整个过程的动画都是在这两个布局上改变的。
5、手在屏幕上滑动时,根据手指的移动距离,设置setVisiableHeight()改变Header的下拉变化,设置BottomMargin改变Footer的上拉效果。当手机离开屏幕时,调用Scroller.startScroll()来实现布局的回弹,同时调用更新或者加载更多的方法。

      为了使RecyclerView具有XListView的一样的效果,同时减少工作量,我们在XListView的基础上修改,修改后的RecyclerView命名为XRecyclerView。
首先我们来确定要修改的部分:
1、不修改XListViewHeader和XListViewFooter的代码(源码100%原用),只将文件名修改成XRecyclerViewHeader和XRecyclerViewFooter。
2、重组RecyclerView的适配器,添加头尾布局到主体布局上。
3、根据需求处理滑动事件,使之更适合开发的需要。
4、在XRecyclerView中,唯独只有WrapAdapter是新增的,其他的部分大部分是保留XListView代码,同时只是修改个别逻辑。

添加XRecyclerViewHeader和XRecyclerViewFooter到主体布局
       我们创建XRecyclerView类,继承RecyclerView。由于ListView与RecyclerView的适配机制不一样,我们不能按照XListView的方法添加headerView和footerView,所以我们通过重组适配器Adapter来实现XRecyclerView的头尾布局的添加。
       同XListView一样,在XRecyclerView中的initView(),方法中实例化XRecyclerViewHeader和XRecyclerViewFooter,并获取Header的高度。代码如下:
private void initView(Context context) {    scroller = new Scroller(context, new DecelerateInterpolator());    //实例化Header和Footer    headerView = new XRecyclerViewHeader(context);    footerView = new XRecyclerViewFooter(context);    //获取Header的高度    headerViewContent = (RelativeLayout) headerView.findViewById(R.id.xlistview_header_content);    headerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {        @SuppressWarnings("deprecation")        @Override        public void onGlobalLayout() {            headerHeight = headerViewContent.getHeight();            getViewTreeObserver().removeGlobalOnLayoutListener(this);        }    });}
       重写XRecyclerView中的setAdapter(Adapter adapter)方法,并在方法中重构适配器,重构适配器的类名为WrapAdapter,同时把实例化的headerView和footerView传入适配器WrapAdapter。代码如下:
@Overridepublic void setAdapter(Adapter adapter) {    mWrapAdapter = new WrapAdapter(this,headerView, footerView, adapter);    super.setAdapter(mWrapAdapter);}
        先看一下WraptAdapter类。
public class WrapAdapter extends RecyclerView.Adapter {    private static final int TYPE_REFRESH_HEADER = -5; //添加刷新头    private static final int TYPE_NORMAL = 0;    private static final int TYPE_FOOTER = -3;//添加上拉加载布局    private RecyclerView.Adapter adapter;    private XRecyclerViewHeader mHeaderViews;    private XRecyclerViewFooter mFootView;    private XRecyclerView recyclerView;    public WrapAdapter(XRecyclerView recyclerView,XRecyclerViewHeader headerViews, XRecyclerViewFooter footView, RecyclerView.Adapter adapter) {        this.adapter = adapter;        this.mHeaderViews = headerViews;        this.mFootView = footView;        this.recyclerView = recyclerView;    }    @Override    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {        if (viewType == TYPE_REFRESH_HEADER) {            return new SimpleViewHolder(mHeaderViews);        } else if (viewType == TYPE_FOOTER) {            return new SimpleViewHolder(mFootView);        }        return adapter.onCreateViewHolder(parent, viewType);    }    @Override    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {        if (isHeader(position)) {            return;        }        int adjPosition = position - 1;        int adapterCount;        if (adapter != null) {            adapterCount = adapter.getItemCount();            if (adjPosition < adapterCount) {                adapter.onBindViewHolder(holder, adjPosition);                return;            }        }    }    @Override    public int getItemCount() {        int count = 2;        if (adapter != null) {            count = 2 + adapter.getItemCount();        }        //判断是都为列表数为零.则隐藏头部和尾部的item        if(count == 2){            mHeaderViews.setVisibility(View.GONE);            mFootView.setVisibility(View.GONE);        } else {            mHeaderViews.setVisibility(View.VISIBLE);            //如果设置了加载更多为不可为时,隐藏尾部            if(recyclerView.isEnableLoadMore()){                mFootView.setVisibility(View.VISIBLE);            } else {                //如果设置了不能下拉刷新,则itemCount减少1                count--;                mFootView.setVisibility(View.GONE);            }        }        return count;    }    @Override    public int getItemViewType(int position) {        //如果position是0,返回刷新的标志位        if (position == 0) {            return TYPE_REFRESH_HEADER;        }        //如果position是footerView的位置时,返回加载更多的标志位        if (isFooter(position)) {            return TYPE_FOOTER;        }        //否则返回正常的标志位        int adjPosition = position - 1;        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 >= 1) {            int adjPosition = position - 1;            int adapterCount = adapter.getItemCount();            if (adjPosition < adapterCount) {                return adapter.getItemId(adjPosition);            }        }        return -1;    }    private class SimpleViewHolder extends RecyclerView.ViewHolder {        public SimpleViewHolder(View itemView) {            super(itemView);        }    }    public boolean isHeader(int position) {        return position >= 0 && position < 1;    }    public boolean isFooter(int position) {        return position < getItemCount() && position >= getItemCount() - 1 && recyclerView.isEnableLoadMore();    }}
       接下来我们对WraptAdapter进行分析。
getItemCount()方法:
@Overridepublic int getItemCount() {    int count = 2;    if (adapter != null) {        count = 2 + adapter.getItemCount();    }    //判断是都为列表数为零.则隐藏头部和尾部的item    if(count == 2){        mHeaderViews.setVisibility(View.GONE);        mFootView.setVisibility(View.GONE);    } else {        mHeaderViews.setVisibility(View.VISIBLE);        //如果设置了加载更多为不可为时,隐藏尾部        if(recyclerView.isEnableLoadMore()){            mFootView.setVisibility(View.VISIBLE);        } else {            //如果设置了不能下拉刷新,则itemCount减少1            count--;            mFootView.setVisibility(View.GONE);        }    }    return count;}
       通过代码我们发现,item的总数目是itemCount加上2(即count+2)。当count为2的时候,此时只是包含了头部和尾部的两个item,是没有主体数据显示,此时我们隐藏掉头部和尾部。如果数据count大于2时,是有主体数据显示,此时我们要判断我们的XRecyclerView是否设置上拉加载更多,如果没有设置,则隐藏footerView,count也要在count+2的基础上减去(即count + 1),此设置是为了适用于不需要上拉刷新的使用。
getItemViewType()方法:
@Overridepublic int getItemViewType(int position) {    //如果position是0,返回刷新的标志位    if (position == 0) {        return TYPE_REFRESH_HEADER;    }    //如果position是footerView的位置时,返回加载更多的标志位    if (isFooter(position)) {        return TYPE_FOOTER;    }    //否则返回正常的标志位    int adjPosition = position - 1;    int adapterCount;    if (adapter != null) {        adapterCount = adapter.getItemCount();        if (adjPosition < adapterCount) {            return adapter.getItemViewType(adjPosition);        }    }    return TYPE_NORMAL;}
       在这个方法,在header和footer位置处返回对应的标识,用于在onCreateViewHolder(ViewGroun parent,int viewType)识别,然后加载对应的布局,至此就将头尾布局添加到XRecyclerView中
       isHeader()和isFooter()两个方法是判断当前的item是否属于头部或者尾部的位置,尾部还特别判断了是否有开启加载更多的条件。

XRecyclerView上下滑动的动画实现
       根据赵凯强博主的讲述中,我们知道XListView的上下滑动的动画的效果是根据手势的滑动距离来设定header布局的高度或者footer布局是与底部的距离来实现的,所以XRecyclerView的实现方法也是一样。在这里我们直接重写XRecyclerView的onTouchEvent(MotionEvent ev)方法。先贴上代码。
@Overridepublic boolean onTouchEvent(MotionEvent ev) {    LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();    int totalItemCount = getLayoutManager().getItemCount();    //如果列表条数为零(放弃头尾两个item),则不处理    if(totalItemCount == 2){        return false;    }    switch (ev.getAction()) {        case MotionEvent.ACTION_DOWN:            isEvent = true;            // 记录按下的坐标            lastY = ev.getRawY();            break;        case MotionEvent.ACTION_MOVE:            // 计算移动距离            float deltaY = ev.getRawY() - lastY;            //这里的处理是为了防止与item的点击事件其冲突            if(!isEvent){                isEvent = true;                deltaY = 0;            }            lastY = ev.getRawY();            //有一个在加载数据的时候,另外一个不加载数据            if(!isRefreashing && !isLoadingMore) {                // 是第一项并且标题已经显示或者是在下拉                if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0 && (headerView.getVisiableHeight() > 0 || deltaY > 0) && enableRefresh) {                    updateHeaderHeight(deltaY / OFFSET_RADIO);                } else if (layoutManager.findLastCompletelyVisibleItemPosition() == totalItemCount - 1 && (footerView.getBottomMargin() > 0 || deltaY < 0) && enableLoadMore) {                    updateFooterHeight(-deltaY / OFFSET_RADIO);                }            }            break;        case MotionEvent.ACTION_UP:            isEvent = false;            if (layoutManager.findFirstVisibleItemPosition() == 0) {                if (enableRefresh && headerView.getVisiableHeight() > headerHeight) {                    isRefreashing = true;                    headerView.setState(XRecyclerViewHeader.STATE_REFRESHING);                    if (mXRecyclerViewListener != null) {                        mXRecyclerViewListener.onRefresh();                    }                }                resetHeaderHeight();            } else if (layoutManager.findLastVisibleItemPosition() == totalItemCount - 1) {                if (enableLoadMore && footerView.getBottomMargin() > PULL_LOAD_MORE_DELTA) {                    startLoadMore();                }                resetFooterHeight();            }            break;    }    return super.onTouchEvent(ev);}
       首先我们判断整个列表是否有数据,有数据才会对列表数据处理。当我们的手指点击到屏幕的时候,先记下当前点击的Y坐标,手指滑动的时候,记下滑动的距离,此距离是当前滑动触发的位置减去上一次触发的位置的距离差。if(isRefreshing && !isLoadingMore)是处理如果正在刷新操作或者加载更多数据的请求时,禁止触发加载数据或者刷新数据事件。
       在满足无触发刷新或者加载数据的情况下,根据layoutManager.findFirstCompletelyVisibleItemPosition(),判断第一个完全可见的item是不是Position为0(即为头部布局),同时开启下拉刷新的功能时,调用updateHeaderHeight(deltay/OFFSET_RADIO)方法改变头部的得高度到达动态下来的效果。
private void updateHeaderHeight(float delta) {    headerView.setVisiableHeight((int) delta + headerView.getVisiableHeight());    // 未处于刷新状态,更新箭头    if (enableRefresh && !isRefreashing) {        if (headerView.getVisiableHeight() > headerHeight) {            headerView.setState(XRecyclerViewHeader.STATE_READY);        } else {            headerView.setState(XRecyclerViewHeader.STATE_NORMAL);        }    }}
       从该方法我们可以看到,如果下拉滑动到footerView高度的时候,会改变headerView的状态,变成刷新状态。
       相同的,上拉的原理是类似的。但是有个注意点是我们这边用是findFirstCompletelyVisibleItemPosition()方法而不是findFirstVisibleItemPosition()方法,如果用的是后者的方法,会出现上拉或者下拉的时候,出现布局悬停的情况。
       当手指放开的时候,先从headerView分析。根据滑动的距离和触发刷新条件来判断,如果条件不满足,则直接调用resetHeaderHeight()方法,实现headerView的回弹,当满足刷新的条件时候,则回弹到headerView触顶,同时触发刷新数据方法,数据刷新结束则回弹隐藏。
回弹代码:
private void resetHeaderHeight() {    // 当前的可见高度    int height = headerView.getVisiableHeight();    // 如果正在刷新并且高度没有完全展示    if ((isRefreashing && height <= headerHeight) || (height == 0)) {        return;    }    // 默认会回滚到header的位置    int finalHeight = 0;    // 如果是正在刷新状态,则回滚到header的高度    if (isRefreashing && height > headerHeight) {        finalHeight = headerHeight;    }    mScrollBack = SCROLLBACK_HEADER;    // 回滚到指定位置    scroller.startScroll(0, height, 0, finalHeight - height,            SCROLL_DURATION);    invalidate();}
       从代码上可以看出:当手指放开时,会根据刷新状态赋值给finalHeight,如果如果条件满足,则finalHeight设置为headerView的高度,否则设置为0,然后设置scroller的参数,并在computeScroll获取的currY()位置,然后设置headerView的高度,动态实现回弹。
在整个的手势滑动中,我们这边有一个标志位isEvent,该表位为是为了防止在手点击到屏幕上时,item的点击事件与滑动事件冲突。
       footerView的实现方式也是如出一辙。至此,我们主要原理已经分析了,更详细的请看源码。
       本来想免费分享的,但是这边的资源分没有0的,只能设置最低的1分,如果没有积分的,请留下。
       源码的地址:http://download.csdn.net/download/huadashihuangzhe/9930784

阅读全文
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 销售方遗失发票怎么办 增值税发票发票联丢失怎么办 苹果购买发票丢失怎么办 空白增值税发票发票丢失怎么办 网购发票 领购簿怎么办 购物发票丢了怎么办 饭店客人买单要少钱怎么办 发票备注栏写错怎么办 卖房子发票丢失怎么办 发票二维码蓝票怎么办 车祸伤者出院怎么办 微信付款失败怎么办 增值税电子发票没打税号怎么办 买假出租车发票怎么办 纳税号错了怎么办 发票抬头写错怎么办 增值税发票打错顺序怎么办 发票打错了怎么办 电子发票错了怎么办 税率开高了怎么办 增值发票折叠了怎么办 播放器格式不对怎么办 发票弄上油了怎么办 快手视频快进了怎么办? 三星手机没声音怎么办 mp4不是标准格式怎么办 苹果七充电慢怎么办 迅捷转换器转换失败怎么办 爱奇艺摄像头拒绝扫描怎么办 电脑有些格式打不开怎么办 cdr转pdf打不开怎么办 pr cc2018很卡怎么办 职称转换后时间怎么办 文件更改或移动怎么办 太阳镜镜片花了怎么办 太阳镜镜片划了怎么办 键盘上的下横杠怎么办 小宝宝突然怕水怎么办 夏季半袖领口大怎么办 骑电动车怕上坡怎么办 tp路由器离线了怎么办