You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Studio波浪式字母滚动条动画优化求助

波浪式位置移动动画效果

我在LinearLayout中放置了一组字母TextView,希望点击任意字母时,能实现如图所示的波浪式位置移动动画,该效果与Niagra启动器的应用抽屉滚动条类似。目前我的代码能实现类似效果,但动画流畅度不如该启动器的滚动条,恳请帮忙优化!

当前实现代码

private fun moveTextViewsToLeft1(index: Int) {
        val adjacentViews = mutableListOf<Pair<View, Float>>()
        val touchedViewTranslationX = -100f
        val neighborTranslationX = -80f
        val extraNeighborTranslationX1 = -45f
        val extraNeighborTranslationX2 = -10f

        // Add touched textView
        val touchedView = getChildAt(index)
        adjacentViews.add(touchedView to touchedViewTranslationX)

        // Add top neighbors
        for (i in index - 1 downTo 0) {
            val view = getChildAt(i)
            val translationX = when (index - i) {
                1 -> neighborTranslationX
                2 -> extraNeighborTranslationX1
                3 -> extraNeighborTranslationX2
                else -> 0f
            }
            adjacentViews.add(view to translationX)
        }

        // Add bottom neighbors
        for (i in index + 1 until childCount) {
            val view = getChildAt(i)
            val translationX = when (i - index) {
                1 -> neighborTranslationX
                2 -> extraNeighborTranslationX1
                3 -> extraNeighborTranslationX2
                else -> 0f
            }
            adjacentViews.add(view to translationX)
        }

        val animators = mutableListOf<ObjectAnimator>()
        for ((view, translationXValue) in adjacentViews) {
            val animator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, translationXValue)
            animator.duration = ANIMATION_DURATION
            animator.interpolator = AccelerateDecelerateInterpolator()
            animators.add(animator)
        }

        val set = AnimatorSet()
        set.playTogether(animators as Collection<Animator>?)
        set.addListener(object : Animator.AnimatorListener {
            override fun onAnimationStart(animation: Animator) {
                isAnimating = true
            }

            override fun onAnimationEnd(animation: Animator) {
                isAnimating = false
            }

            override fun onAnimationCancel(animation: Animator) {
                isAnimating = false
            }

            override fun onAnimationRepeat(animation: Animator) {}
        })
        set.start()
    }

优化方案及实现

核心优化点

  1. 减少Animator实例数量:原代码给每个View单独创建ObjectAnimator,会导致系统同时处理多个动画任务,改用单个ValueAnimator统一更新所有View的偏移量,降低系统开销。
  2. 优化遍历逻辑:只处理点击位置前后各3个View(超过3个偏移量为0),避免不必要的循环遍历。
  3. 替换插值器:用PathInterpolator自定义更贴合波浪效果的动画曲线,比默认的AccelerateDecelerateInterpolator更自然。
  4. 减少对象创建:缓存动画参数和插值器,避免每次触发动画都重复创建。

优化后的代码

// 作为成员变量缓存,避免每次创建
private val waveOffsetMap = mapOf(
    0 to -100f,    // 点击的View偏移量
    1 to -80f,     // 相邻1级偏移量
    2 to -45f,     // 相邻2级偏移量
    3 to -10f      // 相邻3级偏移量
)
private val waveInterpolator = PathInterpolator(0.3f, 0.0f, 0.2f, 1.0f) // 可根据效果微调控制点

private fun moveTextViewsToLeftOptimized(index: Int) {
    val touchedView = getChildAt(index) ?: return
    isAnimating = true

    ValueAnimator.ofFloat(0f, 1f).apply {
        duration = ANIMATION_DURATION
        interpolator = waveInterpolator
        addUpdateListener { animator ->
            val progress = animator.animatedValue as Float

            // 更新点击的View
            touchedView.translationX = waveOffsetMap[0]!! * progress

            // 更新上方最多3个邻居
            for (level in 1..3) {
                val targetIndex = index - level
                if (targetIndex < 0) break
                getChildAt(targetIndex).translationX = waveOffsetMap[level]!! * progress
            }

            // 更新下方最多3个邻居
            for (level in 1..3) {
                val targetIndex = index + level
                if (targetIndex >= childCount) break
                getChildAt(targetIndex).translationX = waveOffsetMap[level]!! * progress
            }
        }
        addListener(object : Animator.AnimatorListener {
            override fun onAnimationEnd(animation: Animator) {
                isAnimating = false
                // 若需要动画结束后重置位置,可在此处遍历相关View设置translationX=0f
            }

            override fun onAnimationStart(animation: Animator) {}
            override fun onAnimationCancel(animation: Animator) {
                isAnimating = false
            }
            override fun onAnimationRepeat(animation: Animator) {}
        })
        start()
    }
}

额外优化建议

  • 开启硬件加速:在包含TextView的LinearLayout布局中添加android:hardwareAccelerated="true",或在代码中给目标View设置view.setLayerType(View.LAYER_TYPE_HARDWARE, null),提升动画渲染性能。
  • 动画中断处理:如果用户在动画未结束时再次点击,可先取消当前动画再启动新动画,避免动画冲突。

内容的提问来源于stack exchange,提问作者Ashique

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 08:51:01