4.Android RecyclerView完美支持下拉刷
从月薪3000到年薪60万。从专科生到深圳一线大厂。关注我就能达到大师级水平,这话我终于敢说了, 年薪60万不是梦!
请问:简书怎么可以把代码格式调整?我贴出来换格式了。你们直接去Github下载工程!
今天开始讲RecycleView的系列教程。分割线,分组,局部刷新,动态添加,缓存原理,抖音效果,瀑布流。嵌套,动画等等
框架重写需要考虑的问题
1.是否支持下拉
2.是否支持上拉
3.自定义Header
4.自定义Footer
5.自定义动画
原理介绍:
使用RecyclerView.OnScrollListener滚动的监听器来监听RecyclerView是否滑动到了底部,这样我们就可以执行添加底部动画的操作。
边界条件
根据原理我们能够知道,做一个下拉刷新的动画加载,我们需要做的有两个动作:
1.如何判断RecyclerView已经滑动到底部。
2.如何添加RecyclerView的底部动画。
重要的就是RecyclerView滚动监听
RecyclerView.OnScrollListener
mRecyclerView.canScrollVertically(1) 是否拉到底部
4.一些属性的介绍
setWaveHeight、setHeaderHeight、setBottomHeight、setOverScrollHeight
setWaveHeight 设置头部可拉伸的最大高度。
setHeaderHeight 头部固定高度(在此高度上显示刷新状态)
setBottomHeight 底部高度
setOverScrollHeight 设置最大的越界高度
setEnableRefresh、setEnableLoadmore
灵活的设置是否禁用上下拉。
setHeaderView(IHeaderView headerView)、setBottomView(IBottomView bottomView)
设置头部/底部个性化刷新效果,头部需要实现IHeaderView,底部需要实现IBottomView。
setEnableOverScroll
是否允许越界回弹。
总结步骤:
1.完成头部添加和脚部添加
2.下拉刷新实现
3.同理,完成上拉加载
下拉刷新具体实现:
需要用的接口:
1.自动下拉
2.可以停止下拉
3.可以动态设置下拉的ui
4.下拉完成回调
下拉核心东西
1.处理拦截事件:因为拖动和点击事件有冲突
/****
* 条目的点击事件和滑动事件
* @param ev
* @return
*/
@Override
publicbooleandispatchTouchEvent(MotionEventev) {
switch(ev.getAction()) {
caseMotionEvent.ACTION_DOWN:
// 记录手指按下的位置 ,之所以写在dispatchTouchEvent那是因为如果我们处理了条目点击事件,
// 那么就不会进入onTouchEvent里面,所以只能在这里获取
mFingerDownY=(int)ev.getRawY();
break;
caseMotionEvent.ACTION_UP:
if(mCurrentDrag) {//拖动没有超过一定距离就要还原
restoreRefreshView();
}
break;
}
returnsuper.dispatchTouchEvent(ev);
}
2.处理滑动事件:
@Override
publicbooleanonTouchEvent(MotionEvente) {
switch(e.getAction()) {
caseMotionEvent.ACTION_MOVE:
// 如果是在最顶部才处理,否则不需要处理
if(canScrollUp()||mCurrentRefreshStatus==REFRESH_STATUS_REFRESHING) {
// 如果没有到达最顶端,也就是说还可以向上滚动就什么都不处理
returnsuper.onTouchEvent(e);
}
// 解决下拉刷新自动滚动问题
if(mCurrentDrag) {
scrollToPosition(0);
}
// 获取手指触摸拖拽的距离
intdistanceY=(int) ((e.getRawY()-mFingerDownY)*mDragIndex);
// 如果是已经到达头部,并且不断的向下拉,那么不断的改变refreshView的marginTop的值
if(distanceY>0) {
intmarginTop=distanceY-mRefreshViewHeight;
setRefreshViewMarginTop(marginTop);
updateRefreshStatus(marginTop);
mCurrentDrag=true;
returnfalse;
}
break;
}
returnsuper.onTouchEvent(e);
}
需要判断是否到了顶部
/**
* @return Whether it is possible for the child view of this layout to
* scroll up. Override this if the child view is a custom view.
* 判断是不是滚动到了最顶部,这个是从SwipeRefreshLayout里面copy过来的源代码
*/
publicbooleancanScrollUp() {
if(android.os.Build.VERSION.SDK_INT<14) {
returnViewCompat.canScrollVertically(this,-1)||this.getScrollY()>0;
}else{
returnViewCompat.canScrollVertically(this,-1);
}
}
滑动过程中要不断修改recycleView的参数值
/**
* 设置刷新View的marginTop
*/
publicvoidsetRefreshViewMarginTop(intmarginTop) {
MarginLayoutParamsparams=(MarginLayoutParams)mRefreshView.getLayoutParams();
if(marginTop<-mRefreshViewHeight+1) {
marginTop=-mRefreshViewHeight+1;
}
params.topMargin=marginTop;
mRefreshView.setLayoutParams(params);
}
重写位置方法
@Override
protectedvoidonLayout(booleanchanged,intl,intt,intr,intb) {
super.onLayout(changed,l,t,r,b);
if(changed) {
if(mRefreshView!=null&&mRefreshViewHeight<=0) {
// 获取头部刷新View的高度
mRefreshViewHeight=mRefreshView.getMeasuredHeight();
if(mRefreshViewHeight>0) {
// 隐藏头部刷新的View marginTop 多留出1px防止无法判断是不是滚动到头部问题
setRefreshViewMarginTop(-mRefreshViewHeight+1);
}
}
}
}
1.定义接口:下拉,停止下拉,下拉回调,下拉的view
* Description: 下拉刷新的辅助类为了匹配所有效果
*/
public abstract class RefreshViewCreator {
/**
* 获取下拉刷新的View
*
* @param context 上下文
* @param parent RecyclerView
*/
public abstract ViewgetRefreshView(Context context, ViewGroup parent);
/**
* 正在下拉
* @param currentDragHeight 当前拖动的高度
* @param refreshViewHeight 总的刷新高度
* @param currentRefreshStatus 当前状态
*/
public abstract void onPull(int currentDragHeight, int refreshViewHeight, int currentRefreshStatus);
/**
* 正在刷新中
*/
public abstract void onRefreshing();
/**
* 停止刷新
*/
public abstract void onStopRefresh();
}
2.核心东西。事件处理
/**
* Description: 下拉刷新的RecyclerView
*/
public class RefreshRecyclerViewextends WrapRecyclerView {
// 下拉刷新的辅助类
private RefreshViewCreatormRefreshCreator;
// 下拉刷新头部的高度
private int mRefreshViewHeight =0;
// 下拉刷新的头部View
private ViewmRefreshView;
// 手指按下的Y位置
private int mFingerDownY;
// 手指拖拽的阻力指数
private float mDragIndex =0.35f;
// 当前是否正在拖动
private boolean mCurrentDrag =false;
// 当前的状态
private int mCurrentRefreshStatus;
// 默认状态
public int REFRESH_STATUS_NORMAL =0x0011;
// 下拉刷新状态
public int REFRESH_STATUS_PULL_DOWN_REFRESH =0x0022;
// 松开刷新状态
public int REFRESH_STATUS_LOOSEN_REFRESHING =0x0033;
// 正在刷新状态
public int REFRESH_STATUS_REFRESHING =0x0033;
public RefreshRecyclerView(Context context) {
super(context);
}
public RefreshRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public RefreshRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
// 先处理下拉刷新,同时考虑刷新列表的不同风格样式,确保这个项目还是下一个项目都能用
// 所以我们不能直接添加View,需要利用辅助类
public void addRefreshViewCreator(RefreshViewCreator refreshCreator) {
this.mRefreshCreator = refreshCreator;
addRefreshView();
}
@Override
public void setAdapter(Adapter adapter) {
super.setAdapter(adapter);
addRefreshView();
}
/****
* 条目的点击事件和滑动事件
* @param ev
* @return
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
// 记录手指按下的位置 ,之所以写在dispatchTouchEvent那是因为如果我们处理了条目点击事件,
// 那么就不会进入onTouchEvent里面,所以只能在这里获取
mFingerDownY = (int) ev.getRawY();
break;
case MotionEvent.ACTION_UP:
if (mCurrentDrag) {//拖动没有超过一定距离就要还原
restoreRefreshView();
}
break;
}
return super.dispatchTouchEvent(ev);
}
/**
* 重置当前刷新状态状态
*/
private void restoreRefreshView() {
int currentTopMargin = ((MarginLayoutParams)mRefreshView.getLayoutParams()).topMargin;
int finalTopMargin = -mRefreshViewHeight +1;
if (mCurrentRefreshStatus ==REFRESH_STATUS_LOOSEN_REFRESHING) {
finalTopMargin =0;
mCurrentRefreshStatus =REFRESH_STATUS_REFRESHING;
if (mRefreshCreator !=null) {
mRefreshCreator.onRefreshing();
}
if (mListener !=null) {
mListener.onRefresh();
}
}
int distance = currentTopMargin - finalTopMargin;
// 回弹到指定位置
ValueAnimator animator = ObjectAnimator.ofFloat(currentTopMargin, finalTopMargin).setDuration(distance);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentTopMargin = (float) animation.getAnimatedValue();
setRefreshViewMarginTop((int) currentTopMargin);
}
});
animator.start();
mCurrentDrag =false;
}
@Override
public boolean onTouchEvent(MotionEvent e) {
switch (e.getAction()) {
case MotionEvent.ACTION_MOVE:
// 如果是在最顶部才处理,否则不需要处理
if (canScrollUp() ||mCurrentRefreshStatus ==REFRESH_STATUS_REFRESHING) {
// 如果没有到达最顶端,也就是说还可以向上滚动就什么都不处理
return super.onTouchEvent(e);
}
// 解决下拉刷新自动滚动问题
if (mCurrentDrag) {
scrollToPosition(0);
}
// 获取手指触摸拖拽的距离
int distanceY = (int) ((e.getRawY() -mFingerDownY) *mDragIndex);
// 如果是已经到达头部,并且不断的向下拉,那么不断的改变refreshView的marginTop的值
if (distanceY >0) {
int marginTop = distanceY -mRefreshViewHeight;
setRefreshViewMarginTop(marginTop);
updateRefreshStatus(marginTop);
mCurrentDrag =true;
return false;
}
break;
}
return super.onTouchEvent(e);
}
/**
* 更新刷新的状态
*/
private void updateRefreshStatus(int marginTop) {
if (marginTop <= -mRefreshViewHeight) {
mCurrentRefreshStatus =REFRESH_STATUS_NORMAL;
}else if (marginTop <0) {
mCurrentRefreshStatus =REFRESH_STATUS_PULL_DOWN_REFRESH;
}else {
mCurrentRefreshStatus =REFRESH_STATUS_LOOSEN_REFRESHING;
}
if (mRefreshCreator !=null) {
mRefreshCreator.onPull(marginTop, mRefreshViewHeight, mCurrentRefreshStatus);
}
}
/**
* 添加头部的刷新View
*/
private void addRefreshView() {
RecyclerView.Adapter adapter = getAdapter();
if (adapter !=null &&mRefreshCreator !=null) {
// 添加头部的刷新View
View refreshView =mRefreshCreator.getRefreshView(getContext(), this);
if (refreshView !=null) {
addHeaderView(refreshView);
this.mRefreshView = refreshView;
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (changed) {
if (mRefreshView !=null &&mRefreshViewHeight <=0) {
// 获取头部刷新View的高度
mRefreshViewHeight =mRefreshView.getMeasuredHeight();
if (mRefreshViewHeight >0) {
// 隐藏头部刷新的View marginTop 多留出1px防止无法判断是不是滚动到头部问题
setRefreshViewMarginTop(-mRefreshViewHeight +1);
}
}
}
}
/**
* 设置刷新View的marginTop
*/
public void setRefreshViewMarginTop(int marginTop) {
MarginLayoutParams params = (MarginLayoutParams)mRefreshView.getLayoutParams();
if (marginTop < -mRefreshViewHeight +1) {
marginTop = -mRefreshViewHeight +1;
}
params.topMargin = marginTop;
mRefreshView.setLayoutParams(params);
}
/**
* @return Whether it is possible for the child view of this layout to
* scroll up. Override this if the child view is a custom view.
* 判断是不是滚动到了最顶部,这个是从SwipeRefreshLayout里面copy过来的源代码
*/
public boolean canScrollUp() {
if (android.os.Build.VERSION.SDK_INT <14) {
return ViewCompat.canScrollVertically(this, -1) ||this.getScrollY() >0;
}else {
return ViewCompat.canScrollVertically(this, -1);
}
}
/**
* 停止刷新
*/
public void onStopRefresh() {
mCurrentRefreshStatus =REFRESH_STATUS_NORMAL;
restoreRefreshView();
if (mRefreshCreator !=null) {
mRefreshCreator.onStopRefresh();
}
}
// 处理刷新回调监听
private OnRefreshListenermListener;
public void setOnRefreshListener(OnRefreshListener listener) {
this.mListener = listener;
}
public interface OnRefreshListener {
void onRefresh();
}
}
3.调用
publicclassPullActivityextendsAppCompatActivity{
privateRefreshRecyclerViewmRecycleView;
privateLinearLayoutManagermLinearLayoutManager;//布局管理器
privateListmList;
@Override
protectedvoidonCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pull);
mList=newArrayList();
mRecycleView=findViewById(R.id.rv_list);
//初始化数据
initData(mList);
//创建布局管理器,垂直设置LinearLayoutManager.VERTICAL,水平设置LinearLayoutManager.HORIZONTAL
mLinearLayoutManager=newLinearLayoutManager(this,LinearLayoutManager.VERTICAL,false);
//创建适配器,将数据传递给适配器
//设置布局管理器
mRecycleView.setLayoutManager(mLinearLayoutManager);
MyRecycleViewAdapteradapter=newMyRecycleViewAdapter(mList);
mRecycleView.setAdapter(newWrapRecyclerAdapter(adapter));
defaultRefreshCreator=newDefaultRefreshCreator();
mRecycleView.addRefreshViewCreator(defaultRefreshCreator);
mRecycleView.setOnRefreshListener(newRefreshRecyclerView.OnRefreshListener() {
@Override
publicvoidonRefresh() {
Log.d("PullActivity","onRefresh");
}
});
handler.sendEmptyMessageDelayed(0,5000);
}
Handlerhandler=newHandler() {
@Override
publicvoidhandleMessage(@NonNullMessagemsg) {
super.handleMessage(msg);
mRecycleView.onStopRefresh();//停止刷新
}
};
DefaultRefreshCreatordefaultRefreshCreator;
publicvoidinitData(Listlist) {
for(inti=1;i<=80;i++) {
list.add("第"+i+"条数据");
}
}
}
问题:
给你一个普通的页面,你自己弄一个下拉刷新?怎么处理?
RecycleView是不是要搭配adapter一起?
不需要,recycleView和第三方的adataper是分开的,2个独立的控件
不能滑动都问题
支持下拉刷新和上拉加载的RefreshLayout,自带越界回弹效果,支持RecyclerView,AbsListView,ScrollView,WebView
Google官方推出了SwipeRefreshLayout和RecyclerView的共同使用,,为我们提供了更加便捷的列表下拉刷新功能,但是,并没有给我们提供上拉加载功能
基于第三方: SmartRefreshLayout
基于第三方控件TwinklingRefreshLayout
第一种方式:嵌套
使用finishRefreshing()方法结束刷新,finishLoadmore()方法结束加载更多。此处OnRefreshListener还有其它方法,可以选择需要的来重写。
如果你想进入到界面的时候主动调用下刷新,可以调用startRefresh()/startLoadmore()方法。(会自动执行RefreshListenerAdapter监听事件中的onRefresh()/onLoadMore())
第二种方式:
方便实现个性化的 Header 和 Footer