android 解决文字跑马灯与属性动画冲突的问题

前言

最近在做一个跑马灯的需求, 本以为一秒就能加上, 没想到掉到坑里两天…

按照以前的写法是这样的:

    <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
    }
}

你可能感兴趣的:(Android知识)