最近在做一个跑马灯的需求, 本以为一秒就能加上, 没想到掉到坑里两天…
按照以前的写法是这样的:
<TextView
android:id="@+id/tv_title"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:singleLine="true"/>
mTvTitle.requestFocus();
mTvTitle.setSelected(true);
没想到竟然不好使, 排查后发现是因为界面中还有一个ObjectAnimator的原因, 停止ObjectAnimator后跑马灯就会自动播放了.
看了下TextView源码中跑马灯相关的部分, 是否播放跑马灯与是否focus/是否selected有关系, 难道是ObjectAnimator播放时一直持有焦点么? 基于此思路, 自定义了一个TextView, 重写所有相关方法, 固定返回true:
class MarqueeTextView @JvmOverloads constructor(context: Context, attributeSet: AttributeSet? = null) : TextView(context, attributeSet) {
init {
isFocusable = true
isFocusableInTouchMode = true
setSingleLine()
ellipsize = TextUtils.TruncateAt.MARQUEE
marqueeRepeatLimit = -1
}
override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
super.onFocusChanged(true, direction, previouslyFocusedRect)
}
override fun onWindowFocusChanged(focused: Boolean) {
super.onWindowFocusChanged(true)
}
override fun isSelected(): Boolean {
return true
}
override fun isFocused(): Boolean {
return true
}
}
然鹅, 还是没有什么卵用, 仍然是只有ObjectAnimation停止后才会播放.
不太明白是不是焦点的问题, 最后自己利用scroller模拟出跑马灯效果, 并参考源码调整了播放间隔参数等, 基本完全实现了原生效果, 直接看代码:
/**
* Created by jdw on 2019/1/22.
*
* 解决marquee与动画冲突问题.
* 完全重现系统marquee表现
*/
class MarqueeTextView @JvmOverloads constructor(context: Context, attributeSet: AttributeSet? = null) : TextView(context, attributeSet) {
private val slr = Scroller(context, LinearInterpolator())
private var realText: CharSequence = ""
/**
* 用于循环调度, 不能使用getHandler, removeCallbacksAndMessages会影响其他主线程消息
*/
private val myHandler = Handler()
init {
setSingleLine()
ellipsize = null
setScroller(slr)
postDelayed({ refreshScrollText() }, MARQUEE_DELAY.toLong())
}
fun setTextMarquee(charSequence: CharSequence) {
slr.forceFinished(true)
myHandler.removeCallbacksAndMessages(null)
realText = ""
text = charSequence
myHandler.postDelayed({ refreshScrollText() }, MARQUEE_DELAY.toLong())
}
private fun refreshScrollText() {
// 字体长度
var scrollingLen = paint.measureText(text.toString()).toInt()
// 总长度
val distance = scrollingLen + compoundPaddingLeft + compoundPaddingRight
if (width >= distance) {
return
}
// 这里添加空格
realText = text.toString()
val black = StringBuilder(" ")
while (paint.measureText(black.toString()) < width / 3) {
black.append(" ")
}
append(black)
// 新的字体长度
scrollingLen = paint.measureText(text.toString()).toInt()
// 滚动时长
val duration = text.length * 400
append(realText.toString() + black)
startCircleMarquee(scrollingLen, duration)
}
private fun startCircleMarquee(distance: Int, duration: Int) {
slr.startScroll(0, 0, distance, 0, duration)
invalidate()
myHandler.postDelayed({ startCircleMarquee(distance, duration) }, duration.toLong() + MARQUEE_DELAY)
}
companion object {
private const val MARQUEE_DELAY = 1200
}
}