用100行代码实现Android自定义View跑马灯效果
作者:访客发布时间:2023-12-18分类:程序开发学习浏览:344
这次自定义是为了实现跑马灯效果。
其实跑马灯效果github上已经有很多实现且可以参考的例子了,但他们都太花里胡哨了,又是构建者模式,又是泛型,还有用到了ViewFlipper。我的需求不需要这些,我只是一个简单的String字符串的文字跑马灯,不需要List,不需要添加animator动画,不需要扩展。
想来想去,还是自己写一个跑马灯吧,节前最后一篇文章,估计看的人少。
没错,这次的是,100行代码实现简易跑马灯效果。
一百行代码实现简易跑马灯
在此之前,可以去看看扔物线官网上的 自定义 View 1-3 drawText() 文字的绘制这篇文章,看完之后就会发现,“简易"跑马灯实现起来真的太容易了。
先贴效果图
我知道太丑了,绿底黑字,不过为了显示效果明显,才这么做的,在现实中我对自己的审美很自信的。
代码
直接上代码
class MarqueeView(context: Context, attrs: AttributeSet) : View(context, attrs) {
    private var msg: String = ""//绘制的文字
    private var speed: Int = 1//文字速度
    private val duration = 5L//绘制间隔
    private val textColor by lazy { Color.BLACK }
    private var textSize = 12f
    private val paint by lazy {
        val p = TextPaint(Paint.ANTI_ALIAS_FLAG)
        p.style = Paint.Style.FILL
        p.color = textColor
        val scale = resources.displayMetrics.density
        p.textSize = textSize * scale + 0.5f
        p
    }
    private val rect by lazy { Rect() }
    //协程执行文字的跑马灯效果
    private var task: Job? = null
    //文字的位置
    private var xPos = 0f
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        log(msg)
        canvas?.drawText(msg, xPos, height / 2f + getTextHeight() / 2, paint)
    }
    private fun updateXPosition() {
        if (xPos < -getTextWidth()) {
            xPos = width.toFloat()
        }
    }
    private fun getTextWidth(): Int {
        if (msg.isEmpty()) return 0
        paint.getTextBounds(msg, 0, msg.length, rect)
        return rect.width()
    }
    private fun getTextHeight(): Int {
        if (msg.isEmpty()) return 0
        paint.getTextBounds(msg, 0, msg.length, rect)
        return rect.height()
    }
    fun startWithContent(msg: String): MarqueeView {
        if (msg.isEmpty()) return this
        this.msg = msg
        startRoll()
        return this
    }
    
    private fun startRoll() {
        if (task == null) {
            task = CoroutineScope(Dispatchers.IO).launch {
                while (isActive) {
                    kotlin.runCatching {
                        delay(duration)
                        xPos -= speed
                        updateXPosition()
                        postInvalidate()
                    }
                }
            }
        }
    }
    fun stop(): MarqueeView {
        task?.cancel()
        task = null
        return this
    }
    fun reStart(): MarqueeView {
        if (msg.isEmpty()) return this
        startRoll()
        return this
    }
    private fun log(msg:String) {
        Log.e("TAG", msg)
    }
}
一个完整的自定义View,算上import的,差不多100行不到。
解析
属性都带注释了,重点有两块,主要是这两块实现了跑马灯的效果。
drawText
canvas?.drawText(msg, xPos, height / 2f + getTextHeight() / 2, paint)
第一个参数是跑马灯的内容,第二个参数是文字绘制的X轴坐标,第三个参数是文字绘制的Y轴坐标,第四个参数是画笔。
只要改变绘制起始位置的X轴坐标,就能实现文字的位移。假设msg="abcdefg",当xPos==”ab的宽度“的时候,绘制效果如下,
注意的是,文本框之外的"ab"是不会对用户显示的,而”cdefg“才是真正被用户可见的内容。
updateXPosition
更新文字绘制的X坐标,当字符串"abcdefg"全部都左移到文本框外时,需要更新X轴的坐标,才能实现循环滚动的效果。
    private fun updateXPosition() {
        if (xPos < -getTextWidth()) {
            xPos = width.toFloat()
        }
    }
调用
xml中的代码
<com.testdemo.MarqueeView
    android:id="@+id/marquee"
    android:layout_width="200dp"
    android:layout_height="40dp"
    android:background="@color/c_green"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />
activity中的代码
val marqueeView = findViewById<MarqueeView>(R.id.marquee)
marqueeView.startWithContent("abcdefg")//测试文字:各方人员请注意,接下来是跑马灯,跑马灯,跑马灯要来了!
结语
以上就是简易跑马灯的全部实现代码了。
当然,还有很多是可以优化的,目前文字必须是全部移除视野后才会重新从右边移入,中间有一个width宽度的空白。这个可以做优化。不过刚才尝试了一下,新增一个xPos1作为衔接文字的坐标,衔接文字与上一个文字之间的宽度设置为8个字符,在onDraw中绘制两次文字,代码如下
...省略代码...
private var xPos1 = 0f
private val space by lazy {
    val s = "aaaaaaaa"//8个空格作为衔接
    paint.getTextBounds(s, 0, s.length, rect)
    rect.width()
}
...省略代码...
override fun onDraw(canvas: Canvas?) {
    ...省略代码...
    canvas?.drawText(msg, xPos1, height / 2f + getTextHeight() / 2, paint)
}
...省略代码...
/**
 * 更新绘制的位置,实现循环
 * 第二个text的位置是在第一个位置后面的8个空格后
 */
