Android SnapHelper 2

01.先看业务需求

  • LinearSnapHelper 实现了居中对齐,那么我们只要更改一下对齐的规则就行,更改为开始对齐(计算目标 View到 Parent start 要滑动的距离),其他的逻辑和 LinearSnapHelper 是一样的。因此我们选择继承 LinearSnapHelper
  • 大概流程
    • 重写calculateDistanceToFinalSnap方法,计算SnapView当前位置与目标位置的距离
    • 写findSnapView方法,找到当前时刻的SnapView
    • 可以发现完成上面两个方法就可以呢,但是感觉滑动效果不太好。滑动比较快时,会滚动很远。在分析了上面的代码可知,滚动速率,由createSnapScroller方法中的calculateSpeedPerPixel()方法决定。那么是不是可以修改一下速率就可以解决问题呢。最后测试真的可以,ok,完成了。
    • 当然还会发现滚动时候,会滑动多个item,如果相对item个数做限制,可以在findTargetSnapPosition()方法中处理。

02.自定义helper类

  • 重写calculateDistanceToFinalSnap方法

    • 这里需要知道,在LinearSnapHelper中,out[0]和out[1]是通过distanceToCenter获取的。那么既然要设置开始对齐,那么这里需要创建distanceToStart方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @Nullable
    @Override
    public int[] calculateDistanceToFinalSnap(RecyclerView.LayoutManager layoutManager, View targetView) {
    int[] out = new int[2];
    if (layoutManager.canScrollHorizontally()) {
    out[0] = distanceToStart(targetView, getHorizontalHelper(layoutManager));
    } else {
    out[0] = 0;
    }
    if (layoutManager.canScrollVertically()) {
    out[1] = distanceToStart(targetView, getVerticalHelper(layoutManager));
    } else {
    out[1] = 0;
    }
    return out;
    }

    private int distanceToStart(View targetView, OrientationHelper helper) {
    return helper.getDecoratedStart(targetView) - helper.getStartAfterPadding();
    }
  • 写findSnapView方法,找到当前时刻的SnapView

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    @Nullable
    @Override
    public View findSnapView(RecyclerView.LayoutManager layoutManager) {
    if (layoutManager instanceof LinearLayoutManager) {
    if (layoutManager.canScrollHorizontally()) {
    return findStartView(layoutManager, getHorizontalHelper(layoutManager));
    } else {
    return findStartView(layoutManager, getVerticalHelper(layoutManager));
    }
    }
    return super.findSnapView(layoutManager);
    }

    private View findStartView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) {
    if (layoutManager instanceof LinearLayoutManager) {
    int firstChild = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
    //需要判断是否是最后一个Item,如果是最后一个则不让对齐,以免出现最后一个显示不完全。
    boolean isLastItem = ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition()
    == layoutManager.getItemCount() - 1;
    if (firstChild == RecyclerView.NO_POSITION || isLastItem) {
    return null;
    }
    View child = layoutManager.findViewByPosition(firstChild);
    if (helper.getDecoratedEnd(child) >= helper.getDecoratedMeasurement(child) / 2
    && helper.getDecoratedEnd(child) > 0) {
    return child;
    } else {
    if (((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition()
    == layoutManager.getItemCount() - 1) {
    return null;
    } else {
    return layoutManager.findViewByPosition(firstChild + 1);
    }
    }
    }
    return super.findSnapView(layoutManager);
    }
  • 修改滚动速率

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    @Nullable
    protected LinearSmoothScroller createSnapScroller(final 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;
    final int dy;
    if (snapDistances != null) {
    dx = snapDistances[0];
    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;
    }
    };
    }