SnapHelpter,相信很多人可能都不知道它或者没怎么关注过它,但是通过它实现的效果肯定都见过。比如短视频应用中切换视频时一划划一页的效果,这可不是ViewPager实现的啊,使用ViewPager实现的话成本太高,所以这类效果都是通过RecyclerVIew + SnapHelper来实现的,拿刚才讲的短视频切换效果来说,使用的就是RecyclerVIew和SnapHelper的子类PagerSnapHelper来实现的。

目录

  • 一、SnapHelper初解
  • 二、三个方法
    • 1、calculateDistanceToFinalSnap()
    • 2、findSnapView()
    • 3、findTargetSnapPosition()
  • 三、自定义SnapHelper实战

一、SnapHelper初解

说了这些,那么SnapHelper到底是什么东西呢?见名思意,Snap,翻译成中文有‘移到某位置’的意思,那么SnapHelper可以理解为‘移到某位置的帮手’,而这个被移到某位置的东西显然就是RecyclerVIew中的Item。

public abstract class SnapHelper extends RecyclerView.OnFlingListener {//....@Nullable
public abstract int[] calculateDistanceToFinalSnap(@NonNull LayoutManager layoutManager,@NonNull View targetView);@Nullable
public abstract View findSnapView(LayoutManager layoutManager);public abstract int findTargetSnapPosition(LayoutManager layoutManager, int velocityX,int velocityY);
}

可以看到SnapHelper是一个抽象类,并继承了RecyclerView.OnFlingListener这个类,其中还包括三个抽象方法,我们通过实现这三个方法,就可以帮助RecyclerView移动item到‘某位置’。

为了更好理解SnapHelper的这三个方法,先说说RecyclerView.OnFlingListener这个类。

public abstract static class OnFlingListener {/*** 可用于实现自定义投掷行为** @param velocityX X轴上的抛掷速度* @param velocityY Y轴上的抛掷速度** @return 如果处理了投掷,则为 true,否则为 false。*/public abstract boolean onFling(int velocityX, int velocityY);
}

这也是个抽象类,并且里面只有一个抽象方法,那这个类又是干啥的呢?我们都知道RecyclerView是可以滑动的,在我们手指离开屏幕后,RecyclerView还会继续顺着我们手指的方向再滑动一段距离,这个操作就是通过实现OnFlingListener接口来做到的。

SnapHelper继承了OnFlingListener实现了onFling方法,并在调用attachToRecyclerView()方法的时候将OnFlingListener设置给了RecyclerView。

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();}}/*** 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);
}

二、三个方法

接着我们继续看SnapHelper中的三个抽象方法。

1、calculateDistanceToFinalSnap()

/*** 计算将目标item移动到最终位置所需距离** @param layoutManager * @param targetView 需要被移动的item** @return 输出坐标将结果,out[0] 是水平轴上的距离,out[1] 是垂直轴上的距离。*/
@SuppressWarnings("WeakerAccess")
@Nullable
public abstract int[] calculateDistanceToFinalSnap(@NonNull LayoutManager layoutManager,@NonNull View targetView);public abstract int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,@NonNull View targetView);

这个方法是SnapHelper中另外两个抽象方法findSnapView()和findTargetSnapPosition()的下游方法,其参数中的targetView就是这两个方法提供的

通过findSnapView()提供

void snapToTargetExistingView() {/***/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]);}
}

通过findTargetSnapPosition()提供

private boolean snapFromFling(@NonNull RecyclerView.LayoutManager layoutManager, int velocityX,int velocityY) {/**/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;
}

findTargetSnapPosition()被调用后,将找到的位置设置给smoothScroller,然后再通过layoutManager调用startSmoothScroll()方法启动smoothScroller

public void startSmoothScroll(SmoothScroller smoothScroller) {if (mSmoothScroller != null && smoothScroller != mSmoothScroller&& mSmoothScroller.isRunning()) {mSmoothScroller.stop();}mSmoothScroller = smoothScroller;mSmoothScroller.start(mRecyclerView, this);
}

在smoothScroller的start()方法中找到targetView

void start(RecyclerView recyclerView, LayoutManager layoutManager) {/***/mTargetView = findViewByPosition(getTargetPosition());onStart();mRecyclerView.mViewFlinger.postOnAnimation();mStarted = true;
}

