自从使用了RecyclerView
再也回不去了,什么ListView、GridView统统让他们退休了。必须安利起来,用了才能体会它的神奇!
根据使用RecyclerView
以来,拓展的一些功能及对RecyclerView.Adapter
的封装,想在这里跟大家分享一些经验,还望指正。
- 功能介绍
====
基于对RecyclerView
在使用过程中的一些痛点写了这个开源项目 TurboRecyclerViewHelper 。功能点详见README。
本次主要介绍针对TurboRecyclerView
上拉/左滑的功能的实现及思路。
下面直接进入正题...
- 状态分析
====
以上拉加载的过程作为本次分享的一个栗子。状态如下图:
我们所关心的是RecyclerView滑动到底部的状态,这个状态下是我们需要处理的临界状态
。
初始状态以及滑动中的状态我们不需要关心(交给RecyclerView本身处理即可)。
- 目标状态的条件限制
====
废话不多说直接看代码:
if (!mLoadEnabled || canScrollEnd() || mIsLoading || isEmpty()) {
return super...;
}
我们从两个方面来分析可以开始处理Touch事件的条件:
3.1 客观条件
所谓客观条件即是RecyclerView滑动到底部的这个状态的物理状态,体现在代码上就是
private boolean canScrollEnd() {
//判断在纵向是否还能向上滑动
return ViewCompat.canScrollVertically(this, 1);
}
这里为了简化只判断了纵向是否可以向下滑动,实际代码中这里是判断条件为ViewCompat.canScrollVertically(this, 1) || ViewCompat.canScrollHorizontally(this, 1)
这个条件返回值如果是false,则代表我们可以从这个临界状态开始处理Touch事件,否则不处理。
3.2 逻辑条件(主观条件)
逻辑条件(或者称为主观条件)是设计控件本身所考虑的限制条件。
判断条件如下:
//是否允许上拉 || 是否正处于刷新状态 || 是否处理空状态
if (!mLoadEnabled || mIsLoading || isEmpty())
在以上条件下我们认为是控件本身应处于不可上拉的状态,我们不做处理。
- Touch事件的处理
====
在同时满足客观条件和逻辑条件下,我们就可以开始处理上拉的效果。
4.1 记录初始值
我们需要在MotionEvent.ACTION_DOWN
MotionEvent.ACTION_POINTER_DOWN
时记录初始值:
mInitialMotionX = getMotionEventX(e, actionIndex);
mInitialMotionY = getMotionEventY(e, actionIndex);
4.2 判断滑动是否符合预期值
在MotionEvent.ACTION_MOVE
时判断是否是上拉的状态:
//LayoutManager中提供的判断是否纵向可以滑动的方法
final boolean canScrollVertically = getLayoutManager().canScrollVertically();
...
final int y = getMotionEventY(e, index);
int deltaY = y - mInitialMotionY;
if (canScrollVertically && Math.abs(deltaY) > mTouchSlop && deltaY < 0) { ... //处理上拉效果}
记录当前Y值,且判断是否手指在上滑状态。
4.3 实现上拉效果
在自定义控件中,实现上拉效果有多种途径,例如大家常用的利用Scroller
配合scrollTo
来实现滑动,但是在RecyclerView的实现中并不支持这种方式。这个方案Close!
@Override
public void scrollTo(int x, int y) {
Log.w(TAG, "RecyclerView does not support scrolling to an absolute position. "
+ "Use scrollToPosition instead");
}
这里采用setTranslationY
来实现上拉效果,根据手指移动的距离计算出移动距离来改变RecyclerView的位置。
...
float targetEnd = -dampAxis(deltaY); //阻尼值的计算
setTranslationY(targetEnd);
return true; //消费掉此事件
到这里上拉的效果已经实现完毕。
4.4 复位及刷新
距离成功只差一点点。
在用户手指松开以后,我们要考虑做两件事:RecyclerView的复位及是否可以处于刷新状态。
针对复位操作,我们只需要逆向setTranslationY
值即可。这里我们采用属性动画来实现
private void animateOffsetToEnd(final String propertyName, final Interpolator interpolator, float... value) {
if (mResetAnimator == null) {
mResetAnimator = new ObjectAnimator();
mResetAnimator.setTarget(this);
}
mResetAnimator.cancel();
mResetAnimator.setPropertyName(propertyName);
mResetAnimator.setFloatValues(value);
mResetAnimator.setInterpolator(interpolator);
mResetAnimator.start();
}
...
if(canScrollVertically)
animateOffsetToEnd("translationY", mInterpolator, 0f);
...
对于刷新我们要做的事情也比较简单,判断当前移动距离达到阈值后,回调监听事件并显示LOAING_VIEW。
Log.i(TAG, "refreshing...");
mIsLoading = true;
dispatchOnLoadingMoreListeners();
smoothScrollToPosition(mLastVisibleItemPosition + 1);
刷新完毕后,记得通知TurboRecyclerView更新状态哦!
mTurboRecyclerView.addOnLoadingMoreListener(new OnLoadMoreListener() {
@Override
public void onLoadingMore() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
mRecyclerView.loadMoreComplete(Arrays.asList(sCheeseStrings));
}
}, 2000);
}
});
至此整个上拉到复位刷新的过程完成。
完整代码详见 TurboRecyclerView.java
希望我的分享能让您能有所收获。也欢迎支持一下这个项目~ 持续维护~
下次准备介绍一下对RecyclerView.Adapter
的封装,还请关注!😊