private fun updateXPosition() {
    val textWidth = getTextWidth()
    if (xPos < -textWidth) {
        xPos = xPos1 + textWidth + space
    }
    if (xPos > -textWidth && xPos < width - textWidth) {
        xPos1 = xPos + space + textWidth
    } else {
        xPos1 -= speed
    }
}
...省略代码...
gif就不放了,你们可以直接贴代码。用示例文字"各方人员请注意,接下来是跑马灯,跑马灯,跑马灯要来了!"运行起来没问题。
这就好了?
不,不!要知道,此时,文字的总长度是大于自定义View的width的,假如文字的长度小于width的话,就又要考虑不同的情形了。有点麻烦,updateXPosition()函数内的逻辑就复杂起来了。(内心独白:我是一个容易放弃的人,这个就算了,还是让文字身后留出一个自定义view的宽度的空白吧)
还有,假如我想要滚动的不是一个字符串,而是字符串列表List,那怎么办?
这个也容易,直接贴代码(完整版,这个应该不用改了)
class MarqueeView(context: Context, attrs: AttributeSet) : View(context, attrs) {
    private var msg: String = ""//绘制的文字
    private var speed: Int = 1//文字速度
    private var duration = 5L//绘制间隔
    private var textColor = Color.BLACK
    private var textSize = 12f
    private val scale by lazy { resources.displayMetrics.density }
    private val paint by lazy {
        val p = TextPaint(Paint.ANTI_ALIAS_FLAG)
        p.style = Paint.Style.FILL
        p.color = textColor
        p.textSize = textSize * scale + 0.5f
        p
    }
    private val rect by lazy { Rect() }
    //协程执行文字的跑马灯效果
    private var task: Job? = null
    //文字的位置
    private var xPos = 0f
    private val dataList by lazy { ArrayList<String>() }
    private var dataPos = 0
    private var showList = false//设置标记位,判断是否显示list
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        canvas?.drawText(msg, xPos, height / 2f + getTextHeight() / 2, paint)
    }
    private fun updateXPosition() {
        val textWidth = getTextWidth()
        if (xPos < -textWidth) {
            xPos = width.toFloat()
        }
    }
    private fun getTextWidth(): Int {
        if (msg.isEmpty()) return 0
        paint.getTextBounds(msg, 0, msg.length, rect)
        return rect.width()
    }
    private fun getTextHeight(): Int {
        if (msg.isEmpty()) return 0
        paint.getTextBounds(msg, 0, msg.length, rect)
        return rect.height()
    }
    fun startWithContent(msg: String): MarqueeView {
        if (msg.isEmpty()) return this
        this.msg = msg
        startRoll()
        return this
    }
    private fun startRoll() {
        if (task == null) {
            task = CoroutineScope(Dispatchers.IO).launch {
                while (isActive) {
                    kotlin.runCatching {
                        delay(duration)
                        xPos -= speed
                        updateXPosition()
                        updateMsg()
                        postInvalidate()
                    }
                }
            }
        }
    }
    fun stop(): MarqueeView {
        task?.cancel()
        task = null
        return this
    }
    fun reStart(): MarqueeView {
        if (msg.isEmpty()) return this
        startRoll()
        return this
    }
    fun setSpeed(speed: Int): MarqueeView {
        if (speed == 0) return this
        this.speed = speed
        return this
    }
    fun setDuration(duration: Long): MarqueeView {
        if (duration == 0L) return this
        this.duration = duration
        return this
    }
    fun setTextColor(@ColorInt textColor: Int): MarqueeView {
        this.textColor = textColor
        paint.color = textColor
        return this
    }
    fun setTextSize(textSize: Float): MarqueeView {
        if (textSize < 10f) return this
        paint.textSize = textSize * scale + 0.5f
        return this
    }
    fun startWithList(data: List<String>) : MarqueeView{
        showList = true
        dataList.clear()
        dataList.addAll(data)
        if (dataList.isEmpty()) return this
        startRoll()
        dataPos = 0
        return this
    }
    private fun updateMsg() {
        if (!showList || dataList.isEmpty() || xPos <= (width - speed)) return
        msg = dataList[dataPos++ % dataList.size]
    }
}
相关推荐
- 轻松的Dropbox集成-从您的WordPress网站最好的WordPress常用插件下载博客插件模块浏览、上传、查看、下载和管理Dropbox文件
- 轻松上手:(三)笔记可再编辑 
- 如何在wordPress中轻松嵌入IFRAME代码
- 如何在WordPress中创建自定义帖子类型存档页面
- 如何在iPhone,iPad和Android上使用WordPress应用程序
- 如何将自定义域名博客转移到WordPress
- 如何在WordPress中创建自定义固定链接
- 如何在wordpress中将自定义元字段添加到自定义分类
- 如何在WordPress中显示前后照片
- 如何在WordPress 3.0中使用自定义帖子类型
- 程序开发学习排行
- 最近发表


