Android LinearSmoothScroller 使用教程

I. 滚动的RecyclerView

要让RecyclerView开始滚动,我们有以下几种方法(不全):

  1. RecyclerView / LayoutManager 类 中的 scrollTo 方法 (滚动至任意位置)

  2. RecyclerView / LayoutManager 类 中的 scrollToPosition 方法 (滚动至特定Item位置)

  3. RecyclerView / LayoutManager 类 中的 smoothScrollBy 方法 (平滑滚动至任意位置)

  4. RecyclerView / LayoutManager 类 中的 smoothScrollTo 方法 (平滑滚动至特定Item位置)

  5. LayoutManager 类 中的 startSmoothScroll 方法 (使用外部创建的 RecyclerView.SmoothScroller,平滑滚动至特定Item位置)

现在我们可以来分析一下现有的实现滚动的方法了 ——

1, 2 都是非平滑滚动,也就是瞬间滚动至指定位置。3, 4 都是平滑滚动,且 4 可以特定滚动插值器。5 调用了外部的 SmoothScroller 实现平滑滚动。

1, 3 相比,2, 4, 5 的滚动至Item位置更加省事,但是可能就有朋友发现了,这几种方法都不能自定义插值器以及滚动时长,而唯一能指定插值器的 3 却无法帮助我们方便的滚动到特定Item位置、自定义时间,甚至如果滚动路程过长,3 的动画就会显得又臭又长

那么,难道我们就没法方便的滚动到特定的Item位置吗?难道我们非要绞尽脑汁自己写出定位位置,自己实现动画过度,写出一个可以用的滚动实现么?不————

诸君,且留步,听我娓娓道来——

II. LinearSmoothScroller,堂堂登场!

LinearSmoothScroller,继承 RecyclerView.SmoothScroller,是一个现成的滚动工具类。 它已经帮我们写好了位置定位,动画过渡,那么我们来看看怎么使用它吧。

构建 LinearSmoothScroller

val mLinearSmoothScroller = object : LinearSmoothScroller(context) {}
mLinearSmoothScroller.targetPosition = seekPosition                       // 滚动目标在 adapter 中的位置
mLinearLayoutManager.startSmoothScroll(mLinearSmoothScroller)

调教 LinearSmoothScroller

好,那么我们先写一个实例应用试试吧!

5个 ViewHolder,15 个 ViewHolder,滚动顺利… 不对!出问题了!在 ViewHolder 过长的时候, smoothScroller 的动画会显得拖沓!

没有关系,让我们来复写滚动时间吧,让动画看起来更顺畅一点。

private fun createSmoothScroller(): RecyclerView.SmoothScroller {
        return object : CustomSmoothScroller(context) {
​
            override fun calculateTimeForDeceleration(dx: Int): Int {
                return 500
            }
            
        }
    }

再次实验,是不是顺畅多了?

这个时候,有的朋友又有新问题了:我如何让指定的 ViewHolder 出现在屏幕顶端?底端?或者是我想要的任意位置?

别着急,这个也简单,让我们再来复写几个 method 就是了:

override fun getVerticalSnapPreference(): Int {
    return SNAP_TO_START
}

返回值可以有这几种:

public static final int SNAP_TO_START = -1;
public static final int SNAP_TO_END = 1;
public static final int SNAP_TO_ANY = 0;

第一种返回值 SNAP_TO_START,表示一直滚动,直到目标在屏幕顶端为止。 第二种返回值 SNAP_TO_END,表示一直滚动,直到目标在屏幕底端为止。 第三种返回值 SNAP_TO_ANY,表示一直滚动,直到目标出现在屏幕里为止。

如果我们想要滚动直到目标出现在距屏幕顶端 72px 的地方,怎么处理?

override fun calculateDtToFit(
    viewStart: Int,
    viewEnd: Int,
    boxStart: Int,
    boxEnd: Int,
    snapPreference: Int
): Int {
    return super.calculateDtToFit(viewStart, viewEnd, boxStart, boxEnd, snapPreference) + 72
}

好,近乎完美,现在你已经学会了如何使用基础的 LinearSmoothScroller,而网上的教程大多到此为止——下面只剩下我夹带的干货了。

III. LinearSmoothScroller 到底是如何工作的?

通过分析源码,我们可以得知,LinearSmoothScroller 会先计算目标 ViewHolder 距离目的地的位置,然后再做出情况判断:

  • 第一种情况:如果目标在屏幕上可视,调用 onTargetFound ,利用 mDecelerateInterpolator 这个插值器来进行平滑的减速。
  • 第二种情况:如果目标在屏幕上不可视,调用 updateActionForInterimTarget,利用 mLinearInterpolator 线性滚动直到目标在屏幕中可见,再利用 onTargetFound 以及 mDecelerateInterpolator 来平滑减速到目的地。

在上一段里,我们提到了 在 ViewHolder 过长的时候, smoothScroller 的动画会显得拖沓 的问题,那么我们再次深入源码来分析看看:

