在这里特别感谢大神,这里附上大神帖子:https://github.com/1030310877/LoadMoreRecyclerView
SwipeRefreshLayout嵌套RecyclerView实现上下拉刷新。SwipeRedreshLayout是Android自带的一个下拉刷新控件。
它有自带的下拉刷新方法setOnRefreshListener();
//下拉刷新
swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
//最后清空数据,否则可能造成下标越界,但是业务要求先清空数据,所以,我在刷新数据的同时,将RecyclerView的滑动事件给拦截掉
msgList.clear();
//设置RecyclerView的滑动状态,在下拉刷新时,将RecyclerView的滑动事件给消费,连拦截
recyclerView.setRecycleScrollBug(true);
//加载数据
getData("0", "20");
//判断是否下拉刷新
refreshFlag = 1;
//得到刷新数据的状态
firstFlag = 0;
}
});
比较简单不在赘述。只是有一点需要注意:在下拉刷新的同时,如果同时做上拉加载的动作会导致下标越界。原因是,先将数据清空后,导致请求下拉无数据。但是,这是刷新,又不得不先将数据清空,所以,我在做下拉刷新的动作时,将页面滑动给禁止。我只需要禁止掉RecyclerView的滑动事件就可以了。使用如下方法。
//暂时关闭RecyclerView滑动
public void setRecycleScrollBug(final boolean mIsRefreshing) {
recyclerView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (mIsRefreshing) {
return true;
} else {
return false;
}
}
});
}
很简单,只是拦截的RecyclerView的onTouch事件,return true拦截,return false不拦截。
一般来说,上拉加载更多,只需要在RecyclerView添加尾部局就可以了。但是亲测不行。
//返回item数量
@Override
public int getItemCount() {
//如果返回mMsgList.size()+1会造成下标越界的异常。但是如果反回mMsgList.size()时。会造成数据少一条,加载更多试图将最后一条item给替换了
return mMsgList.size() == 0 ? 0 : mMsgList.size() + 1;
}
@Override
public int getItemViewType(int position) {
if (position + 1 == getItemCount()) {
//最后一个item设置为footerView
return TYPE_FOOTER;
} else {
return TYPE_ITEM;
}
}
如果返回mMsgList.size()+1会造成下标越界的异常。
但是如果反回mMsgList.size()时。会造成数据少一条,加载更多试图将最后一条item给替换了。
纠结了好长一段时间。因为我的思路一直停留在RecyclerView的多条目加载上面了。
这个有个弊端就是在GridLayoutManager时候就没有用了,footView或者headVeiw就跑到第一个和最后一个Grid中去了。
或者在Adapter中进行处理
这个需要通过LinearLayoutManager中findLastVisibleView的方法来判断是否到达最后。
通过GridLayoutManager中的方法设置头尾view的Grid占比来使头尾变成一整行。 这种在adapter中处理的方法,不觉得还算是自定义view的范畴,顶多是recyclerView的活用。
直到今天看到另外一种写法,直接在RecyclerView布局下面增加布局,使用使用组合View的方法,结合v4包中的NestedScrollingParent来处理和RecyclerView的滑动,然后使用NestedScrollingChild来处理嵌套在SwipeRefreshLayout中的滑动事件,达成使用SwipeRefreshLayout来进行下拉刷新。这样只要处理好滑动事件,那就不管是什么LayoutManager,footView永远在最下面。
上代码
public class MyRecyclerView extends LinearLayout implements NestedScrollingParent, NestedScrollingChild {
private View rootLayout;
private RecyclerView recyclerView;
private FrameLayout footView;
private NestedScrollingParentHelper helper;
private NestedScrollingChildHelper childHelper;
private boolean isBottom = false;
private boolean changeBottom = false;
private boolean enableLoad = true;
private boolean isLoading = false;
//TODO footView的内容View
private View footContentView;
private final int[] mScrollOffset = new int[2];
private ProgressBar progressBar;
public MyRecyclerView(Context context) {
this(context, null);
}
public MyRecyclerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
helper = new NestedScrollingParentHelper(this);
childHelper = new NestedScrollingChildHelper(this);
childHelper.setNestedScrollingEnabled(true);
initView();
}
private void initView() {
View.inflate(getContext(), R.layout.layout_myrecycler, this);
recyclerView = (RecyclerView) findViewById(R.id.recycler_content);
footView = (FrameLayout) findViewById(R.id.layout_footView);
//TODO
footContentView = LayoutInflater.from(getContext()).inflate(R.layout.footview_up, null);
footView.addView(footContentView);
rootLayout = getChildAt(0);
}
private void smoothScrollBy(int dx, int dy) {
//将ProgressBar隐藏
findViewById(R.id.item_load_pb).setVisibility(GONE);
//设置mScroller的滚动偏移量
if (isBottom) {
//已经到了底部,并且还在往上拉,直接返回,不处理事件
if (rootLayout.getScrollY() + dy >= footView.getMeasuredHeight()) {
return;
}
//回弹
if (rootLayout.getScrollY() + dy <= 0) {
dy = -rootLayout.getScrollY();
}
changeBottom = false;
rootLayout.scrollBy(0, dy);
invalidate();//这里必须调用invalidate()才能保证computeScroll()会被调用,否则不一定会刷新界面,看不到滚动效果
} else {
if (!isLoading) {
//TODO 重置视图
((TextView) footContentView.findViewById(R.id.status_tv)).setText("上拉加载");
}
//往上拉时,距离大于了footview的高度,只让它拉到那么大
if (rootLayout.getScrollY() + dy >= footView.getMeasuredHeight()) {
dy = footView.getMeasuredHeight() - rootLayout.getScrollY();
changeBottom = true;
//TODO 拉到footView最大高度时候可以做的事情,视图变化
if (!isLoading) {
((TextView) footContentView.findViewById(R.id.status_tv)).setText("松开加载更多");
}
} else {
//往下拉时,滑动距离大于了当前的偏移值
if (rootLayout.getScrollY() + dy < 0) {
dy = -rootLayout.getScrollY();
}
}
rootLayout.scrollBy(0, dy);
invalidate();//这里必须调用invalidate()才能保证computeScroll()会被调用,否则不一定会刷新界面,看不到滚动效果
}
}
/*==========以下为Child的方法==========*/
@Override
public void setNestedScrollingEnabled(boolean enabled) {
childHelper.setNestedScrollingEnabled(enabled);
}
@Override
public boolean isNestedScrollingEnabled() {
return childHelper.isNestedScrollingEnabled();
}
@Override
public boolean startNestedScroll(int axes) {
return childHelper.startNestedScroll(axes);
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
return childHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return childHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
@Override
public void stopNestedScroll() {
childHelper.stopNestedScroll();
}
@Override
public boolean hasNestedScrollingParent() {
return childHelper.hasNestedScrollingParent();
}
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return childHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return childHelper.dispatchNestedPreFling(velocityX, velocityY);
}
/*==========以下为Parent的方法,用来接收RecyclerView的滑动事件==========*/
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
startNestedScroll(nestedScrollAxes);
return enableLoad;
}
@Override
public void onNestedScrollAccepted(View child, View target, int axes) {
helper.onNestedScrollAccepted(child, target, axes);
}
@Override
public int getNestedScrollAxes() {
return helper.getNestedScrollAxes();
}
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
if (dy < 0 && rootLayout.getScrollY() > 0) {
//如果是手指下滑
smoothScrollBy(dx, dy);
consumed[1] = dy;
} else {
dispatchNestedPreScroll(dx, dy, consumed, mScrollOffset);
}
}
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
if (dyConsumed == 0 && dyUnconsumed > 0) {
smoothScrollBy(dxUnconsumed, dyUnconsumed);
} else {
//其余的未消费的事件,传给父View消费
dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, mScrollOffset);
}
}
@Override
public void onStopNestedScroll(View child) {
//将ProgressBar展示
findViewById(R.id.item_load_pb).setVisibility(VISIBLE);
isBottom = changeBottom;
if (isBottom && !isLoading && rootLayout.getScrollY() == footView.getMeasuredHeight()) {
isLoading = true;
//TODO LoadMore视图变化
((TextView) footContentView.findViewById(R.id.status_tv)).setText("正在加载");
if (listener != null) {
listener.onLoading();
}
} else {
isBottom = false;
if (rootLayout.getScrollY() > 0 && !isBottom) {
smoothScrollBy(0, -rootLayout.getScrollY());
}
}
helper.onStopNestedScroll(child);
//最后一定要调用这个,告诉父view滑动结束,不然父view的滑动会卡住。
stopNestedScroll();
}
@Override
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
return false;
}
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
return false;
}
@Override
public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle args) {
return false;
}
/*==============以下为开放部分的recyclerView方法 ================*/
//TODO 如果还需要其他recyclerview的方法,可在下方进行开放。
public void setAdapter(RecyclerView.Adapter adapter) {
recyclerView.setAdapter(adapter);
}
public void setLayoutManager(RecyclerView.LayoutManager layoutManager) {
recyclerView.setLayoutManager(layoutManager);
}
public void addItemDecoration(RecyclerView.ItemDecoration decoration) {
recyclerView.addItemDecoration(decoration);
}
public void addItemDecoration(RecyclerView.ItemDecoration decoration, int index) {
recyclerView.addItemDecoration(decoration, index);
}
public RecyclerView.Adapter getAdapter() {
return recyclerView.getAdapter();
}
public RecyclerView.LayoutManager getLayoutManager() {
return recyclerView.getLayoutManager();
}
public void addOnScrollListener(RecyclerView.OnScrollListener onScrollListener) {
recyclerView.addOnScrollListener(onScrollListener);
}
/**
* 加载结束后调用该方法进行footview缩回
*
* @param total
* @param size
*/
public void loadFinished(int total, int size) {
isLoading = false;
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (isBottom) {
smoothScrollBy(0, -footView.getMeasuredHeight());
}
}
}, 200);
if (total == size){
//TODO 加载完成后的处理,缩回以及视图变化
//将ProgressBar隐藏
findViewById(R.id.item_load_pb).setVisibility(GONE);
((TextView) footContentView.findViewById(R.id.status_tv)).setText("无更多数据");
}else {
//TODO 加载完成后的处理,缩回以及视图变化
((TextView) footContentView.findViewById(R.id.status_tv)).setText("加载成功");
}
}
public void setEnableLoad(boolean tf) {
this.enableLoad = tf;
}
private Handler handler = new Handler();
/*=============== 监听 ===================*/
public onLoadingMoreListener listener;
public void setOnLoadingListener(onLoadingMoreListener listener) {
this.listener = listener;
}
public interface onLoadingMoreListener {
void onLoading();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev);
}
/**
* 关闭RecyclerView滑动
* @param mIsRefreshing true表示拦截滑动事件,页面不可滑动,false表示不拦截事件,页面可以滑动
*/
public void setRecycleScrollBug(final boolean mIsRefreshing) {
recyclerView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (mIsRefreshing) {
return true;
} else {
return false;
}
}
});
}
}
贴上布局代码:layout_myrecycler
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<FrameLayout
android:id="@+id/layout_footView"
android:layout_width="match_parent"
android:layout_height="50dp" />
</LinearLayout>
布局代码:footview_up
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="80dp"
android:gravity="center"
android:orientation="horizontal">
<ProgressBar
android:id="@+id/item_load_pb"
style="@android:style/Widget.Holo.ProgressBar"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerHorizontal="true"
/>
<TextView
android:id="@+id/status_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/ten_dp"
android:text="上拉刷新" />
</LinearLayout>
不过这样还是会出现下拉刷新时偶尔会造成下标越界的Bug,原因还是一样,刷新的同时,将数据清空导致的。我在这里使用了一个方法,再刷新时,写一个标识,在请求数据时,得到数据之前,将之前存在的数据清空,这样就不会导致下标越界了
/**
* 如果绑定的 List 对象在更新数据之前进行了 clear,而这时用户紧接着迅速上滑RecycleView,就会造成崩溃,而且异常不会报到你的代码上,属于RecycleView的内部错误。
* 原因是当你 clear 了 list 之后,这时迅速上滑,而新数据还没到来,导致 RecycleView 要更新加载下面的 Item 时候,找不到数据源了,造成程序直接崩溃了
* 思路:当你在下拉刷新时,将RecyclerView设为不可滑动状态,刷新完成在设置为可滑动状态。暂时解决
*/
//下拉刷新
swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
//设置RecyclerView的滑动状态,在下拉刷新时,将RecyclerView的滑动事件给消费,连拦截
recyclerView.setRecycleScrollBug(true);
try {
//最后清空数据,否则可能造成下标越界,但是业务要求先清空数据,所以,我在刷新数据的同时,将RecyclerView的滑动事件给拦截掉
//msgList.clear();
//设置标识位,在刷新时,得到数据前,将数据清空,而不是在刷新方法里清空
pageindex = 1;
getData("0", "" + NEXNUM);
//判断是否下拉刷新
refreshFlag = 1;
//得到刷新数据的状态
totalFlag = 0;
} catch (Exception e) {
e.printStackTrace();
ToastUtils.showShort("操作次数过于频繁,请稍后再试");
}
}
});
源码在文章开头的github里面能找到。
来源:CSDN
作者:I_have_to_believe
链接:https://blog.csdn.net/zjc_null/article/details/83417336