SnapHelper学习记录

4,425 阅读3分钟
原文链接: blog.csdn.net

1. 简介

先看看英文介绍

/**
* Class intended to support snapping for a {@link RecyclerView}.
*


* SnapHelper tries to handle fling as well but for this to work properly, the
* {@link RecyclerView.LayoutManager} must implement the {@link ScrollVectorProvider} interface or
* you should override {@link #onFling(int, int)} and handle fling manually.
*/

可以看出SnapHelper是对RecyclerView的一种拓展功能,支持snapping。SnapHelper通过处理RecyclerView的fling,来达到要展示的效果,但是这需要一个前提条件,就是RecyclerView的LayoutManager必须实现ScrollVectorProvider接口或者手动实现onFling接口,自己实现fling处理。

SnapHelper的实现原理是监听RecyclerView.OnFlingListener中的onFling接口。LinearSnapHelper&PagerSnapHelper是抽象类SnapHelper的具体实现。
区别在于:
LinerSnapHelper,可滑动多页,居中显示;
PagerSnapHelper,每次只能滑动一页,居中显示;


2. 基本实现

2.1 横向LinearSnapHelper

LinearLayoutManager llm = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
CardAdapter cardAdapter = new CardAdapter(this);
LinearSnapHelper linearSnapHelper = new LinearSnapHelper();

mRvContent.setLayoutManager(llm);
mRvContent.setAdapter(cardAdapter);
linearSnapHelper.attachToRecyclerView(mRvContent);

这里写图片描述

2.2 纵向LinearSnapHelper

LinearLayoutManager llm = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
CardAdapter cardAdapter = new CardAdapter(this);
LinearSnapHelper linearSnapHelper = new LinearSnapHelper();

mRvContent.setLayoutManager(llm);
mRvContent.setAdapter(cardAdapter);
linearSnapHelper.attachToRecyclerView(mRvContent);

这里写图片描述

2.3 PagerSnapHelper(以横向为例)

LinearLayoutManager llm = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
CardAdapter cardAdapter = new CardAdapter(this);
PagerSnapHelper pagerSnapHelper = new PagerSnapHelper();

mRvContent.setLayoutManager(llm);
mRvContent.setAdapter(cardAdapter);
pagerSnapHelper.attachToRecyclerView(mRvContent);

这里写图片描述

一般步骤:
1. 创建SnapHelper;
2. attachToRecyclerView。


3. 源码分析

SnapHelper源码结构:

这里写图片描述

SnapHelper是一个抽象类,无构造方法。其中有三个抽象方法是需要子类去实现的

这里写图片描述

方法名 描述
calculateDistanceToFinalSnap 计算第二个参数对应的ItemView当前的坐标与需要对齐的坐标之间的距离。返回一个int[2]数组,分别对应x轴和y轴方向上的距离
findSnapView 找到最接近对齐位置的view,该view称为SanpView,对应的position称为SnapPosition。如果返回null,就表示没有需要对齐的View,也就不会做滚动对齐调整
findTargetSnapPosition 根据Fling操作的速率(参数velocityX和参数velocityY)找到需要滚动到的targetSnapPosition,该位置对应的View就是targetSnapView。如果找不到targetSnapPosition,就返回RecyclerView.NO_POSITION

3.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.判断绑定的RecyclerView 和之前的是否相同,不重复绑定; 2.如果传入的RecyclerView不为空,则执行destoryCallbacks(), 移除滚动监听和onFlingListener

 private void destroyCallbacks() {
        mRecyclerView.removeOnScrollListener(mScrollListener);
        mRecyclerView.setOnFlingListener(null);
    }

3.赋值成员变量mRecyclerView 4.setupCallbacks()和destoryCallbacks()对应

   private void setupCallbacks() throws IllegalStateException {
        //如果onFlingListener已经设置过,则抛出异常
        if (mRecyclerView.getOnFlingListener() != null) {
            throw new IllegalStateException("An instance of OnFlingListener already set.");
        }
        mRecyclerView.addOnScrollListener(mScrollListener);
        mRecyclerView.setOnFlingListener(this);
    }

5.创建mGravityScroller
6.snapToTargetExistingView()
这个方法在这里调用一次,其实更关键的调用地方是在OnScrollListener中

    // 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;
                        //当滑动状态为idle状态时并且刚结束滚动
                        snapToTargetExistingView();
                    }
                }

                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                    if (dx != 0 || dy != 0) {
                        mScrolled = true;
                    }
                }
            };
    void snapToTargetExistingView() {
        if (mRecyclerView == null) {
            return;
        }
        LayoutManager layoutManager = mRecyclerView.getLayoutManager();
        if (layoutManager == null) {
            return;
        }
        //找到SnapView
        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]);
        }
    }

3.2 onFling

Fling操作: 手指在屏幕上滑动RecyclerView后松手,RecyclerView中的内容会靠着惯性继续往之前滑动的方向继续滚动直到停止,这个过程叫做Fling。Fling操作从手指离开屏幕瞬间被触发,在滚动停止时结束。

刚才在setupCallbacks()中设置的onFlingerListener

    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;
        }
        //获取触发fling的最小速度
        int minFlingVelocity = mRecyclerView.getMinFlingVelocity();
        //横向或者纵向必须有一个滑动速度可以触发fling,snapFromFling和上面滚动监听器中的实现思路类似
        return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity)
                && snapFromFling(layoutManager, velocityX, velocityY);
    }
    private boolean snapFromFling(@NonNull LayoutManager layoutManager, int velocityX,
            int velocityY) {
         //这就是为何前文中提到需要实现ScrollVectorProvider否则旧得自己实现onFling接口
        if (!(layoutManager instanceof ScrollVectorProvider)) {
            return false;
        }

        //创建scroller
        RecyclerView.SmoothScroller smoothScroller = createSnapScroller(layoutManager);
        if (smoothScroller == null) {
            return false;
        }

        //找到目标snap位置
        int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY);
        if (targetPosition == RecyclerView.NO_POSITION) {
            return false;
        }

        //平滑的滚动过去
        smoothScroller.setTargetPosition(targetPosition);
        layoutManager.startSmoothScroll(smoothScroller);
        return true;
    }

有兴趣的盆友可以去查阅下LinearSnapHelper和PagerSnapHelper的源码。
推荐源码阅读网站
androidxref.com


4. 源码

RecyclerViewGallery


5. 推荐开源库

RecyclerViewSnap