最后回调到SnapHelper中创建的SmoothScroller中的onTargetFound()方法

@Nullable
@Deprecated
protected LinearSmoothScroller createSnapScroller(RecyclerView.LayoutManager layoutManager) {/***/return new LinearSmoothScroller(mRecyclerView.getContext()) {@Overrideprotected void onTargetFound(View targetView, RecyclerView.State state, 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);}}@Overrideprotected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;}};
}

2、findSnapView()

/*** 找到需要被移动的item.* 如果返回 {@code null}, 则SnapHelper 不需要移动任何item.** @param layoutManager** @return 需要被移动的item*/
@SuppressWarnings("WeakerAccess")
@Nullable
public abstract View findSnapView(LayoutManager layoutManager);

这个方法会在SnapHelper绑定到RecyclerView时和RecyclerView停止滑动时被调用

void snapToTargetExistingView() {/***/View snapView = findSnapView(layoutManager);/***/
}//绑定RecyclerView时被调用
public void attachToRecyclerView(@Nullable RecyclerView recyclerView)throws IllegalStateException {/***/if (mRecyclerView != null) {/***/snapToTargetExistingView();}
}//RecyclerView停止滑到时被调用
private final RecyclerView.OnScrollListener mScrollListener =new RecyclerView.OnScrollListener() {boolean mScrolled = false;@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {super.onScrollStateChanged(recyclerView, newState);if (newState == RecyclerView.SCROLL_STATE_IDLE && mScrolled) {mScrolled = false;snapToTargetExistingView();}}@Overridepublic void onScrolled(RecyclerView recyclerView, int dx, int dy) {/***/}};

3、findTargetSnapPosition()

/*** 找到需要被移动的目标item在adapter中的位置** @param layoutManager * @param 水平轴上的抛掷速度* @param 纵轴上的抛掷速度** @return 返回需要被移动的目标item在adapter中的位置或者无需移动时返回 {@link RecyclerView#NO_POSITION}*/
public abstract int findTargetSnapPosition(LayoutManager layoutManager, int velocityX,int velocityY);
}

这个方法会在RecyclerView触发fling操作时被调用

private boolean snapFromFling(@NonNull RecyclerView.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;
}@Overridepublic boolean onFling(int velocityX, int velocityY) {/***/return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity)&& snapFromFling(layoutManager, velocityX, velocityY);}

三、自定义SnapHelper实战

了解了SnapHelper三个方法的作用以及何时会调用后,我们趁热打铁,自己实现一个SnapHelper,如果想更多了解关于SnapHelper的实现,可以去看看官方实现的LinearSnapHelperPagerSnapHelper

这次我们继承SnapHelper,实现对RecyclerView一滑滑一页的效果,类似官方的PagerSnapHelper,但是比它更灵活,因为它的一页是一条item,我们的一页可以是多个item。

其实这次要实现的效果在很多App中都能看到,尤其是应用商城类的App。

光说还是有点懵,先看看实现的最终效果吧~

