博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
整页滑动的 RecyclerView
阅读量:6186 次
发布时间:2019-06-21

本文共 5008 字,大约阅读时间需要 16 分钟。

本文章对应的示例已上传到 GitHub

需求描述

我们在开发一个列表的时候,有时候会需要实现列表整页滑动的效果。列表的实现大家应该都会使用 RecyclerView ,但 RecyclerView 原生是不支持整页滑动的。最近 RecyclerView 添加了 SnapHelper 的 API,它是用来帮助实现 ItemView 的对齐,SDK 默认实现了 LinearSnapHelperPagerSnapHelper ,分别用来实现居中对齐和每次滑动一个 ItemView 的效果。

我们就借助 SnapHelper 的原理来实现一个可以整页滑动的 RecyclerView 。效果如下所示:

SnapHelper 介绍

SnapHelper 是用来帮助对齐 ItemView 的,继承 SnapHelper 我们需要实现三个方法,分别是

View findSnapView(RecyclerView.LayoutManager layoutManager)复制代码

找到需要对齐的 ItemView ,我们这里称为 snapView

int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,            @NonNull View targetView)复制代码

计算 snapView 到要对齐的位置之间的距离。

int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX,            int velocityY)复制代码

找到要对齐的 ItemViewAdapter 里面的 position

如上图所示,假设我们每次需要滑动一页:snapView 就是需要对齐的 ItemView ,对应 findSnapView() 的返回值;SnapViewAdapter 中的 position 就是需要对齐的位置,对应 findTargetSnapPosition() 的返回值;snap distance 就是对齐需要滑动的距离,对应 calculateDistanceToFinalSnap() 的返回值。

PagerSnapHelper 实现

我们要实现一个可以整页滑动的 SnapHelper

  • 首先我们需要找到需要对齐的 ItemView
override fun findSnapView(layoutManager: RecyclerView.LayoutManager?): View? {        if (mFlung) {            resetCurrentScrolled()            mFlung = false            return null        }        if (layoutManager == null) return null        // 首先找到需要对齐的 ItemView 在 Adapter 中的位置        val targetPosition = getTargetPosition()        println("$TAG findSnapView, pos: $targetPosition")        if (targetPosition == RecyclerView.NO_POSITION) return null        // 正常情况下,我们在这里通过 layoutManager.findViewByPosition(int pos) 返回 view 即可,但会存在两个问题:        // 1. 如果这个位置的 view 还没有 layout 的话,会返回 null,达不到对齐的效果;        // 2. 即使 view 不为空,但滑动速度会不一致,后面会讲到;        // 所以在这里,我们把 position 传递给 LinearSmoothScroller,让它帮助我们滑动到指定位置。        layoutManager.startSmoothScroll(createScroller(layoutManager).apply {            this?.targetPosition = targetPosition        })        return null    }复制代码

LinearSmoothScroller 可以设置一个 targetPosition ,然后调用 layoutManager.startSmoothScroll(LinearSmoothScroller scroller) ,它会帮助我们自动把 targetPosition 对应的 ItemView 对齐到边界,默认是左对齐,和我们需求一致。

private fun getTargetPosition(): Int {        println("$TAG getTargetPosition, mScrolledX: $mScrolledX, mCurrentScrolledX: $mCurrentScrolledX")        val page = when {            mCurrentScrolledX > 0 -> mScrolledX / mRecyclerViewWidth + 1            mCurrentScrolledX < 0 -> mScrolledX / mRecyclerViewWidth            else -> RecyclerView.NO_POSITION        }        resetCurrentScrolled()        return (if (page == RecyclerView.NO_POSITION) RecyclerView.NO_POSITION else page * itemCount)    }复制代码

getTargetPosition() 就是根据 RecyclerView 滑动的距离和方向,找出滑动一页后,需要对齐的 ItemView 的位置。

private val mScrollListener = object : RecyclerView.OnScrollListener() {        private var scrolledByUser = false        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {            if (newState == RecyclerView.SCROLL_STATE_DRAGGING) scrolledByUser = true            if (newState == RecyclerView.SCROLL_STATE_IDLE && scrolledByUser) {                scrolledByUser = false            }        }        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {            mScrolledX += dx            mScrolledY += dy            if (scrolledByUser) {                mCurrentScrolledX += dx                mCurrentScrolledY += dy            }        }    }复制代码

mScrolledXmScrolledY 就是 RecyclerView 滑动的总距离,mCurrentScrolledXmCurrentScrolledY 就是 RecyclerView 本次滑动的距离,用来判断 RecyclerView 滑动的方向。

  • 找出需要对齐的 ItemViewAdapter 中的位置:
override fun findTargetSnapPosition(        layoutManager: RecyclerView.LayoutManager?,        velocityX: Int,        velocityY: Int    ): Int {        val targetPosition = getTargetPosition()        mFlung = targetPosition != RecyclerView.NO_POSITION        println("$TAG findTargetSnapPosition, pos: $targetPosition")        return targetPosition    }复制代码

很简单,就是 getTargetPosition() 返回的值。解释一点,findTargetSnapPosition() 方法只有在 RecyclerView 触发 fling 的时候才会调用。SnapHelper 内部也是使用的 LinearSmoothScroller 实现的滑动,设置的 targetPosition 就是 findTargetSnapPosition() 的返回值。这也解释了我们为什么不在 findSnapView() 方法中直接返回 snapView ,就是为了保持滑动速度的一致。

  • 计算需要滑动的距离
override fun calculateDistanceToFinalSnap(layoutManager: RecyclerView.LayoutManager, targetView: View): IntArray? {        val out = IntArray(2)        if (layoutManager.canScrollHorizontally()) {            out[0] = distanceToStart(targetView, getHorizontalHelper(layoutManager))            out[1] = 0        } else if (layoutManager.canScrollVertically()) {            out[0] = 0            out[1] = distanceToStart(targetView, getVerticalHelper(layoutManager))        }        return out    }复制代码
private fun distanceToStart(targetView: View, orientationHelper: OrientationHelper): Int {        return orientationHelper.getDecoratedStart(targetView) - orientationHelper.startAfterPadding    }复制代码

解释一点,OrientationHelper 可以很方便地帮助我们计算 ItemView 的位置。

本文章对应的示例已上传到 GitHub

转载于:https://juejin.im/post/5d07a3e6e51d4510803ce3b1

你可能感兴趣的文章
2018/02/13
查看>>
echarts(二)
查看>>
Hibernate映射文件结构
查看>>
rollPagerView引导页轮播图
查看>>
redis集群介绍,redis集群搭建配置,redis集群操作
查看>>
Gitbilt hooks 简单的账户操作权限控制
查看>>
[3.30]#珠海GDG#成立大会胜利闭幕!
查看>>
mybatis 批量Update(2)
查看>>
RabbitMQ安装
查看>>
django 学习笔记 (五)
查看>>
iOS UItableviewCell实现可变高度的UITextView,动态刷新高度
查看>>
iOS开发- 利用runtime拦截UIButton的点击事件,防止重复点击
查看>>
Java,Jsp获取客户端IP地址
查看>>
100-88
查看>>
android4.0.3 编译lichee 报错dhd-cdc-sdmmc-gpl-3.0.8问题
查看>>
crc16 - 产生Modbus RTU格式的CRC码
查看>>
招聘又来了,这次推荐有奖哦
查看>>
我的友情链接
查看>>
Linux下ssh秘钥方式登录远程服务器
查看>>
golang test测试使用
查看>>