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的主要原理实现:
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分,如果没有积分的,请留下。
footerView的实现方式也是如出一辙。至此,我们主要原理已经分析了,更详细的请看源码。
本来想免费分享的,但是这边的资源分没有0的,只能设置最低的1分,如果没有积分的,请留下。
源码的地址:http://download.csdn.net/download/huadashihuangzhe/9930784
阅读全文
0 0
- XRecyclerview的XListView式的下拉刷新和上拉加载(改自XListView,修改比率低于30%,效果很好)
- XlistView的上拉刷新下拉加载
- Xlistview的上拉加载,下拉刷新
- XListView的下拉刷新,上拉加载
- XlistView的上拉加载下拉刷新
- 安卓中Xlistview的上拉加载和下拉刷新
- 简易的XListView下拉加载和上拉刷新
- XListView的上拉加载和下拉刷新
- XListView 的使用 (上拉加载,下拉刷新)
- XListView上拉加载和下拉刷新
- xlistview上拉刷新和下拉加载
- 上拉加载和下拉刷新(XListView)
- xlistview下拉刷新上拉加载的适配器
- XListView的上拉及下拉刷新
- 支持下拉刷新和上划加载更多的自定义RecyclerView(仿XListView效果)
- XListView的下滑刷新上拉加载
- XlistView的上拉刷新下拉加载 和Fragment 和无限轮播
- 国庆技术博客之XListView的上拉刷新和下拉加载
- JavaScript对象继承
- 2222
- Hibernate基本知识概括
- 创建并运行一个基本的Python测试程序(自己的看法修改)
- POJ1904:King's Quest(强连通 & 二分图)
- XRecyclerview的XListView式的下拉刷新和上拉加载(改自XListView,修改比率低于30%,效果很好)
- mysql安装和Django 项目hello world创建
- (14)union
- Android 常见导致OOM的主要原因
- 关于GC的一些总结
- Unity 手势识别插件
- nexus start的时候报 wrapper | The nexus service was launched, but failed to start 2014-08-03 08:08 5182人
- Nginx根目录修改失效问题
- Spring框架的优点