public class MyGallerySnapHelper extends SnapHelper {protected RecyclerView mRecyclerView;@Nullableprivate OrientationHelper mHorizontalHelper;private int pageSize;@Overridepublic void attachToRecyclerView(@Nullable RecyclerView recyclerView) throws IllegalStateException {mRecyclerView = recyclerView;super.attachToRecyclerView(recyclerView);}@Nullable@Overridepublic int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View view) {int[] out = new int[2];//RecyclerView为横向方向时if (layoutManager.canScrollHorizontally()) {out[0] = distance2Start(layoutManager, view,getHorizontalHelper(layoutManager));}return out;}private int distance2Start(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView, OrientationHelper helper) {//获取item的宽度int columnWidth = helper.getDecoratedMeasurement(targetView);//获取item的下标int position = layoutManager.getPosition(targetView);//计算RecyclerView一屏可以展示多少itempageSize = (mRecyclerView.getWidth() - mRecyclerView.getPaddingStart() - mRecyclerView.getPaddingEnd()) / getHorizontalHelper(layoutManager).getDecoratedMeasurement(targetView);//计算item处于第几屏int pageIndex = position / pageSize;//计算上一步所得屏数中第一个item的下标int currentPageStart = pageIndex * pageSize;//计算传入item和它所属屏数第一个item的距离int distance = ((position - currentPageStart)) * columnWidth;//获取传入item的顶部在RecyclerView中的位置(像素)final int childStart = helper.getDecoratedStart(targetView);return childStart - distance;}@Nullable@Overridepublic View findSnapView(RecyclerView.LayoutManager layoutManager) {return findStartView(layoutManager, getHorizontalHelper(layoutManager));}private View findStartView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) {int childCount = layoutManager.getChildCount();if (childCount == 0) return null;int lastPosition = 0;//获取最后一个完整可见item的下标if (layoutManager instanceof LinearLayoutManager) {lastPosition = ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition();}int absClosest = Integer.MAX_VALUE;View snapView = null;//如最后一个完整可见item的下标等于列表最后一个item的下标if (lastPosition == layoutManager.getItemCount() - 1) {snapView = layoutManager.getChildAt(lastPosition);} else {//找到距离RecyclerView顶部最近的itemfor (int i = 0; i < childCount; i++) {View child = layoutManager.getChildAt(i);int absDistance = helper.getDecoratedStart(child);if (absDistance < absClosest) {absClosest = absDistance;snapView = child;}}}return snapView;}@Overridepublic int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) {//找到距离RecyclerView顶部最近的itemView snapView = findSnapView(layoutManager);if (snapView == null) return RecyclerView.NO_POSITION;//得到距离RecyclerView顶部最近的item的下标int startMostPosition = layoutManager.getPosition(snapView);if (startMostPosition == RecyclerView.NO_POSITION) return RecyclerView.NO_POSITION;//滑动方向,ture为正方向滑动 false为反方向滑动final boolean forwardDirection;if (layoutManager.canScrollHorizontally()) {forwardDirection = velocityX > 0;} else {forwardDirection = velocityY > 0;}View childAt = layoutManager.getChildAt(0);//计算RecyclerView一屏可以展示多少itemif (childAt != null) {pageSize = (mRecyclerView.getWidth() - mRecyclerView.getPaddingStart() - mRecyclerView.getPaddingEnd()) / getHorizontalHelper(layoutManager).getDecoratedMeasurement(childAt);}//计算item处于第几屏int pageIndex = startMostPosition / pageSize;//计算上一步所得屏数中第一个item的下标int currentPageStart = pageIndex * pageSize;//根据滑动方向,在当前屏首的下标上加减数量return forwardDirection ? Math.min(currentPageStart + pageSize, layoutManager.getItemCount() - 1) : Math.max(0, currentPageStart + pageSize - 1);}@Nullable@Overrideprotected RecyclerView.SmoothScroller createScroller(RecyclerView.LayoutManager layoutManager) {return !(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider) ? null : new LinearSmoothScroller(this.mRecyclerView.getContext()) {protected void onTargetFound(View targetView, RecyclerView.State state, RecyclerView.SmoothScroller.Action action) {if (mRecyclerView != null) {int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(), targetView);int dx = snapDistances[0];int dy = snapDistances[1];int time = this.calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));if (time > 0) {action.update(dx, dy, time, this.mDecelerateInterpolator);}}}protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {return 50.0F / (float) displayMetrics.densityDpi;}};}@NonNullprivate OrientationHelper getHorizontalHelper(@NonNull RecyclerView.LayoutManager layoutManager) {if (this.mHorizontalHelper == null || this.mHorizontalHelper.getLayoutManager() != layoutManager) {this.mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager);}return this.mHorizontalHelper;}
}

写的有点粗糙,只支持了LayoutManager为LinearLayoutManager时的水平方向,没有竖直方向的逻辑,也没有RecyclerView倒序时的逻辑,也没多少代码,懒得写了,想用的自己加吧。。

