嘿嘿。又少了一个不用Compose的理由~官方支持跑马灯效果了!

5,388 阅读5分钟

前言

在app开发中,很多场景需要在有限的范围内显示完整的内容。如果内容本身没有超出控件范围,那么没有任何问题。但是如果内容的长度超过了控件的范围,那么就需要酌情处理了。而跑马灯效果就是处理方式之一。

跑马灯并不是什么新东西,在xml的时代,textview就可以通过简单的属性设置实现效果。不知道用过的xdm有没有跟我同样的感受,就是往往很难一下子完美的实现效果,因为什么焦点啊,行数啊这些属性都会影响到最终的效果并且有些场景需要多个textview按需跑马灯等等这些问题,总之让这个简单的功能实现起来不是那么顺畅~

当然啊,好歹xml的方式还是可以实现的。而强大的Compose居然在1.4.0-alpha04版本之前不支持跑马灯效果!这几天看文档发现从1.4.0-alpha04已经支持了跑马灯这个可能有点迟来的功能。下面就来一起探索一下Compose的跑马灯实现吧。

最快实现

实现非常简单,调用Modifier.basicMarquee()即可!不用像之前xml的时候,需要设置焦点行数等。

Text(
    modifier = Modifier.padding(padding).basicMarquee(),
    text = "套马杆的汉子你威武雄壮!套马杆的汉子你威武雄壮!套马杆的汉子你威武雄壮!套马杆的汉子你威武雄壮!套马杆的汉子你威武雄壮!套马杆的汉子你威武雄壮!套马杆的汉子你威武雄壮!",
)

首页效果是用到了大话Compose炼体(1)-先一餐吃3碗饭 - 掘金 (juejin.cn)这篇的实现。感兴趣的可以去看看点个赞,这里主要看Text的跑马灯效果就好

另外我们缩减Text的text内容,再来看看

Text(
    modifier = Modifier.padding(padding).basicMarquee(),
    text = "套马杆的汉子你威武雄壮!",
)

因为这里的Text的宽没有限制啊,所以是屏幕宽度。那么这里的控件宽度肯定大于内容,发现这是是没有跑马灯效果的

如果我们再把Text的宽度指定到一个肖小于内容的值示例为50dp

Text(
    modifier = Modifier.padding(padding).width(50.dp).basicMarquee(),
    text = "套马杆的汉子你威武雄壮!",
)

发现跑马灯效果又出现了。这里就不再贴图演示了。

结论:只有内容超出了有限范围才会有跑马灯效果,反之没有!

配合焦点实现跑马灯

考虑到电量优化和性能优化,很多时候让跑马灯无条件并无限时长的运行并不是个好主意。上面提到要实现跑马灯我们要调用Modifier.basicMarquee方法。这个方法都有默认参数,所以我们上面没有传参就能实现了。稍微看了一下参数类型和默认值,发现animationMode这个字段可以控制跑马灯动画的触发条件

fun Modifier.basicMarquee(
    iterations: Int = DefaultMarqueeIterations,
    //关键参数
    animationMode: MarqueeAnimationMode = Immediately,
    delayMillis: Int = DefaultMarqueeDelayMillis,
    initialDelayMillis: Int = if (animationMode == Immediately) delayMillis else 0,
    spacing: MarqueeSpacing = DefaultMarqueeSpacing,
    velocity: Dp = DefaultMarqueeVelocity
): Modifier


//动画模式
value class MarqueeAnimationMode private constructor(private val value: Int) {

    override fun toString(): String = when (this) {
        Immediately -> "Immediately"
        WhileFocused -> "WhileFocused"
        else -> error("invalid value: $value")
    }

    companion object {
        /**
         *不管有没焦点状态,立即执行
         */
        @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
        @ExperimentalFoundationApi
        @get:ExperimentalFoundationApi
        val Immediately = MarqueeAnimationMode(0)

        /**
         * 只有控件有焦点或者子控件有焦点时才执行
         */
        @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
        @ExperimentalFoundationApi
        @get:ExperimentalFoundationApi
        val WhileFocused = MarqueeAnimationMode(1)
    }
}

看到这心里就有个实现想法了,通过点击来控制焦点,然后通过是否有焦点来控制跑马灯动画是否运行。对Compose焦点管理不了解的xdm可以花个几分钟看看这篇文章Jetpack Compose中的焦点管理 - 掘金 (juejin.cn)

改造代码如下:

 //获取并记住焦点申请器,后面用它来申请焦点
val focusRequester = remember { FocusRequester() }
//获取焦点管理器 后面用它来清理焦点
val focusManager = LocalFocusManager.current
//申明一个变量来记录当前是否有焦点
var isFocused = false
Text(
    text = "套马杆的汉子你威武雄壮!套马杆的汉子你威武雄壮!",
    Modifier
        .padding(padding)
        .width(200.dp)
        //传参 MarqueeAnimationMode.WhileFocused用焦点来控制跑马灯动画
        .basicMarquee(animationMode = MarqueeAnimationMode.WhileFocused)
        //设置焦点申请器
        .focusRequester(focusRequester)
        //设置焦点改变监听
        .onFocusChanged {
            isFocused = it.isFocused
        }
        //这句必须要有,调用后表明可以被聚焦
        .focusable()
        //设置点击事件
        .clickable {
            if (isFocused) {
                //如果当前有焦点,也就是在跑动画时。清楚焦点,停止动画。
                focusManager.clearFocus()
            } else {
                //如果当前没有焦点,申请焦点,开始动画
                focusRequester.requestFocus()
            }
        },
)

可以看到效果是符合预期的

结论:可以通过焦点来控制动画的起止。失去焦点停止动画,并回到初始的状态而不是动画停止时的状态。

都能跑 才是好的跑马灯

之前大多数场景用到跑马灯,都是Text这种控件。但是Compose的跑马灯是否只能用在Text上呢?答案是“并不是哟”。所有可组合项都可以用到跑马灯效果!这个就是发挥xdm脑洞的时候了,下面展示一个简单效果:

调快一点速度,默认是30dp,这里改成了60;减少一点动画头尾的距离,默认是3/1屏幕宽,这里改成10/1

Row(modifier = Modifier
    .padding(padding)
    .basicMarquee(velocity =60.dp, spacing = MarqueeSpacing.fractionOfContainer(1f / 10f))) {
    Image(painter = painterResource(id = R.drawable.avatar1) , contentDescription = null)
    Image(painter = painterResource(id = R.drawable.avatar2) , contentDescription = null)
    Image(painter = painterResource(id = R.drawable.avatar3) , contentDescription = null)
    Image(painter = painterResource(id = R.drawable.avatar4) , contentDescription = null)
    Image(painter = painterResource(id = R.drawable.avatar5) , contentDescription = null)
    Image(painter = painterResource(id = R.drawable.avatar6) , contentDescription = null)

}

小结

通过上面的内容,可以发现。在Compose中要实现跑马灯效果非常简单,核心方法就是basicMarquee(),并且所有可组合项都可以通过Modifier来实现。具体有哪些其他的地方可以用到,这就看大家的想象力了!欢迎留言说说你的想法!