/**
 * Calculates the time it should take to scroll the given distance (in pixels)
 *
 * @param dx Distance in pixels that we want to scroll
 * @return Time in milliseconds
 * @see #calculateSpeedPerPixel(android.util.DisplayMetrics)
 */
protected int calculateTimeForScrolling(int dx) {
    // In a case where dx is very small, rounding may return 0 although dx > 0.
    // To avoid that issue, ceil the result so that if dx > 0, we'll always return positive
    // time.
    return (int) Math.ceil(Math.abs(dx) * getSpeedPerPixel());
}private float getSpeedPerPixel() {
    if (!mHasCalculatedMillisPerPixel) {
        mMillisPerPixel = calculateSpeedPerPixel(mDisplayMetrics);
        mHasCalculatedMillisPerPixel = true;
    }
    return mMillisPerPixel;
}/**
 * Calculates the scroll speed.
 *
 * <p>By default, LinearSmoothScroller assumes this method always returns the same value and
 * caches the result of calling it.
 *
 * @param displayMetrics DisplayMetrics to be used for real dimension calculations
 * @return The time (in ms) it should take for each pixel. For instance, if returned value is
 * 2 ms, it means scrolling 1000 pixels with LinearInterpolation should take 2 seconds.
 */
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
    return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
}

好了,那么现在我们知道为何会出问题了:MILLISECONDS_PER_INCH 永远是一个定值,calculateSpeedPerPixel 虽然是按照 DPI 来计算滚动时间了,但并没有将滚动长度考虑在内,因为 LinearSmoothScroller 会假设 calculateSpeedPerPixel 的返回值永远不会变,缓存这个值。

网上很多教程都会叫你去复写 calculateSpeedPerPixel ,来将速度提高,实际上我测试的效果也不理想,在 RecyclerView 项目过多的时候还是会出现卡顿的现象,而最理想,最简单的 Workaround 便是直接将 calculateTimeForScrolling 设置为一个定值,这样滚动的速度会按照滚动时间来动态计算。

IV. 美化 LinearSmoothScroller 的滚动过程

什么?你说 LinearSmoothScroller 的滚动过程不动感,不美观?你在做 LyricView,想要让滚动过程和你的 View 动画过程变得统一?

小菜一碟~

前面我们说了,在进行滚动的时候 LinearSmoothScroller 会调用两个插值器,那么修改这两个插值器就可以了。mLinearInterpolator 其实如果没有特别的需求不用修改,因为在长时间线性滚动的时候用插值器和不用插值器其实区别不大。我们只需要修改 mDecelerateInterpolator 便能达到很好的效果。

当然,LinearSmoothScroller 不允许你直接复写这两个插值器。幸运的是 LinearSmoothScroller 不会调用什么奇怪的内部组件和受限的 API,我们只需复制粘贴 LinearSmoothScroller 的内容,改个名字就可以了。

在这里推荐使用 material-componentsmotionEasingStandardInterpolator

protected TimeInterpolator mDecelerateInterpolator;@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
public CustomSmoothScroller(Context context) {
    mDisplayMetrics = context.getResources().getDisplayMetrics();
    mDecelerateInterpolator = MotionUtils.resolveThemeInterpolator(
        context,
        R.attr.motionEasingStandardInterpolator,  // interpolator theme attribute
        FastOutSlowInInterpolator()  // default fallback interpolator
    );
}

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题
图片

  • 13
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android Studio是一款由Google开发的集成开发环境(IDE),用于开发Android应用程序。它提供了丰富的功能和工具,使开发人员能够轻松地创建、调试和部署Android应用程序。 以下是Android Studio的使用教程: 1. 下载和安装Android Studio:首先,您需要从官方网站(https://developer.android.com/studio)下载Android Studio的最新版本。然后,按照安装向导的指示进行安装。 2. 创建新项目:打开Android Studio后,您可以选择创建一个新项目或导入现有项目。选择“Create New Project”,然后按照向导的指示填写项目名称、包名和其他相关信息。 3. 设计用户界面:Android Studio提供了一个可视化的布局编辑器,使您能够直观地设计应用程序的用户界面。您可以拖放各种UI组件,设置属性和样式,以及预览应用程序的外观。 4. 编写代码:Android Studio内置了一个强大的代码编辑器,支持Java和Kotlin两种编程语言。您可以在代码编辑器中编写应用程序的逻辑和功能,并使用自动补全、调试工具和其他辅助功能提高开发效率。 5. 调试和测试:Android Studio提供了丰富的调试和测试工具,帮助您识别和修复应用程序中的错误和问题。您可以设置断点、监视变量、模拟设备和运行单元测试等。 6. 构建和部署:使用Gradle构建系统,Android Studio可以自动处理应用程序的依赖关系和构建过程。您可以配置构建类型、签名应用程序、生成APK文件,并将应用程序部署到模拟器或真实设备上进行测试。 7. 发布应用程序:一旦您完成了应用程序的开发和测试,您可以使用Android Studio生成一个发布版本的APK文件。然后,您可以将APK文件上传到Google Play商店或其他应用分发渠道,供用户下载和安装。 这是一个简要的Android Studio使用教程,希望对您有所帮助。如果您有任何进一步的问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值