基于 TextView 的跑马灯效果和踩坑记录
出发点
对于一个 TextView ,当文本过长的时候通常需要专门处理,保持比较好的显示效果。默认情况下会采取折行显示的策略。
但 UI 设计图通常不会允许无限换行,所以经常限制行数为1:
android:maxLines="1"
顺便设置一下显示不完整的话最后以...
结束:
android:ellipsize="end"
后来有了新的需求,要用跑马灯的形式展示。于是开始查找相关资料。
三方库实现的跑马灯效果
期间尝试了一些杂七杂八的第三方库,但并没有找到特别满意的。
他们有的不仅实现了跑马灯效果,还可以调节速度等参数。然而这些并不在我的需求范围内。
这些第三方库大多是自行实现滚动效果,也就是说不是继承自 TextView ,这样最大的问题就是不能用和 TextView 一模一样的方式设置一些自带的属性,导致迁移不便。
年久失修,许多年没有更新。
Android TextView 自带的跑马灯效果
实际上 TextView 是有跑马灯效果的,但是有些坑点让我琢磨很久。
首先,需要给 TextView 设置 4 个属性:
android:ellipsize="marquee"
android:focusable="true"
android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true"
各属性的意义如下:
- 超长文本采用跑马灯效果;
- 可获取焦点;
- 跑马灯重复次数永久;
- 单行展示。
坑点1:必须有焦点才能跑马灯
设置完大概率是不动的,因为 TextView 设计为必须获取到了焦点才能展示。因为这个效果其实是为 TV 设计的,TV 通过遥控器遥控,通常有个高亮选中项的效果,选中项获得焦点则开始跑马灯。
那我要无论如何都跑马灯如何呢?查看源码中startMarquee
方法发现,marquee的启动条件为:
(mMarquee == null || mMarquee.isStopped()) && isAggregatedVisible()
&& (isFocused() || isSelected()) && getLineCount() == 1 && canMarquee()
isFocused
和isSelected
是平级的,而isSelected
涉及到的调用处更少,本着最小影响的原则,可以从isSelected
出发进行改造。
新建类MarqueeTextView
继承 TextView,使得isSelected
始终返回true,最后在布局中把 TextView 替换为MarqueeTextView
即可。示例如下:
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行里,然后看最多能显示多少字符,剩下的不显示。