Skip to content

基于 TextView 的跑马灯效果和踩坑记录

出发点

对于一个 TextView ,当文本过长的时候通常需要专门处理,保持比较好的显示效果。默认情况下会采取折行显示的策略。

但 UI 设计图通常不会允许无限换行,所以经常限制行数为1:

xml
android:maxLines="1"

顺便设置一下显示不完整的话最后以...结束:

xml
android:ellipsize="end"

后来有了新的需求,要用跑马灯的形式展示。于是开始查找相关资料。

三方库实现的跑马灯效果

期间尝试了一些杂七杂八的第三方库,但并没有找到特别满意的。

  • 他们有的不仅实现了跑马灯效果,还可以调节速度等参数。然而这些并不在我的需求范围内。

  • 这些第三方库大多是自行实现滚动效果,也就是说不是继承自 TextView ,这样最大的问题就是不能用和 TextView 一模一样的方式设置一些自带的属性,导致迁移不便。

  • 年久失修,许多年没有更新。

Android TextView 自带的跑马灯效果

实际上 TextView 是有跑马灯效果的,但是有些坑点让我琢磨很久。

首先,需要给 TextView 设置 4 个属性:

xml
android:ellipsize="marquee"
android:focusable="true"
android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true"

各属性的意义如下:

  • 超长文本采用跑马灯效果;
  • 可获取焦点;
  • 跑马灯重复次数永久;
  • 单行展示。

坑点1:必须有焦点才能跑马灯

设置完大概率是不动的,因为 TextView 设计为必须获取到了焦点才能展示。因为这个效果其实是为 TV 设计的,TV 通过遥控器遥控,通常有个高亮选中项的效果,选中项获得焦点则开始跑马灯。

那我要无论如何都跑马灯如何呢?查看源码中startMarquee方法发现,marquee的启动条件为:

java
(mMarquee == null || mMarquee.isStopped()) && isAggregatedVisible()
                && (isFocused() || isSelected()) && getLineCount() == 1 && canMarquee()

isFocusedisSelected是平级的,而isSelected涉及到的调用处更少,本着最小影响的原则,可以从isSelected出发进行改造。

新建类MarqueeTextView继承 TextView,使得isSelected始终返回true,最后在布局中把 TextView 替换为MarqueeTextView即可。示例如下:

kotlin
class MarqueeTextView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {

    override fun isSelected(): Boolean {
        // marquee 启动条件
        return true
    }
}

坑点2:singleLine="true"与maxLines="1"的异同

一般情况下,这两者的效果没有明显差异,而且前者被谷歌标记为 Deprecated,推荐我们用后者。但是 TextView 要实现跑马灯效果,必须使用前者

摘录区别如下(原文见参考文章):

maxLines:限制TextView的最高高度,大概就是指通过限制行数来限制最高高度。

singleLine:强制设置TextView的文字不换行。

区别就是:maxLines还是会默认自动进行换行策略,假如一段文字自动换行后有5行,maxLines设置为1,那么就只显示第一行的内容,其他行不显示。

但是,如果是设置了singleLine, 那么这段可以有5行的文字将会被强制放在1行里,然后看最多能显示多少字符,剩下的不显示。

参考文章

https://cloud.tencent.com/developer/article/1391484

https://cloud.tencent.com/developer/article/1128204

上次更新于: