自定义RecyclerView步骤如下:
- 新建MyCustomRecyclerView类,继承RecyclerView类
[第一步]
新建MyCustomRecyclerView类,继承RecyclerView类
代码如下:
/**
* 现在开始自定义RecyclerView
*/
public class MyCustomRecyclerView extends RecyclerView {
public MyCustomRecyclerView(@NonNull Context context) {
this(context, (AttributeSet)null);
}
public MyCustomRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MyCustomRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
}
以上需要注意的是,该3个构造方法采用连接调用的方式,核心代码如下:
this(context, (AttributeSet)null);
this(context, attrs, 0);
也就是说,如果调用第一个构造方法,会接着调用第二个方法,再接着调用第三个构造方法。
这里需要说明的是:
- 如果使用如下Java代码:
MyCustomRecyclerView myCustomRecyclerView = new MyCustomRecyclerView(this);
MyCustomRecyclerView myCustomRecyclerView = new MyCustomRecyclerView(this, null);
MyCustomRecyclerView myCustomRecyclerView = new MyCustomRecyclerView(this, null, 0);
分别调用自定义RecyclerView的第一个、第二个、第三个构造方法。
- 如果在xml中配置,那么默认调用第二个构造方法。
使用联级构造方法的好处在于,初始化代码只需要写在最后一个构造方法中即可
图片.png[第二步]
监听手指滑动(也就是说手势)
这里请注意,这是手势监听,而不是RecyclerView滚动监听。
分析手势之前,您可能需要了解一下触摸标记,如下:
public static final int ACTION_DOWN = 0;
public static final int ACTION_UP = 1;
public static final int ACTION_MOVE = 2;
public static final int ACTION_CANCEL = 3;
public static final int ACTION_OUTSIDE = 4;
public static final int ACTION_POINTER_DOWN = 5;
public static final int ACTION_POINTER_UP = 6;
public static final int ACTION_HOVER_MOVE = 7;
public static final int ACTION_SCROLL = 8;
public static final int ACTION_HOVER_ENTER = 9;
public static final int ACTION_HOVER_EXIT = 10;
public static final int ACTION_BUTTON_PRESS = 11;
public static final int ACTION_BUTTON_RELEASE = 12;
我们在初始化时,重新设置了手势监听
private void init(){
this.setOnFlingListener(new OnFlingListener() {
@Override
public boolean onFling(int velocityX, int velocityY) {
return true;
}
});
}
当手指滑动时,总会返回两个参数:
- velocityX:表示X轴方向的滑动值,向左滑动为正数,向右滑动为负数,滑动的速度越快他们的绝对值越大,反之越小。(如果是纵屏,velocityX始终为0)
- velocityY:表示Y轴方向的滑动值,向上滑动为正数,向下滑动为负数,滑动的速度越快他们的绝对值越大,反之越小。(如果是横屏,velocityY始终为0)
当设置监听之后,我们发现,RecyclerView失去了本身的滚动效果,如图:
105.gif然而,原本的滚动效果应该是这样的:
106.gif遇到这种问题,我们只能从分析源码了,我在源码中找到了RecyclerView的触摸事件:
public boolean onTouchEvent(MotionEvent e) {
if (!this.mLayoutFrozen && !this.mIgnoreMotionEventTillDown) {
if (this.dispatchOnItemTouch(e)) {
this.cancelTouch();
return true;
} else if (this.mLayout == null) {
return false;
} else {
//...隐藏代码
switch(action) {
//...隐藏代码
case 1:
this.mVelocityTracker.addMovement(vtev);
eventAddedToVelocityTracker = true;
this.mVelocityTracker.computeCurrentVelocity(1000, (float)this.mMaxFlingVelocity);
float xvel = canScrollHorizontally ? -this.mVelocityTracker.getXVelocity(this.mScrollPointerId) : 0.0F;
float yvel = canScrollVertically ? -this.mVelocityTracker.getYVelocity(this.mScrollPointerId) : 0.0F;
//====关键代码====start
if (xvel == 0.0F && yvel == 0.0F || !this.fling((int)xvel, (int)yvel)) {
this.setScrollState(0);
}
//====关键代码====end
this.resetTouch();
break;
//...隐藏代码
}
if (!eventAddedToVelocityTracker) {
this.mVelocityTracker.addMovement(vtev);
}
vtev.recycle();
return true;
}
} else {
return false;
}
}
对应图上的触摸标志,1代表手指抬起,在关键代码中有一段关键代码,我们只需要分析当前类的this.fling()
这个方法即可。
下面是this.fling()
方法核心代码截图:
默认情况下,mOnFlingListener
为null,也就一定会走到代码this.mViewFlinger.fling(velocityX, velocityY)
,最后返回true,结束事件的分发。
我们来继续分析this.mViewFlinger.fling(velocityX, velocityY)
方法:
public void fling(int velocityX, int velocityY) {
RecyclerView.this.setScrollState(2);
this.mLastFlingX = this.mLastFlingY = 0;
this.mScroller.fling(0, 0, velocityX, velocityY, -2147483648, 2147483647, -2147483648, 2147483647);
this.postOnAnimation();
}
我们只看关键代码this.mScroller.fling
,在OverScroller
类中还有一个fling
方法,看到这里请不要进入懵逼状态了,RecyclerView手指滑动触发的滚动事件其实就是执行了OverScroller
的fling方法。
有关OverScroller
的讲解,请查看这篇博客Android OverScroller分析
当我们在自定义RecyclerView中主动设置了手势监听时,也就是说mOnFlingListener
不为null,那么是不是说就一定不执行this.mViewFlinger.fling(velocityX, velocityY)
呢?别急,源码中还有一个判断:
如上图所示,决定自定义RecyclerView是否有滚动动画有两个条件:
- 是否设置手势的监听?
- 如果设置了手势的监听,它的返回值是true还是false?
[代码一]
:依然有滚动动画,因为onFling的返回值永远为false
public class MyCustonFling extends RecyclerView.OnFlingListener {
@Override
public boolean onFling(int velocityX, int velocityY) {
return false;
}
}
[代码二]
:没有滚动动画,因为onFling的返回值永远为true
public class MyCustonFling extends RecyclerView.OnFlingListener {
@Override
public boolean onFling(int velocityX, int velocityY) {
return true;
}
}
,这时直接返回true,结束事件分发。
这样就不会执行OverScroller
的fling方法了,为了实现RecyclerView的滚动动画,我们必须在监听的onFling
回调方法中手动实现滚动效果,RecyclerView类中有个SmoothScroller
内部类,常常用它来实现滚动效果,官方还专门为RecyclerView开发了LinearSmoothScroller
类,该类的父类就是SmoothScroller
。我们经常使用的
recyclerview.smoothScrollToPosition(position);
接口就是为了实现滚动效果出现的,它的滚动动画本质上就是基于LinearSmoothScroller
实现的。
有关LinearSmoothScroller
的知识可以看这篇博客LinearSmoothScroller分析。
那么,onFling
方法中的代码该怎么写呢?
@Override
public boolean onFling(int velocityX, int velocityY) {
RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
if (layoutManager == null) {
return false;
}
RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
if (adapter == null) {
return false;
}
//获取最小滑动速度
int minFlingVelocity = mRecyclerView.getMinFlingVelocity();
//计算返回值,true:终止滚动 false:继续滚动
return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity) && snapFromFling(layoutManager, velocityX, velocityY);
}
一般这样写就是固定格式了,这里的重点其实是snapFromFling
方法,snapFromFling
方法需要实现滚动动画,使用LinearSmoothScroller
实现滚动效果步骤如下:
[第一步]
:创建LinearSmoothScroller对象
[第二步]
:绑定目标位置
[第三步]
:开始动画
代码如下:
LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext());
linearSmoothScroller.setTargetPosition(position);
this.startSmoothScroll(linearSmoothScroller);
但是,为了调整滚动速度,您可能需要重写calculateSpeedPerPixel
方法
LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()){
@Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
}
};
linearSmoothScroller.setTargetPosition(position);
this.startSmoothScroll(linearSmoothScroller);
MILLISECONDS_PER_INCH
控制了RecyclerView的滚动速度。
这里还有一点,position是我们不知道的,我们需要计算出position值,也就是求出目标位置。
我们需要3个参数,分别是layoutManager
、velocityX
、velocityY
layoutManager
:布局管理器对象,可以根据布局管理器求出当前位置。
velocityX
:X轴滚动速度,负数则为反方向滚动,正数则为正方向滚动,可以确定X轴方向的手势方向;
velocityY
:Y轴滚动速度,负数则为反方向滚动,正数则为正方向滚动,可以确定Y轴方向的手势方向;
假设手指每次滑动只滚动一个Item
目标位置=当前位置 +(或-) 1
代码如下:
private int getTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) {
final int itemCount = layoutManager.getItemCount();
if (itemCount == 0) {
return RecyclerView.NO_POSITION;
}
View mStartMostChildView = null;
if (layoutManager.canScrollVertically()) {
mStartMostChildView = findStartView(layoutManager, getVerticalHelper(layoutManager));
} else if (layoutManager.canScrollHorizontally()) {
mStartMostChildView = findStartView(layoutManager, getHorizontalHelper(layoutManager));
}
if (mStartMostChildView == null) {
return RecyclerView.NO_POSITION;
}
final int centerPosition = layoutManager.getPosition(mStartMostChildView);
if (centerPosition == RecyclerView.NO_POSITION) {
return RecyclerView.NO_POSITION;
}
//方向 true:手指向上或者向左滑动(滚动条向下或向右滚动) false:向右或者向下滑动(滚动条向左或向上滚动)
final boolean forwardDirection;
if (layoutManager.canScrollHorizontally()) {
forwardDirection = velocityX > 0;
} else {
forwardDirection = velocityY > 0;
}
int lastPosition = centerPosition - 1 < 0 ? 0 : centerPosition - 1;
int nextPosition = centerPosition + 1 > itemCount ? itemCount : centerPosition + 1;
return forwardDirection ? nextPosition : lastPosition;
}
以上目标位置的计算真的正确吗?答案是当然不正确,如果使用以上的计算方式,那么向左滚动时,有可能连滚动两个Item的情况,所以需要改成:
int lastPosition = centerPosition< 0 ? 0 : centerPosition;
int nextPosition = centerPosition + 1 > itemCount ? itemCount : centerPosition + 1;
return forwardDirection ? nextPosition : lastPosition;
以上解决方案虽然解决了连续滚动两个Item的情况,但是真的就没有问题了吗?答案是仍然有问题。因为它没有考虑到反向布局的情况,比如LinearLayoutManager
类中提供了setReverseLayout
方法:
//设置成反向布局
linearLayoutManager.setReverseLayout(true);
所以,我们还需要考虑到反向布局的情况,修改后的代码如下:
private int getTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) {
int itemCount = layoutManager.getItemCount();
if (itemCount == 0) {
return RecyclerView.NO_POSITION;
}
View mStartMostChildView = null;
if (layoutManager.canScrollVertically()) {
mStartMostChildView = findStartView(layoutManager, getVerticalHelper(layoutManager));
} else if (layoutManager.canScrollHorizontally()) {
mStartMostChildView = findStartView(layoutManager, getHorizontalHelper(layoutManager));
}
if (mStartMostChildView == null) {
return RecyclerView.NO_POSITION;
}
final int centerPosition = layoutManager.getPosition(mStartMostChildView);
if (centerPosition == RecyclerView.NO_POSITION) {
return RecyclerView.NO_POSITION;
}
//方向 true:手指向上或者向左滑动(滚动条向下或向右滚动) false:向右或者向下滑动(滚动条向左或向上滚动)
final boolean forwardDirection;
if (layoutManager.canScrollHorizontally()) {
forwardDirection = velocityX > 0;
} else {
forwardDirection = velocityY > 0;
}
boolean reverseLayout = false;
if ((layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
RecyclerView.SmoothScroller.ScrollVectorProvider vectorProvider = (RecyclerView.SmoothScroller.ScrollVectorProvider) layoutManager;
//Vector是向量的意思,显而易见,computeScrollVectorForPosition是为了计算布局的方向
PointF vectorForEnd = vectorProvider.computeScrollVectorForPosition(centerPosition);
if (vectorForEnd != null) {
reverseLayout = vectorForEnd.x < 0 || vectorForEnd.y < 0;
}
}
return reverseLayout
? (forwardDirection ? centerPosition - 1 : centerPosition)
: (forwardDirection ? centerPosition + 1 : centerPosition);
}
核心代码是computeScrollVectorForPosition
,Vector是向量的意思,显而易见,computeScrollVectorForPosition是为了计算布局的方向。
当我们稍微移动列表时,经常停止在当前位置,如图:
图片.png感觉界面卡主一样,我们理想的效果是位置能够自动矫正
,我们看如下代码
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
boolean mScrolled = false;
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE && mScrolled) {
//这里编写矫正位置的代码
mScrolled = false;
}
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dx != 0 || dy != 0) {
mScrolled = true;
}
}
});
监听RecyclerView的滚动事件,它有两个回调方法:
- onScrolled
dx为x轴速度向量,等于0表示没有滚动,小于0表示反方向滚动,大于0表示正方向滚动。
dy为y轴速度向量,等于0表示没有滚动,小于0表示反方向滚动,大于0表示正方向滚动。
当dx和dy都为0时,表示没有滚动,当其中有一个不为0,则说明已滚动,mScrolled变量为true时,说明为滚动状态。
- onScrollStateChanged
scrollState有三种状态,分别是开始滚动SCROLL_STATE_FLING
,正在滚动SCROLL_STATE_TOUCH_SCROLL
, 已经停止SCROLL_STATE_IDLE
。
当滚动状态已停止,并且mScrolled = true
时,开始编写矫正位置的代码。
[第一步]
:计算当前中间位置并获取中间Item的对象
private View findSnapView(RecyclerView.LayoutManager layoutManager) {
if (layoutManager.canScrollVertically()) {
return findCenterView(layoutManager, getVerticalHelper(layoutManager));
} else if (layoutManager.canScrollHorizontally()) {
return findCenterView(layoutManager, getHorizontalHelper(layoutManager));
}
return null;
}
private View findCenterView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) {
int childCount = layoutManager.getChildCount();
if (childCount == 0) {
return null;
}
View closestChild = null;
final int center;
if (layoutManager.getClipToPadding()) {
center = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
} else {
center = helper.getEnd() / 2;
}
int absClosest = Integer.MAX_VALUE;
for (int i = 0; i < childCount; i++) {
final View child = layoutManager.getChildAt(i);
int childCenter = helper.getDecoratedStart(child) + (helper.getDecoratedMeasurement(child) / 2);
int absDistance = Math.abs(childCenter - center);
if (absDistance < absClosest) {
absClosest = absDistance;
closestChild = child;
}
}
return closestChild;
}
[第二步]
:计算出最终滚动的位置
private int[] calculateDistanceToFinalSnap(RecyclerView.LayoutManager layoutManager, View targetView) {
int[] out = new int[2];
if (layoutManager.canScrollHorizontally()) {
out[0] = distanceToCenter(layoutManager, targetView, getHorizontalHelper(layoutManager));
} else {
out[0] = 0;
}
if (layoutManager.canScrollVertically()) {
out[1] = distanceToCenter(layoutManager, targetView, getVerticalHelper(layoutManager));
} else {
out[1] = 0;
}
return out;
}
private int distanceToCenter(RecyclerView.LayoutManager layoutManager, View targetView, OrientationHelper helper) {
final int childCenter = helper.getDecoratedStart(targetView) + (helper.getDecoratedMeasurement(targetView) / 2);
final int containerCenter;
if (layoutManager.getClipToPadding()) {
containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
} else {
containerCenter = helper.getEnd() / 2;
}
return childCenter - containerCenter;
}
说明:文章最后会贴出全部代码。
效果如下:
108.gif我们发现,矫正位置时,它的滚动速度和正常滑动的速度不一致,看起来很不协调,为了处理这种情况我们必须重写LinearSmoothScroller
类的onTargetFound
方法,原来的滚动距离的计算已经不适合这个需求了 ,原来的如下:
protected void onTargetFound(View targetView, State state, Action action) {
int dx = this.calculateDxToMakeVisible(targetView, this.getHorizontalSnapPreference());
int dy = this.calculateDyToMakeVisible(targetView, this.getVerticalSnapPreference());
int distance = (int)Math.sqrt((double)(dx * dx + dy * dy));
int time = this.calculateTimeForDeceleration(distance);
if (time > 0) {
action.update(-dx, -dy, time, this.mDecelerateInterpolator);
}
}
为了完成最后的矫正工作,为了将Item矫正到屏幕的中央,我们重新计算了最终的distance
,所以当滚动停止时,我们需要按照矫正的规则重新计算滚动向量
、滚动距离
、时间
。代码如下:
@Override
protected void onTargetFound(View targetView, RecyclerView.State state, RecyclerView.SmoothScroller.Action action) {
int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(), targetView);
final int dx = snapDistances[0];
final int dy = snapDistances[1];
final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));
if (time > 0) {
action.update(dx, dy, time, mDecelerateInterpolator);
}
}
当调用
recycleview.smoothScrollToPosition(position);
时,如果需要调整滚动速度,可以重写布局管理器,可随意控制滚动速度,代码如下:
public class MyCustomLayoutManager extends LinearLayoutManager {
private float MILLISECONDS_PER_INCH = 25f; //修改可以改变数据,越大速度越慢
private Context contxt;
public MyCustomLayoutManager(Context context) {
super(context);
this.contxt = context;
}
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
}
@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {
@Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return MyCustomLayoutManager.this.computeScrollVectorForPosition(targetPosition);
}
@Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return MILLISECONDS_PER_INCH / displayMetrics.density; //返回滑动一个pixel需要多少毫秒
}
};
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
}
//可以用来设置速度
public void setSpeedSlow(float x) {
MILLISECONDS_PER_INCH = contxt.getResources().getDisplayMetrics().density * 0.3f + (x);
}
}
最后,我贴一下代码:
/**
* 现在开始自定义RecyclerView
*/
public class MyCustomRecyclerView extends RecyclerView {
private OrientationHelper mVerticalHelper;
private OrientationHelper mHorizontalHelper;
public MyCustomRecyclerView(@NonNull Context context) {
this(context, (AttributeSet)null);
}
public MyCustomRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MyCustomRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init(){
this.setOnFlingListener(new MyCustonFling(this));
}
}
public class MyCustonFling extends RecyclerView.OnFlingListener {
/**
* 值越大,滑动速度越慢, 源码默认速度是25F
*/
static final float MILLISECONDS_PER_INCH = 125f;
OrientationHelper mVerticalHelper;
OrientationHelper mHorizontalHelper;
private RecyclerView mRecyclerView;
public MyCustonFling(RecyclerView recyclerView){
mRecyclerView = recyclerView;
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
boolean mScrolled = false;
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE && mScrolled) {
mScrolled = false;
snapToCenter();
}
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dx != 0 || dy != 0) {
mScrolled = true;
}
}
});
}
@Override
public boolean onFling(int velocityX, int velocityY) {
RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
if (layoutManager == null) {
return false;
}
RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
if (adapter == null) {
return false;
}
//获取最小滑动速度
int minFlingVelocity = mRecyclerView.getMinFlingVelocity();
//计算返回值,true:终止滚动 false:继续滚动
return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity) && snapFromFling(layoutManager, velocityX, velocityY);
}
private boolean snapFromFling(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) {
if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
return false;
}
RecyclerView.SmoothScroller smoothScroller = createSnapScroller(layoutManager);
if (smoothScroller == null) {
return false;
}
int targetPosition = getTargetSnapPosition(layoutManager, velocityX, velocityY);
if (targetPosition == RecyclerView.NO_POSITION) {
return false;
}
smoothScroller.setTargetPosition(targetPosition);
layoutManager.startSmoothScroll(smoothScroller);
return true;
}
private LinearSmoothScroller createSnapScroller(RecyclerView.LayoutManager layoutManager) {
if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
return null;
}
return new LinearSmoothScroller(mRecyclerView.getContext()) {
@Override
protected void onTargetFound(View targetView, RecyclerView.State state, RecyclerView.SmoothScroller.Action action) {
int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(), targetView);
final int dx = snapDistances[0];
final int dy = snapDistances[1];
final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));
if (time > 0) {
action.update(dx, dy, time, mDecelerateInterpolator);
}
}
@Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
}
};
}
private View findStartView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) {
int childCount = layoutManager.getChildCount();
if (childCount == 0) {
return null;
}
View closestChild = null;
int startest = Integer.MAX_VALUE;
for (int i = 0; i < childCount; i++) {
final View child = layoutManager.getChildAt(i);
int childStart = helper.getDecoratedStart(child);
if (childStart < startest) {
startest = childStart;
closestChild = child;
}
}
return closestChild;
}
private int getTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) {
int itemCount = layoutManager.getItemCount();
if (itemCount == 0) {
return RecyclerView.NO_POSITION;
}
View mStartMostChildView = null;
if (layoutManager.canScrollVertically()) {
mStartMostChildView = findStartView(layoutManager, getVerticalHelper(layoutManager));
} else if (layoutManager.canScrollHorizontally()) {
mStartMostChildView = findStartView(layoutManager, getHorizontalHelper(layoutManager));
}
if (mStartMostChildView == null) {
return RecyclerView.NO_POSITION;
}
final int centerPosition = layoutManager.getPosition(mStartMostChildView);
if (centerPosition == RecyclerView.NO_POSITION) {
return RecyclerView.NO_POSITION;
}
//方向 true:手指向上或者向左滑动(滚动条向下或向右滚动) false:向右或者向下滑动(滚动条向左或向上滚动)
final boolean forwardDirection;
if (layoutManager.canScrollHorizontally()) {
forwardDirection = velocityX > 0;
} else {
forwardDirection = velocityY > 0;
}
boolean reverseLayout = false;
if ((layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
RecyclerView.SmoothScroller.ScrollVectorProvider vectorProvider = (RecyclerView.SmoothScroller.ScrollVectorProvider) layoutManager;
//Vector是向量的意思,显而易见,computeScrollVectorForPosition是为了计算布局的方向
PointF vectorForEnd = vectorProvider.computeScrollVectorForPosition(centerPosition);
if (vectorForEnd != null) {
reverseLayout = vectorForEnd.x < 0 || vectorForEnd.y < 0;
}
}
return reverseLayout
? (forwardDirection ? centerPosition - 1 : centerPosition)
: (forwardDirection ? centerPosition + 1 : centerPosition);
}
private int[] calculateDistanceToFinalSnap(RecyclerView.LayoutManager layoutManager, View targetView) {
int[] out = new int[2];
if (layoutManager.canScrollHorizontally()) {
out[0] = distanceToCenter(layoutManager, targetView, getHorizontalHelper(layoutManager));
} else {
out[0] = 0;
}
if (layoutManager.canScrollVertically()) {
out[1] = distanceToCenter(layoutManager, targetView, getVerticalHelper(layoutManager));
} else {
out[1] = 0;
}
return out;
}
private int distanceToCenter(RecyclerView.LayoutManager layoutManager, View targetView, OrientationHelper helper) {
final int childCenter = helper.getDecoratedStart(targetView) + (helper.getDecoratedMeasurement(targetView) / 2);
final int containerCenter;
if (layoutManager.getClipToPadding()) {
containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
} else {
containerCenter = helper.getEnd() / 2;
}
return childCenter - containerCenter;
}
private OrientationHelper getVerticalHelper(RecyclerView.LayoutManager layoutManager) {
if (mVerticalHelper == null) {
mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager);
}
return mVerticalHelper;
}
private OrientationHelper getHorizontalHelper(RecyclerView.LayoutManager layoutManager) {
if (mHorizontalHelper == null) {
mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager);
}
return mHorizontalHelper;
}
/**
* 矫正位置的代码
* 将Item移动到中央
*/
void snapToCenter() {
RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
if (layoutManager == null) {
return;
}
View snapView = findSnapView(layoutManager);
if (snapView == null) {
return;
}
int[] snapDistance = calculateDistanceToFinalSnap(layoutManager, snapView);
if (snapDistance[0] != 0 || snapDistance[1] != 0) {
//当X轴Y轴有偏移时,开始矫正位置
mRecyclerView.smoothScrollBy(snapDistance[0], snapDistance[1]);
} else {
//当X轴Y轴没有有偏移时的处理
onSnap(snapView);
}
}
/**
* 滑动到中间停止时的回调
* @param snapView
*/
protected void onSnap(View snapView) {
//当滑动到屏幕中央时的处理
}
private View findSnapView(RecyclerView.LayoutManager layoutManager) {
if (layoutManager.canScrollVertically()) {
return findCenterView(layoutManager, getVerticalHelper(layoutManager));
} else if (layoutManager.canScrollHorizontally()) {
return findCenterView(layoutManager, getHorizontalHelper(layoutManager));
}
return null;
}
private View findCenterView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) {
int childCount = layoutManager.getChildCount();
if (childCount == 0) {
return null;
}
View closestChild = null;
final int center;
if (layoutManager.getClipToPadding()) {
center = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
} else {
center = helper.getEnd() / 2;
}
int absClosest = Integer.MAX_VALUE;
for (int i = 0; i < childCount; i++) {
final View child = layoutManager.getChildAt(i);
int childCenter = helper.getDecoratedStart(child) + (helper.getDecoratedMeasurement(child) / 2);
int absDistance = Math.abs(childCenter - center);
if (absDistance < absClosest) {
absClosest = absDistance;
closestChild = child;
}
}
return closestChild;
}
}
[本章完...]
网友评论