LinearSnapHelper源码解析

原文出处:Jameson's Blog 

Google最新发布的support v4包更新到24.2.0,由原来的一个大包分割成多个小module。这次来聊聊RecyclerView的新特性和SnapHelper的关系。

com.android.support:support-compat:24.2.0
com.android.support:support-core-utils:24.2.0
com.android.support:support-core-ui:24.2.0
com.android.support:support-media-compat:24.2.0
com.android.support:support-fragment:24.2.0

一句话介绍SnapHelper: SnapHelper是RecyclerView功能的一种拓展,使RecyclerView滑动行为类似ViewPager,无论怎么滑动最终停留在某页正中间。ViewPager一次只能滑动一页,RecyclerView+SnapHelper方式可以一次滑动好几页,且最终都停留在某页正中间。非常实用和酷炫。
SnapHelper的实现原理是监听RecyclerView.OnFlingListener中的onFling接口。LinearSnapHelper是抽象类SnapHelper的具体实现。

SnapHelper.gif

上面的效果只需下面几行代码即可。重点在于new LinearSnapHelper().attachToRecyclerView(recyclerView);

LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(linearLayoutManager);
new LinearSnapHelper().attachToRecyclerView(recyclerView);

接下来具体分析LinearSnapHelper是怎么实现类似ViewPager的功能的

attachToRecyclerView,居中处理分析

public void attachToRecyclerView(@Nullable RecyclerView recyclerView) throws IllegalStateException {
   ...
   snapToTargetExistingView();
   ...
}
/**
 * 1. 找到居中显示的View
 * 2. 计算view离当前的位置距离, 调用mRecyclerView.smoothScrollBy使其居中
 */
private void snapToTargetExistingView() {
    View snapView = findSnapView(layoutManager);
    int\[\] snapDistance = calculateDistanceToFinalSnap(layoutManager, snapView);
    if (snapDistance\[0\] != 0 || snapDistance\[1\] != 0) {
        mRecyclerView.smoothScrollBy(snapDistance\[0\], snapDistance\[1\]);
    }
}
/**
 * 1. 找到当前RecyclerView的居中位置center
 * 2. 循环遍历子节点,找出子节点居中位置最接近center的视图,及SnapView
 */
public View findSnapView(RecyclerView.LayoutManager layoutManager) {
    ...
}
/**
 * 计算到targetView要移动的距离
 */
@Override
public int\[\] calculateDistanceToFinalSnap(
        @NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) {
      ... 
}

RecyclerView.OnFling,滑动后停止到居中位置分析

SnapHelper extends RecyclerView.OnFlingListener,重载onFling函数

public boolean onFling(int velocityX, int velocityY) {
    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);
}
/**
 * 同样的套路,先根据移动速度确定最终位置,然后startSmoothScroll
 */
private boolean snapFromFling(@NonNull LayoutManager layoutManager, int velocityX,
        int velocityY) {
    ...
    int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY);
    if (targetPosition == RecyclerView.NO_POSITION) {
        return false;
    }
    smoothScroller.setTargetPosition(targetPosition);
    layoutManager.startSmoothScroll(smoothScroller);
    return true;
}

代码下载

https://github.com/huazhiyuan2008/RecyclerViewCardGallery