1、SnapHelper说明
SnapHelper是个辅助类,用于辅助RecyclerView在滚动结束时将Item对齐到某个位置。SnapHelper是个抽象类,官方提供了LinearSnapHelper、PagerSnapHelper子类。例如LinearSnapHelper可以让RecyclerView滚动停止时相应的Item停留中间位置。PagerSnapHelper可以使RecyclerView像ViewPager一样的效果,一次只能滑一页,而且居中显示。
2、SnapHelper使用
new LinearSnapHelper().attachToRecyclerView(mRecyclerView);
//或者
new PagerSnapHelper().attachToRecyclerView(mRecyclerView);
创建SnapHelper对象之后调用attachToRecyclerView()附着到对应的RecyclerView对象上就可以了。
3、Fling操作
Fling操作从手指离开屏幕瞬间被触发,在滚动停止时结束。手指在屏幕上滑动RecyclerView然后松手,RecyclerView中的内容会顺着惯性继续往手指滑动的方向继续滚动直到停止,这个过程叫做Fling。
4、SnapHelper三个抽象方法说明
(1)
public abstract int findTargetSnapPosition(LayoutManager layoutManager, int velocityX, int velocityY);
说明:该方法会根据触发Fling操作的速率(参数velocityX和参数velocityY)来找到RecyclerView需要滚动到哪个位置,该位置对应的ItemView就是那个需要进行对齐的列表项。我们把这个位置称为targetSnapPosition,对应的View称为targetSnapView。如果找不到targetSnapPosition,就返回RecyclerView.NO_POSITION。
(2)
public abstract View findSnapView(LayoutManager layoutManager);
说明:该方法会找到当前layoutManager上最接近对齐位置的那个view,该view称为SanpView,对应的position称为SnapPosition。如果返回null,就表示没有需要对齐的View,也就不会做滚动对齐调整。
(3)
public abstract int[] calculateDistanceToFinalSnap(LayoutManager layoutManager, View targetView);
说明:这个方法会计算第二个参数对应的View当前的坐标与需要对齐的坐标之间的距离。该方法返回一个大小为2的int数组,分别对应x轴和y轴方向上的距离。
5、SnapHelper源码分析
(1)attachToRecyclerView
public void attachToRecyclerView(@Nullable RecyclerView recyclerView)
throws IllegalStateException {
if (mRecyclerView == recyclerView) {
return; // nothing to do
}
if (mRecyclerView != null) {
destroyCallbacks();
}
mRecyclerView = recyclerView;
if (mRecyclerView != null) {
setupCallbacks();
mGravityScroller = new Scroller(mRecyclerView.getContext(),
new DecelerateInterpolator());
snapToTargetExistingView();
}
}
说明:(1)如果snaphelper之前已经添加到该recyclerView上,则什么都不做。(2)如果snaphelper之前依附的recyclerView和现在的不一致,则移除之前recyclerView的所有回调。(3)将snaphelper依附到新的recyclerView上,并设置新的回调。(4)创建一个Scroller对象mGravityScroller,用于辅助计算fling操作。(5)调用snapToTargetExistingView方法实现对SnapView进行对齐滚动处理。
(2)destroyCallbacks
/**
* Called when the instance of a {@link RecyclerView} is detached.
*/
private void destroyCallbacks() {
mRecyclerView.removeOnScrollListener(mScrollListener);
mRecyclerView.setOnFlingListener(null);
}
说明:snaphelper依附的recyclerView移除ScrollListener和FlingListener。
(3)setupCallbacks
/**
* Called when an instance of a {@link RecyclerView} is attached.
*/
private void setupCallbacks() throws IllegalStateException {
if (mRecyclerView.getOnFlingListener() != null) {
throw new IllegalStateException("An instance of OnFlingListener already set.");
}
mRecyclerView.addOnScrollListener(mScrollListener);
mRecyclerView.setOnFlingListener(this);
}
说明:给recyclerView添加ScrollListener和FlingListener。
(4)snapToTargetExistingView
/**
* Snaps to a target view which currently exists in the attached {@link RecyclerView}. This
* method is used to snap the view when the {@link RecyclerView} is first attached; when
* snapping was triggered by a scroll and when the fling is at its final stages.
*/
void snapToTargetExistingView() {
if (mRecyclerView == null) {
return;
}
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) {
mRecyclerView.smoothScrollBy(snapDistance[0], snapDistance[1]);
}
}
说明:(1)使用findSnapView(layoutManager)找到离目标位置最近的snapview。(2)然后使用calculateDistanceToFinalSnap(layoutManager, snapView)计算snapview和目标位置之间的距离。(3)最后调用mRecyclerView.smoothScrollBy滚动到目标位置。
(5)mScrollListener
// Handles the snap on scroll case.
private final RecyclerView.OnScrollListener mScrollListener =
new RecyclerView.OnScrollListener() {
boolean mScrolled = false;
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE && mScrolled) {
mScrolled = false;
snapToTargetExistingView();
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (dx != 0 || dy != 0) {
mScrolled = true;
}
}
};
说明:滚动监听器实现逻辑是,在正常滚动停止的时候调用了snapToTargetExistingView()方法对targetView进行滚动调整,以确保停止的位置是在对应的坐标上,这就是RecyclerView添加该OnScrollListener的目的。
(6)OnFlingListener
@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();
return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity)
&& snapFromFling(layoutManager, velocityX, velocityY);
}
说明:(1)获取RecyclerView要进行fling操作需要的最小速率,只有超过该速率,ItemView才会有足够的动力在手指离开屏幕时继续滚动下去。(2)这里会调用snapFromFling()这个方法,就是通过该方法实现平滑滚动并使得在滚动停止时itemView对齐到目的坐标位置。
(7)snapFromFling
private boolean snapFromFling(@NonNull RecyclerView.LayoutManager layoutManager, int velocityX,
int velocityY) {
if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
return false;
}
RecyclerView.SmoothScroller smoothScroller = createScroller(layoutManager);
if (smoothScroller == null) {
return false;
}
int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY);
if (targetPosition == RecyclerView.NO_POSITION) {
return false;
}
smoothScroller.setTargetPosition(targetPosition);
layoutManager.startSmoothScroll(smoothScroller);
return true;
}
说明:(1)创建一个平滑滚动器smoothScroller,用于对ItemView进行平滑滚动操作。(2)通过findTargetSnapPosition()方法,根据速率找到targetSnapPosition。(3)设置滚动器的滚动目标位置,并启动平滑滚动器开始滚动到目标位置。