RecyclerView的好朋友 — SnapHelpter相关推荐

  1. android 微信评论功能,Android仿微信朋友圈点击评论自动定位到相关行功能

    最近闲来无事,随便看看各种UI实现的代码 打开你的微信朋友圈,点击评论,你就会发现有一个小细节:文本输入框的高度恰好定位到这条信息的底部位置 这个实现起来其实很简单,咱们就来看看吧 最简单的Recyc ...

  2. android 微信评论功能,Android仿微信朋友圈点击评论自动定位到相关行功能.pdf

    Android仿仿微微信信朋朋友友圈圈点点击击评评论论自自动动定定位位到到相相关关行行功功能能 这篇文章主要介绍了android仿微信朋友圈点击评论自动定位到相关行功能的实现,本文图文并茂给大家介绍的 ...

  3. android 查找朋友圈,Android仿微信朋友圈点击评论自动定位到相关行功能

    最近闲来无事,随便看看各种UI实现的代码 打开你的微信朋友圈,点击评论,你就会发现有一个小细节:文本输入框的高度恰好定位到这条信息的底部位置 这个实现起来其实很简单,咱们就来看看吧 最简单的Recyc ...

  4. RecyclerView实现QQ空间和微信朋友圈头部刷新效果

    RecyclerView实现QQ空间和微信朋友圈头部刷新效果 老规矩先上图 本篇主要讲RecyclerView实现QQ空间和微信朋友圈头部刷新效果,如果想了解ListView如何实现,请查看上篇:Li ...

  5. android朋友圈九宫格用Recyclerview实现

    //效果图 //话不多说,代码先给上关键代码 if (StringUtils.isEmpty(list.get(position).getImage())) { holder.gridView.set ...

  6. 从选择到上传,可能是最贴心的高仿朋友圈编辑了

    Luban-Circle-Demo 自己要用的上这个,找轮子的时候正巧发现了一个不错的图片压缩轮子鲁班;正巧原repo主用的rxjava,我一直没有时间点亮这个技能树,所以把rxjava部分扣掉了;正 ...

  7. 微信朋友圈,QQ空间,微博等列表展示的功能实现

    内容摘要 该控件能够应用于内容资讯展示的功能模块中,如:腾讯和新浪微博的微博列表,微信朋友圈及其它社交类应用的好友动态展示列表等:实现了类似腾讯微博的微博列表展示功能,包含微博文本内容,表情,图片,话 ...

  8. android 快速 顶部,Android RecyclerView 快速滑到顶部

    前言: 使用RecyclerView时,调用smoothScrollToPostion()方法滑动到指定位置,但是条目很多时滑动的很慢,本篇文章就是实现RecyclerView的快速滑动. 先介绍如何 ...

  9. ScrollView(RecyclerView等)为什么会自动滚动原理分析,还有阻止自动滑动的解决方...

    2019独角兽企业重金招聘Python工程师标准>>> 引言,有一天我在调试一个界面,xml布局里面包含Scroll View,里面嵌套了recyclerView的时候,界面一进去, ...

最新文章

  1. PyTorch 笔记(01)— Ubuntu 使用 pip 清华源安装 PyTorch
  2. * 执行多条更新的Sql语句
  3. FreeDos 历史
  4. 分布式b2b b2c o2o电子商务云平台
  5. 阿里笔试题—战报交流
  6. 小程序获取sessionkey_小程序,足不出户获取更多客源
  7. 在一个数组中,如何确定所需元素在数组中的位置.
  8. 如何编写wenpack插件
  9. mysql 协议测试_mysqlslap压力测试mysql
  10. MariaDB 10.3 解决掉了UPDATE不支持同一张表的子查询更新
  11. 08.存储Cinder→5.场景学习→01.LVM Volume
  12. JDK 9.0版本下载安装
  13. 子目录和子域名哪个好?子目录和子域名如何利用seo优化?
  14. unity广告投放技巧_是否需要快速投放动画广告系列? 只要在Unity中做到
  15. 一个自动生成评论的小工具
  16. No.005<日常><工具表>《数学符号与希腊字母表》
  17. 可汗学院统计学笔记(一)
  18. Cython 是什么?为什么会有 Cython?
  19. python编写一个函数判断一个数是否为素数是则返回yes_编写函数,判断一个整数是否为素数,并编写主程序调用该函数。_学小易找答案...
  20. VC++中的各种文件的作用

热门文章

  1. 【相机标定】相机内参
  2. Google 正式发起 Android 汽车联盟
  3. JavaScript-高级进阶
  4. 计算H时M分S秒以后是_厨房风机选型设计及计算方法
  5. “住过一晚两万的ICU后,我还是建议你不要轻易买保险”
  6. 每天干的啥?(2018.02)
  7. yara 源码学习(三)  扫描部分
  8. Python 爬虫实战(三) 获取百度地图搜索结果
  9. 各种奇奇怪怪的编码,究竟怎么来的?
  10. 【数理方程】傅氏变换拉氏变换