自定义MarqueeView 实现跑马灯效果,可根据自定义速度、暂停和继续播放
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Typeface
import android.text.TextPaint
import android.util.AttributeSet
import android.util.Log
import android.util.TypedValue
import android.view.View
import android.view.animation.LinearInterpolator
import androidx.annotation.ColorInt
import com.example.flutter_tap.R
/**
* Created by wsm on 2025/8/18
* <p>自定义跑马灯View
*/
class MarqueeView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var TAG = "MarqueeView"
private val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
private var text = ""
private var offsetX = 0f
private var animator: ValueAnimator? = null
private var isPaused = false
private var remainingDuration = 0L // 剩余播放时间
private var pausedOffsetX = 0f // 记录暂停时的位置
init {
// 解析XML属性
attrs?.let {
val typedArray = context.obtainStyledAttributes(
it,
R.styleable.MarqueeView,
defStyleAttr,
0
)
// 字体大小(默认16sp)
textPaint.textSize = typedArray.getDimensionPixelSize(
R.styleable.MarqueeView_android_textSize,
16.spToPx().toInt()
).toFloat()
// 字体颜色(默认黑色)
textPaint.color = typedArray.getColor(
R.styleable.MarqueeView_android_textColor,
Color.BLACK
)
typedArray.recycle()
}
}
/**
* 设置文本
*/
fun setText(text: String) {
this.text = text
requestLayout()
invalidate()
}
/**
* 设置字体类型(Typeface)
*/
fun setTypeface(typeface: Typeface) {
textPaint.typeface = typeface
invalidate()
}
/**
* 设置字体大小(单位:sp)
*/
fun setTextSize(spSize: Int) {
textPaint.textSize = spSize.spToPx()
invalidate()
}
/**
* 动态设置字体颜色
*/
fun setTextColor(@ColorInt color: Int) {
textPaint.color = color
invalidate()
}
/** 开始滚动
* @param durationMs 滚动持续时间(毫秒)值越小速度越快
* @param startX 滚动起始位置(默认从右侧边界外开始)
*/
fun startMarquee(durationMs: Long, startX: Float? = null) {
stopMarquee() // 停止之前的动画
val textWidth = textPaint.measureText(text)
val viewWidth = width.toFloat()
Log.d(TAG, "startMarquee: ")
// 从右侧边界外开始(startX = viewWidth)
// 滚动到左侧边界外结束(endX = -textWidth)
animator = ValueAnimator.ofFloat(startX ?: viewWidth, -textWidth).apply {
duration = durationMs
interpolator = LinearInterpolator() // 匀速滚动
addUpdateListener { animation ->
offsetX = animation.animatedValue as Float
invalidate()
}
start()
}
isPaused = false
}
/**
* 暂停滚动
*/
fun pause() {
Log.d(TAG, "pause: ")
if (animator?.isRunning == true) {
pausedOffsetX = offsetX // 记录当前位置
remainingDuration = animator?.duration?.minus(animator?.currentPlayTime ?: 0) ?: 0
animator?.cancel()
isPaused = true
}
}
/**
* 继续滚动
*/
fun resume() {
Log.d(TAG, "resume: ")
if (isPaused && remainingDuration > 0) {
startMarquee(remainingDuration, pausedOffsetX)
}
}
/**
* 停止滚动(完全重置)
*/
fun stopMarquee() {
Log.d(TAG, "stopMarquee: ")
animator?.cancel()
animator = null
isPaused = false
remainingDuration = 0
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 绘制文本(y坐标居中)
canvas.drawText(text, offsetX, height / 2f + textPaint.textSize / 3, textPaint)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
stopMarquee()
}
/**
* sp转px的扩展函数
*/
private fun Int.spToPx(): Float {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
this.toFloat(),
context.resources.displayMetrics
)
}
}
res/values/attrs.xml文件下
<!--跑马灯-->
<declare-styleable name="MarqueeView">
<!-- 字体大小(单位:sp) -->
<attr name="android:textSize" />
<!-- 字体颜色 -->
<attr name="android:textColor" />
</declare-styleable>
布局文件中引入自定义文件
<com.example.view.MarqueeView
android:id="@+id/marqueeView"
android:layout_width="match_parent"
android:layout_height="20dp"
android:layout_marginStart="45dp"
android:layout_marginEnd="45dp"
android:textColor="#ff191919"
android:textSize="16dp" />
代码中实现
marqueeView = findViewById(R.id.marqueeView);
marqueeView.setText("这是一段实现跑马灯效果的文字,可控制播放速度...");
marqueeView.startMarquee(5000L, null);//5秒播放