闽公网安备 35020302035485号

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
//堆代码 duidaima.com
//文字的位置
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行不到。canvas?.drawText(msg, xPos, height / 2f + getTextHeight() / 2, paint)第一个参数是跑马灯的内容,第二个参数是文字绘制的X轴坐标,第三个参数是文字绘制的Y轴坐标,第四个参数是画笔。只要改变绘制起始位置的X轴坐标,就能实现文字的位移。假设msg="abcdefg",当xPos==”ab的宽度“的时候,绘制效果如下,

private fun updateXPosition() {
if (xPos < -getTextWidth()) {
xPos = width.toFloat()
}
}
调用<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")//测试文字:各方人员请注意,接下来是跑马灯,跑马灯,跑马灯要来了!
...省略代码...
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
// 堆代码 duidaima.com
//文字的位置
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]
}
}