RecyclerView下拉刷新和载入很多其它
之前一直写的是ListVIew下拉刷新,可是好多朋友都说要RecycleView的下拉刷新和滑动载入。事实上,这个原理都是几乎相同。抽出时间,我就写了下RecycleView的下拉刷新和滑动载入很多其它。因此,这才写到博客里,记录一下。
在大家阅读这篇博客前。大家须要了解的知识
事件是以ACTION_DOWN開始到ACTION_UP货ACTION_CANCEL结束的一个序列,期间事件分发,能够通过onInterceptTouchEvent方法和dispatchTouchEvent进行事件的阻止和消费。
比方怎样加入一个Decoration.
当RecyclerView滑动到了最顶部,则能够触发下拉事件;当RecyclerView滑动到了底部,则能够触发滑动载入很多其它的事件。然后在通过事件分发。进行滑动事件的处理。
package com.mjc.recyclerviewdemo.refresh; import android.content.Context;
import android.graphics.Color;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.widget.LinearLayout;
import android.widget.Scroller; import com.mjc.recyclerviewdemo.CustomItemDecoration; /**
* Created by mjc on 2016/3/11.
*/
public class PullToRefreshRecycleView extends LinearLayout { //头部
private IHeaderView mHeaderView;
private int mHeaderHeight; //尾部
private CustomFooterView mFooterView;
private int mFooterHeight; //阻尼系数,越大,阻力越大
public static final float RATIO = 0.5f;
//当前是否阻止事件
private boolean isIntercept;
//是否正在刷新
private boolean isRefreshing;
//滑动类
private Scroller mScroller;
//刷新的View
private RecyclerView mRefreshView; private Context mContext; private int mMaxScrollHeight; private boolean isFirst = true; public static final int NORMAL = 0;
public static final int PULL_TO_REFRESH = 1;
public static final int RELEASE_TO_REFRESH = 2;
public static final int REFRESING = 3;
private int mCurrentState;
private int mTouchSlop; private OnRefreshListener listener; private boolean isPullDownMotion;
private int lastVisible; public PullToRefreshRecycleView(Context context) {
super(context);
init(context);
} public PullToRefreshRecycleView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
} private void init(Context context) {
mContext = context;
this.setOrientation(VERTICAL);
mRefreshView = getRefreshView();
mRefreshView.setBackgroundColor(Color.WHITE);
LayoutParams listParams = new LayoutParams(-1, -1);
mRefreshView.setLayoutParams(listParams);
addView(mRefreshView);
//加入HeaderView
mHeaderView = new CustomHeaderView(context);
LayoutParams params = new LayoutParams(-1, -2);
mHeaderView.setLayoutParams(params);
addView(mHeaderView, 0);
//加入FooterView
mFooterView = new CustomFooterView(context);
LayoutParams fParams = new LayoutParams(-1, 200);
mFooterView.setLayoutParams(fParams);
addView(mFooterView, -1);
//弹性滑动实现
mScroller = new Scroller(context);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
} @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//第一次获取相关參数,并隐藏HeaderView,FooterView
if (isFirst) {
mHeaderHeight = mHeaderView.getMeasuredHeight();
mMaxScrollHeight = mHeaderHeight * 3;
resetHeaderLayout(-mHeaderHeight); mFooterHeight = mFooterView.getMeasuredHeight();
resetFooterLayout(-mFooterHeight);
Log.v("@mHeaderHeight", mHeaderHeight + "");
Log.v("@mFooterHeight", mFooterHeight + "");
isFirst = false;
}
} private void resetHeaderLayout(int offset) {
LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();
params.topMargin = offset;
mHeaderView.requestLayout();
} private void resetFooterLayout(int offset) {
LayoutParams params = (LayoutParams) mFooterView.getLayoutParams();
params.bottomMargin = offset;
mFooterView.requestLayout();
} //按下时的位置,当事件被阻止时,第一次ActionDown事件,onTouchEvent无法获取这个位置
//须要在onInterceptTouchEvent获取
private float downY; @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//假设当前是正在刷新而且是下拉状态,则当前视图处理事件
if (isRefreshing && mScrollY < 0) {
return true;
}
//假设当前是刷新状态,而且处于上拉状态,则视图不可进入下拉状态
if (mScrollY >= 0 && isRefreshing)
return false;
boolean isIntercept = false;
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
downY = ev.getY();
break;
case MotionEvent.ACTION_MOVE: //假设达到了滑动条件
if (Math.abs(ev.getY() - downY) >= mTouchSlop) {
if (ev.getY() - downY > 0) {//下拉
isIntercept = isEnablePullDown();
if (isIntercept)//设置下拉还是上滑的状态,true表示下拉动作
isPullDownMotion = true; } else {//上滑
isIntercept = isEnableLoadMore();
if (isIntercept)//false表示上滑状态
isPullDownMotion = false;
}
} else {
isIntercept = false;
} break;
case MotionEvent.ACTION_CANCEL:
//假设返回true,子视图假设包括点击事件。则无法进行处理
isIntercept = false;
break;
case MotionEvent.ACTION_UP:
isIntercept = false;
break;
}
return isIntercept;
} //记录当前滑动的位置
private int mScrollY; @Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
//第一次推断时,downY仅仅能从intercept中获取,之后从这里获取
downY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
float dY = event.getY() - downY;
if (isPullDownMotion)//下拉
doPullDownMoveEvent(dY);
else {//自己主动载入很多其它
doLoadMoreEvent(dY);
}
break;
case MotionEvent.ACTION_UP: if (isPullDownMotion) {
//处理下拉结果
doPullDownResult();
} else {
//处理滑动载入很多其它结果
doLoadMoreResult();
} break;
case MotionEvent.ACTION_CANCEL:
//同ACTION_UP
if (isPullDownMotion) {
doPullDownResult();
} else {
doLoadMoreResult(); } break;
}
return true;
} /**
* 处理载入很多其它
*/
private void doLoadMoreResult() {
//手指松开时,假设FooterView,没有全然滑动出来。自己主动滑动出来
scrollTo(0, mFooterHeight);
mScrollY = getScrollY();
if (!isRefreshing) {
isRefreshing = true;
if (listener != null)
listener.onLoadMore();
} } /**
* 载入很多其它完毕后调用
*/
public void completeLoadMore() {
scrollTo(0, 0);
mScrollY = 0;
isRefreshing = false;
LinearLayoutManager manager = (LinearLayoutManager) mRefreshView.getLayoutManager();
int count = manager.getItemCount();
if (count > lastVisible + 1)//载入了很多其它数据
mRefreshView.scrollToPosition(lastVisible + 1);
} //处理载入很多其它
private void doLoadMoreEvent(float y) {
int scrollY = (int) (mScrollY - y);
if (scrollY < 0) {
scrollY = 0;
} if (scrollY > mFooterHeight) {
scrollY = mFooterHeight;
}
Log.v("@scrollY", scrollY + "");
scrollTo(0, scrollY);
} /**
* 处理释放后的操作
*/
private void doPullDownResult() {
//先获取如今滑动到的位置
mScrollY = getScrollY();
switch (mCurrentState) {
case PULL_TO_REFRESH:
mCurrentState = NORMAL;
mHeaderView.onNormal();
smoothScrollTo(0);
break;
case RELEASE_TO_REFRESH:
//松开时,假设是释放刷新。则開始进行刷新动作
if (!isRefreshing) {
//滑动到指定位置
smoothScrollTo(-mHeaderHeight); mHeaderView.onRefreshing();
isRefreshing = true;
if (listener != null) {
//运行刷新回调
listener.onPullDownRefresh(); }
//假设当前滑动位置太靠下,则滑动到指定刷新位置
} else if (mScrollY < -mHeaderHeight) {
smoothScrollTo(-mHeaderHeight);
}
break; }
} /**
* 获取到数据后,调用
*/
public void completeRefresh() {
isRefreshing = false;
mCurrentState = NORMAL;
smoothScrollTo(0);
} private void doPullDownMoveEvent(float y) {
int scrollY = (int) (mScrollY - y * RATIO);
if (scrollY > 0) {
scrollY = 0;
}
if (scrollY < -mMaxScrollHeight) {
scrollY = -mMaxScrollHeight;
}
scrollTo(0, scrollY);
if (isRefreshing)
return;
//设置对应的状态
if (scrollY == 0) {
mCurrentState = NORMAL;
mHeaderView.onNormal();
} else if (scrollY <= 0 && scrollY > -mHeaderHeight) {
mCurrentState = PULL_TO_REFRESH;
mHeaderView.onPullToRefresh(Math.abs(scrollY));
} else if (scrollY <= -mHeaderHeight && scrollY >= -mMaxScrollHeight) {
mCurrentState = RELEASE_TO_REFRESH;
mHeaderView.onReleaseToRefresh(Math.abs(scrollY));
}
} /**
* 从当前位置滑动到指定位置
* @param y 滑动到的位置
*/
private void smoothScrollTo(int y) {
int dY = y - mScrollY;
mScroller.startScroll(0, mScrollY, 0, dY, 500);
invalidate(); } private RecyclerView getRefreshView() {
mRefreshView = new RecyclerView(mContext);
mRefreshView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false));
mRefreshView.addItemDecoration(new CustomItemDecoration(mContext, CustomItemDecoration.VERTICAL_LIST));
return mRefreshView;
} public void setAdapter(RecyclerView.Adapter adapter) {
mRefreshView.setAdapter(adapter);
} /**
* 推断列表是否在最顶端
* @return
*/
private boolean isEnablePullDown() {
LinearLayoutManager manager = (LinearLayoutManager) mRefreshView.getLayoutManager();
int firstVisible = manager.findFirstVisibleItemPosition();
//当前还没有数据,能够进行下拉
if(manager.getItemCount()==0)
return true;
return firstVisible == 0 && manager.getChildAt(0).getTop() == 0;
} /**
* 推断列表是否滑动到了最底部
* @return
*/
private boolean isEnableLoadMore() {
LinearLayoutManager manager = (LinearLayoutManager) mRefreshView.getLayoutManager();
lastVisible = manager.findLastVisibleItemPosition();
int totalCount = manager.getItemCount();
//假设没有数据,仅仅能下拉刷新
if (totalCount == 0)
return false;
int bottom = manager.findViewByPosition(lastVisible).getBottom();
int decorHeight = manager.getBottomDecorationHeight(mRefreshView.getChildAt(0));
//最后一个child的底部位置在当前视图的上面
return totalCount == lastVisible + 1 && bottom + decorHeight <= getMeasuredHeight();
} @Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(0, mScroller.getCurrY());
mScrollY = mScroller.getCurrY();
invalidate();
}
} /**
* 设置Footer的内容
*/
public void setFooterViewState(boolean hasMoreData){
if(hasMoreData){
mFooterView.onRefreshing();
}else{
mFooterView.onNoData();
}
}
public interface OnRefreshListener {
void onPullDownRefresh(); void onLoadMore();
} public void setOnRefreshListener(OnRefreshListener listener) { this.listener = listener;
}
}
private void init(Context context) {
mContext = context;
this.setOrientation(VERTICAL);
mRefreshView = getRefreshView();
mRefreshView.setBackgroundColor(Color.WHITE);
LayoutParams listParams = new LayoutParams(-1, -1);
mRefreshView.setLayoutParams(listParams);
addView(mRefreshView);
//加入HeaderView
mHeaderView = new CustomHeaderView(context);
LayoutParams params = new LayoutParams(-1, -2);
mHeaderView.setLayoutParams(params);
addView(mHeaderView, 0);
//加入FooterView
mFooterView = new CustomFooterView(context);
LayoutParams fParams = new LayoutParams(-1, 200);
mFooterView.setLayoutParams(fParams);
addView(mFooterView, -1);
//弹性滑动实现
mScroller = new Scroller(context);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//第一次获取相关參数,并隐藏HeaderView。FooterView
if (isFirst) {
mHeaderHeight = mHeaderView.getMeasuredHeight();
mMaxScrollHeight = mHeaderHeight * 3;
resetHeaderLayout(-mHeaderHeight); mFooterHeight = mFooterView.getMeasuredHeight();
resetFooterLayout(-mFooterHeight);
Log.v("@mHeaderHeight", mHeaderHeight + "");
Log.v("@mFooterHeight", mFooterHeight + "");
isFirst = false;
}
}
在这种方法里面,我们获取了HeaderView,FooterView的測量高。而且,我们设置了HeaderView。FooterView的margin值,隐藏了头部和尾部。
//按下时的位置,当事件被阻止时。第一次ActionDown事件,onTouchEvent无法获取这个位置
//须要在onInterceptTouchEvent获取
private float downY; @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//假设当前是正在刷新而且是下拉状态,则当前视图处理事件
if (isRefreshing && mScrollY < 0) {
return true;
}
//假设当前是刷新状态。而且处于上拉状态。则视图不可进入下拉状态
if (mScrollY >= 0 && isRefreshing)
return false;
boolean isIntercept = false;
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
downY = ev.getY();
break;
case MotionEvent.ACTION_MOVE: //假设达到了滑动条件
if (Math.abs(ev.getY() - downY) >= mTouchSlop) {
if (ev.getY() - downY > 0) {//下拉
isIntercept = isEnablePullDown();
if (isIntercept)//设置下拉还是上滑的状态,true表示下拉动作
isPullDownMotion = true; } else {//上滑
isIntercept = isEnableLoadMore();
if (isIntercept)//false表示上滑状态
isPullDownMotion = false;
}
} else {
isIntercept = false;
} break;
case MotionEvent.ACTION_CANCEL:
//假设返回true,子视图假设包括点击事件,则无法进行处理
isIntercept = false;
break;
case MotionEvent.ACTION_UP:
isIntercept = false;
break;
}
return isIntercept;
}
//假设当前是正在刷新而且是下拉状态,则当前视图处理事件
if (isRefreshing && mScrollY < 0) {
return true;
}
//假设当前是刷新状态,而且处于上拉状态。则视图不可进入下拉状态
if (mScrollY >= 0 && isRefreshing)
return false;
假设当前为下拉而且在刷新状态,则返回true,表示拦截事件,RecyclerView不可滑动。假设当前是滑动载入很多其它。而且刷新状态。则不拦截,由于后面我想在滑动载入很多其它时,RecyclerView能够滑动。 截止后面。在ACTION_DOWN事件中,我们记录下按下的y轴位置。然后是ACTION_MOVE;
//假设达到了滑动条件
if (Math.abs(ev.getY() - downY) >= mTouchSlop) {
if (ev.getY() - downY > 0) {//下拉
isIntercept = isEnablePullDown();
if (isIntercept)//设置下拉还是上滑的状态,true表示下拉动作
isPullDownMotion = true; } else {//上滑
isIntercept = isEnableLoadMore();
if (isIntercept)//false表示上滑状态
isPullDownMotion = false;
}
} else {
isIntercept = false;
}
假设当前滑动,大于这个值,继续走里面的if推断,假设当前是下拉状态,而且是能够下拉。那么拦截事件,否则进行滑动载入很多其它。假设满足滑动载入很多其它的条件,那么能够向上滑动。而且整个过程,用isPullDownMotion记录下了是向上还是向下的动作。后面在onTouchEvent中须要使用。最后,ACTION_UP和ACTION_CANCEL不拦截。假设拦截,会影响到子View的点击事件。
//记录当前滑动的位置
private int mScrollY; @Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
//第一次推断时,downY仅仅能从intercept中获取。之后从这里获取
downY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
float dY = event.getY() - downY;
if (isPullDownMotion)//下拉
doPullDownMoveEvent(dY);
else {//自己主动载入很多其它
doLoadMoreEvent(dY);
}
break;
case MotionEvent.ACTION_UP: if (isPullDownMotion) {
//处理下拉结果
doPullDownResult();
} else {
//处理滑动载入很多其它结果
doLoadMoreResult();
} break;
case MotionEvent.ACTION_CANCEL:
//同ACTION_UP
if (isPullDownMotion) {
doPullDownResult();
} else {
doLoadMoreResult(); } break;
}
return true;
}
private void doPullDownMoveEvent(float y) {
int scrollY = (int) (mScrollY - y * RATIO);
if (scrollY > 0) {
scrollY = 0;
}
if (scrollY < -mMaxScrollHeight) {
scrollY = -mMaxScrollHeight;
}
scrollTo(0, scrollY);
if (isRefreshing)
return;
//设置对应的状态
if (scrollY == 0) {
mCurrentState = NORMAL;
mHeaderView.onNormal();
} else if (scrollY <= 0 && scrollY > -mHeaderHeight) {
mCurrentState = PULL_TO_REFRESH;
mHeaderView.onPullToRefresh(Math.abs(scrollY));
} else if (scrollY <= -mHeaderHeight && scrollY >= -mMaxScrollHeight) {
mCurrentState = RELEASE_TO_REFRESH;
mHeaderView.onReleaseToRefresh(Math.abs(scrollY));
}
}
这样就完毕了触摸滑动。 后面。我们在通过滑动的位置,设置对应的状态。并回调HeaderView的各个状态的方法。
/**
* 处理释放后的操作
*/
private void doPullDownResult() {
//先获取如今滑动到的位置
mScrollY = getScrollY();
switch (mCurrentState) {
case PULL_TO_REFRESH:
mCurrentState = NORMAL;
mHeaderView.onNormal();
smoothScrollTo(0);
break;
case RELEASE_TO_REFRESH:
//松开时。假设是释放刷新,则開始进行刷新动作
if (!isRefreshing) {
//滑动到指定位置
smoothScrollTo(-mHeaderHeight); mHeaderView.onRefreshing();
isRefreshing = true;
if (listener != null) {
//运行刷新回调
listener.onPullDownRefresh(); }
//假设当前滑动位置太靠下,则滑动到指定刷新位置
} else if (mScrollY < -mHeaderHeight) {
smoothScrollTo(-mHeaderHeight);
}
break; }
}
/**
* 从当前位置滑动到指定位置
* @param y 滑动到的位置
*/
private void smoothScrollTo(int y) {
int dY = y - mScrollY;
mScroller.startScroll(0, mScrollY, 0, dY, 500);
invalidate(); }
这种方法,必须要配合computeScroll使用。不然是没有效果的。详细的原因,须要查看View的绘制流程,这里我就不详细分析。
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(0, mScroller.getCurrY());
mScrollY = mScroller.getCurrY();
invalidate();
}
}
这样,每次都移动一小段位置,就实现了平滑滑动的效果。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"> <com.mjc.recyclerviewdemo.refresh.PullToRefreshRecycleView
android:id="@+id/prrv"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
mPRRV = (PullToRefreshRecycleView) findViewById(R.id.prrv);
mPRRV.setOnRefreshListener(new PullToRefreshRecycleView.OnRefreshListener() {
@Override
public void onPullDownRefresh() {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
datas.add(0, "add");
mAdapter.notifyDataSetChanged();
mPRRV.completeRefresh();
}
}, 2000); } @Override
public void onLoadMore() {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
datas.add("李四");
datas.add("王五");
datas.add("张三");
datas.add("李四");
datas.add("王五");
datas.add("张三"); mAdapter.notifyDataSetChanged();
mPRRV.completeLoadMore();
}
}, 1000); }
});
最新文章
- 第01章(认识Java)
- DEV控件中GridView中的复选框与CheckBox实现联动的全选功能
- JavaSpring
- ofo走出校园观察:市场定位导致产品错位?
- 【DIOCP知识库】连接上下文TIocpClientContext
- (译)开发优秀的虚拟现实体验:从开发I Expect You to Die中总结的六个要点
- free 命令解释
- S3C2440的LCD虚拟显示测试
- SolrCloud阶段总结
- python 接口自动化测试--框架整改(五)
- web组件工具之获取表单数据:webUtils
- Type &#39;&#39; cannot conform to protocol &#39;&#39; because it has requirements that cannot be satisfied
- 转载-CentOS7关闭防火墙
- java中二维数组内存分配
- 洛谷P3085 [USACO13OPEN]阴和阳Yin and Yang(点分治,树上差分)
- Centos 7 安装 Mysql 5.5 5.6 5.7
- vue数组操作不更新视图问题
- 并发编程之 Semaphore 源码分析
- Office 365实现单点登录系列(3)—使用Azure AD Connect 进行目录同步
- jQuery技巧笔记
热门文章
- BZOJ4472
- JavaScript 回车键绑定登录 事件 常用键位码(keyCode)
- Java基础学习总结(11)——重载与重写
- ArcGIS api for javascript——地图配置-
- Objective-C学习笔记(十)——循环语句for和do-while的使用
- 学习中 常用到的string内置对象方法的总结
- Aizu - 2564 Tree Reconstruction 并查集
- jQuery - 设置内容和属性 设置内容 - text()、html() 以及 val() , 设置属性 - attr()
- UVA Building designing
- SQL SERVER 2014无法启动T-SQL调试的解决方法(亲自实践)