SnapHelper,对RecyclerView的功能拓展
来源:互联网 发布:ubuntu 拷贝文件命令 编辑:程序博客网 时间:2024/05/25 19:56
转载请注明出处:http://blog.csdn.net/ym4189/article/details/77373379
前言
SnapHelper是Google发布的support v4包24.2.0版本出来的。
SnapHelper是对RecyclerView功能的一种拓展,使RecyclerView滑动行为类似ViewPager,无论怎么滑动最终停留在某页正中间。
ViewPager一次只能滑动一页,RecyclerView+SnapHelper方式可以一次滑动好几页,且最终都停留在某页正中间。非常实用和酷炫。
SnapHelper的实现原理是监听RecyclerView.OnFlingListener中的onFling接口。LinearSnapHelper是抽象类SnapHelper的具体实现。
实现效果
1.LinearSnapHelper是自带的实现效果
类似ViewPager,将某页居中显示,实现也是很简单,只要下面的两行代码:
LinearSnapHelper mLinearSnapHelper = new LinearSnapHelper(); mLinearSnapHelper.attachToRecyclerView(recycleView);
我们来看下LinearSnapHelper是怎么实现SnapHelper的,其中主要实现3个方法:
1.calculateDistanceToFinalSnap()
@Override public int[] calculateDistanceToFinalSnap( @NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) { int[] out = new int[2]; if (layoutManager.canScrollHorizontally()) { out[0] = distanceToCenter(layoutManager, targetView, getHorizontalHelper(layoutManager)); } else { out[0] = 0; } if (layoutManager.canScrollVertically()) { out[1] = distanceToCenter(layoutManager, targetView, getVerticalHelper(layoutManager)); } else { out[1] = 0; } return out; }
当拖拽或滑动结束时会回调该方法,返回一个out = int[2],out[0]x轴,out[1] y轴 ,这个值就是需要修正的你需要的位置的偏移量 。
2.findSnapView()
@Override public View findSnapView(RecyclerView.LayoutManager layoutManager) { if (layoutManager.canScrollVertically()) { return findCenterView(layoutManager, getVerticalHelper(layoutManager)); } else if (layoutManager.canScrollHorizontally()) { return findCenterView(layoutManager, getHorizontalHelper(layoutManager)); } return null; }
看方法名就知道,找到对齐视图,就是上个方法的targetView。
3.findTargetSnapPosition()
@Override public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) { if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) { return RecyclerView.NO_POSITION; } final int itemCount = layoutManager.getItemCount(); if (itemCount == 0) { return RecyclerView.NO_POSITION; } final View currentView = findSnapView(layoutManager); if (currentView == null) { return RecyclerView.NO_POSITION; } final int currentPosition = layoutManager.getPosition(currentView); if (currentPosition == RecyclerView.NO_POSITION) { return RecyclerView.NO_POSITION; } RecyclerView.SmoothScroller.ScrollVectorProvider vectorProvider = (RecyclerView.SmoothScroller.ScrollVectorProvider) layoutManager; // deltaJumps sign comes from the velocity which may not match the order of children in // the LayoutManager. To overcome this, we ask for a vector from the LayoutManager to // get the direction. PointF vectorForEnd = vectorProvider.computeScrollVectorForPosition(itemCount - 1); if (vectorForEnd == null) { // cannot get a vector for the given position. return RecyclerView.NO_POSITION; } int vDeltaJump, hDeltaJump; if (layoutManager.canScrollHorizontally()) { hDeltaJump = estimateNextPositionDiffForFling(layoutManager, getHorizontalHelper(layoutManager), velocityX, 0); if (vectorForEnd.x < 0) { hDeltaJump = -hDeltaJump; } } else { hDeltaJump = 0; } if (layoutManager.canScrollVertically()) { vDeltaJump = estimateNextPositionDiffForFling(layoutManager, getVerticalHelper(layoutManager), 0, velocityY); if (vectorForEnd.y < 0) { vDeltaJump = -vDeltaJump; } } else { vDeltaJump = 0; } int deltaJump = layoutManager.canScrollVertically() ? vDeltaJump : hDeltaJump; if (deltaJump == 0) { return RecyclerView.NO_POSITION; } int targetPos = currentPosition + deltaJump; if (targetPos < 0) { targetPos = 0; } if (targetPos >= itemCount) { targetPos = itemCount - 1; } return targetPos; }
滑动结束时,用于OnFling,返回目标对齐项position 。
2.自定义SnapHelper实现左对齐或右对齐
其实通过上面的分析,就会发现最主要的就是 calculateDistanceToFinalSnap 和 findSnapView 这两个函数。
在寻找目标View的时候,不像findCenterView那么简单。
以为需要考虑到最后item的边界情况。判断的不好就会出现,无论怎么滑动都会出现最后一个item无法完整显示的bug。
package com.example.myapplication.com.example;import android.support.annotation.NonNull;import android.support.annotation.Nullable;import android.support.v7.widget.LinearLayoutManager;import android.support.v7.widget.LinearSnapHelper;import android.support.v7.widget.OrientationHelper;import android.support.v7.widget.RecyclerView;import android.view.View;public class MySnapHelper extends LinearSnapHelper { // 左对齐 public static final int TYPE_SNAP_START = 2; // 右对齐 public static final int TYPE_SNAP_END = 3; // default private int type = TYPE_SNAP_START; @Nullable private OrientationHelper mVerticalHelper; @Nullable private OrientationHelper mHorizontalHelper; public MySnapHelper(int type) { this.type = type; } @Override public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) { if (type == TYPE_SNAP_START) { return calculateDisOnStart(layoutManager, targetView); } else if (type == TYPE_SNAP_END) { return calculateDisOnEnd(layoutManager, targetView); } else { return super.calculateDistanceToFinalSnap(layoutManager, targetView); } } @Override public View findSnapView(RecyclerView.LayoutManager layoutManager) { if (type == TYPE_SNAP_START) { return findStartSnapView(layoutManager); } else if (type == TYPE_SNAP_END) { return findEndSnapView(layoutManager); } else { return super.findSnapView(layoutManager); } } /** * TYPE_SNAP_START * * @param layoutManager * @param targetView * @return */ private int[] calculateDisOnStart(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) { int[] out = new int[2]; if (layoutManager.canScrollHorizontally()) { out[0] = distanceToStart(layoutManager, targetView, getHorizontalHelper(layoutManager)); } else { out[0] = 0; } if (layoutManager.canScrollVertically()) { out[1] = distanceToStart(layoutManager, targetView, getVerticalHelper(layoutManager)); } else { out[1] = 0; } return out; } /** * TYPE_SNAP_END * * @param layoutManager * @param targetView * @return */ private int[] calculateDisOnEnd(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) { int[] out = new int[2]; if (layoutManager.canScrollHorizontally()) { out[0] = distanceToEnd(layoutManager, targetView, getHorizontalHelper(layoutManager)); } else { out[0] = 0; } if (layoutManager.canScrollVertically()) { out[1] = distanceToEnd(layoutManager, targetView, getVerticalHelper(layoutManager)); } else { out[1] = 0; } return out; } /** * calculate distance to start * * @param layoutManager * @param targetView * @param helper * @return */ private int distanceToStart(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView, OrientationHelper helper) { return helper.getDecoratedStart(targetView) - helper.getStartAfterPadding(); } /** * calculate distance to end * * @param layoutManager * @param targetView * @param helper * @return */ private int distanceToEnd(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView, OrientationHelper helper) { return helper.getDecoratedEnd(targetView) - helper.getEndAfterPadding(); } /** * find the start view * * @param layoutManager * @return */ private View findStartSnapView(RecyclerView.LayoutManager layoutManager) { if (layoutManager.canScrollVertically()) { return findStartView(layoutManager, getVerticalHelper(layoutManager)); } else if (layoutManager.canScrollHorizontally()) { return findStartView(layoutManager, getHorizontalHelper(layoutManager)); } return null; } /** * 注意判断最后一个item时,应通过判断距离右侧的位置 * * @param layoutManager * @param helper * @return */ private View findStartView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) { if (!(layoutManager instanceof LinearLayoutManager)) { // only for LinearLayoutManager return null; } int childCount = layoutManager.getChildCount(); if (childCount == 0) { return null; } View closestChild = null; final int start = helper.getStartAfterPadding(); int absClosest = Integer.MAX_VALUE; for (int i = 0; i < childCount; i++) { final View child = layoutManager.getChildAt(i); int childStart = helper.getDecoratedStart(child); int absDistance = Math.abs(childStart - start); if (absDistance < absClosest) { absClosest = absDistance; closestChild = child; } } View firstVisibleChild = layoutManager.getChildAt(0); if (firstVisibleChild != closestChild) { return closestChild; } int firstChildStart = helper.getDecoratedStart(firstVisibleChild); int lastChildPos = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition(); View lastChild = layoutManager.getChildAt(childCount - 1); int lastChildCenter = helper.getDecoratedStart(lastChild) + (helper.getDecoratedMeasurement(lastChild) / 2); boolean isEndItem = lastChildPos == layoutManager.getItemCount() - 1; if (isEndItem && firstChildStart < 0 && lastChildCenter < helper.getEnd()) { return lastChild; } return closestChild; } /** * find the end view * * @param layoutManager * @return */ private View findEndSnapView(RecyclerView.LayoutManager layoutManager) { if (layoutManager.canScrollVertically()) { return findEndView(layoutManager, getVerticalHelper(layoutManager)); } else if (layoutManager.canScrollHorizontally()) { return findEndView(layoutManager, getHorizontalHelper(layoutManager)); } return null; } private View findEndView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) { if (!(layoutManager instanceof LinearLayoutManager)) { // only for LinearLayoutManager return null; } int childCount = layoutManager.getChildCount(); if (childCount == 0) { return null; } if (((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition() == 0) { return null; } View closestChild = null; final int end = helper.getEndAfterPadding(); int absClosest = Integer.MAX_VALUE; for (int i = 0; i < childCount; i++) { final View child = layoutManager.getChildAt(i); int childStart = helper.getDecoratedEnd(child); int absDistance = Math.abs(childStart - end); if (absDistance < absClosest) { absClosest = absDistance; closestChild = child; } } View lastVisibleChild = layoutManager.getChildAt(childCount - 1); if (lastVisibleChild != closestChild) { return closestChild; } if (layoutManager.getPosition(closestChild) == ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition()) { return closestChild; } View firstChild = layoutManager.getChildAt(0); int firstChildStart = helper.getDecoratedStart(firstChild); int firstChildPos = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition(); boolean isFirstItem = firstChildPos == 0; int firstChildCenter = helper.getDecoratedStart(firstChild) + (helper.getDecoratedMeasurement(firstChild) / 2); if (isFirstItem && firstChildStart < 0 && firstChildCenter > helper.getStartAfterPadding()) { return firstChild; } return closestChild; } @NonNull private OrientationHelper getVerticalHelper(@NonNull RecyclerView.LayoutManager layoutManager) { if (mVerticalHelper == null) { mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager); } return mVerticalHelper; } @NonNull private OrientationHelper getHorizontalHelper( @NonNull RecyclerView.LayoutManager layoutManager) { if (mHorizontalHelper == null) { mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager); } return mHorizontalHelper; }}
最后只要用上我们自己的SnapHelper,就可以轻松搞定了。
MySnapHelper mySnapHelper = new MySnapHelper(2);mySnapHelper.attachToRecyclerView(recycleView);
ps:
上面代码中如果使用分隔线,在居中对齐和右对齐时,位移会有误差。
原因是:在计算偏移量时targetView包含item和分隔线。所以我们在计算偏移量时需要把分隔线宽度减掉,
以右对齐为例:在distanceToEnd()中把
private int distanceToEnd(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView, OrientationHelper helper) { //无分隔线 return helper.getDecoratedEnd(targetView) - helper.getEndAfterPadding(); }
改为
private int distanceToEnd(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView, OrientationHelper helper) { //有分割线 return helper.getDecoratedStart(targetView) - helper.getEndAfterPadding() + targetView.getWidth(); }
如果使用的是居中对齐+分隔线,由于自带LinearSnapHelper无法更改,我们可以新建类继承SnapHelper,把LinearSnapHelper中代码全部copy过来,只需更改distanceToCenter()方法即可。
好了,基本就没问题了。
最后,个人建议使用此效果最好不要用分隔线……
- SnapHelper,对RecyclerView的功能拓展
- SnapHelper,对RecyclerView的功能拓展
- Android中对RecyclerView的帮助类Snaphelper使用
- Android 利用RecyclerView 的SnapHelper 实现滚轮效果
- 让你明明白白的使用RecyclerView——SnapHelper详解
- RecyclerView+SnapHelper实现无限循环筛选控件
- SnapHelper
- 拓展JTree的功能
- android 实现城市选择、联系人等功能的易拓展 RecyclerView 库,包含自动索引,粘性等功能
- 对RecyclerView的封装,使用简单,功能丰富
- RecyclerView的基础使用与拓展
- Android 中使用 RecyclerView + SnapHelper 实现类似 ViewPager 效果
- Android 中使用 RecyclerView + SnapHelper 实现类似 ViewPager 效果
- Android中使用RecyclerView + SnapHelper实现类似ViewPager效果
- Android中使用RecyclerView + SnapHelper实现类似ViewPager效果
- android按键功能的拓展
- RecyclerView的长按多选功能
- recyclerview的适配器功能
- Day9-29.Shift operators
- jquery克隆对象
- 顺时针打印矩阵
- win10系统下在vs2015中配置Caffe的详细过程
- JAVA中判断字符串是否为数字的方法
- SnapHelper,对RecyclerView的功能拓展
- 【系统定制】创建自己的安装包组
- HttpURLConnection用法详解
- 《JavaScript高级程序设计》学习笔记(第二章)
- JSP里的小知识总结(一)
- 无意中发现一款以太坊挖矿神器-ETH超级矿工,支持ETH,ETC+SC等各种双挖(10.2内核)破解抽水
- XGBoost快速入门
- javascript算法排序
- java ClassLoader 类加载器(二十二)