安卓中设置渐变字体和描边字体

1.CommonFontSpan

复制代码
abstract class CommonFontSpan : ReplacementSpan() {

    /** 测量的文本宽度  */
    private var mMeasureTextWidth = 0f

    override fun getSize(
        paint: Paint,
        text: CharSequence?,
        start: Int,
        end: Int,
        fontMetricsInt: FontMetricsInt?
    ): Int {
        mMeasureTextWidth = onMeasure(paint, fontMetricsInt, text, start, end)
        // 这段不可以去掉,字体高度没设置,会出现 draw 方法没有被调用的问题
        val metrics = paint.fontMetricsInt
        if (fontMetricsInt != null) {
            fontMetricsInt.top = metrics.top
            fontMetricsInt.ascent = metrics.ascent
            fontMetricsInt.descent = metrics.descent
            fontMetricsInt.bottom = metrics.bottom
        }
        return mMeasureTextWidth.toInt()
    }

    override fun draw(
        canvas: Canvas,
        text: CharSequence?,
        start: Int,
        end: Int,
        x: Float,
        top: Int,
        y: Int,
        bottom: Int,
        paint: Paint
    ) {
        val alpha = paint.alpha
        // 判断是否给画笔设置了透明度
        if (alpha != 255) {
            // 如果是则设置不透明
            paint.alpha = 255
        }
        text?.let {
            onDraw(canvas, paint, it, start, end, x, top, y, bottom)
        }
        // 绘制完成之后将画笔的透明度还原回去
        paint.alpha = alpha
    }

    private fun onMeasure(
        paint: Paint,
        fontMetricsInt: FontMetricsInt?,
        text: CharSequence?,
        @IntRange(from = 0) start: Int,
        @IntRange(from = 0) end: Int
    ): Float {
        return paint.measureText(text, start, end)
    }

    abstract fun onDraw(
        canvas: Canvas,
        paint: Paint,
        text: CharSequence,
        @IntRange(from = 0) start: Int,
        @IntRange(from = 0) end: Int,
        x: Float,
        top: Int,
        y: Int,
        bottom: Int
    )

    fun getMeasureTextWidth(): Float {
        return mMeasureTextWidth
    }
}

2.MultiFontSpan

复制代码
class MultiFontSpan(vararg replacementSpans: ReplacementSpan) : ReplacementSpan() {

    /** 测量的文本宽度  */
    private var mMeasureTextWidth = 0f
    private var mReplacementSpans: List<ReplacementSpan> = mutableListOf()

    init {
        mReplacementSpans = replacementSpans.toList()
    }

    override fun getSize(
        paint: Paint,
        text: CharSequence?,
        start: Int,
        end: Int,
        fm: Paint.FontMetricsInt?
    ): Int {
        for (replacementSpan in mReplacementSpans) {
            val size = replacementSpan.getSize(paint, text, start, end, fm)
            mMeasureTextWidth = Math.max(mMeasureTextWidth, size.toFloat())
        }
        return mMeasureTextWidth.toInt()
    }

    override fun draw(
        canvas: Canvas,
        text: CharSequence?,
        start: Int,
        end: Int,
        x: Float,
        top: Int,
        y: Int,
        bottom: Int,
        paint: Paint
    ) {
        for (replacementSpan in mReplacementSpans) {
            replacementSpan.draw(canvas, text, start, end, x, top, y, bottom, paint)
        }
    }

    override fun updateDrawState(ds: TextPaint?) {
        super.updateDrawState(ds)
        for (replacementSpan in mReplacementSpans) {
            replacementSpan.updateDrawState(ds)
        }
    }

    override fun updateMeasureState(p: TextPaint) {
        super.updateMeasureState(p)
        for (replacementSpan in mReplacementSpans) {
            replacementSpan.updateMeasureState(p)
        }
    }
}

3.LinearGradientFontSpan

复制代码
class LinearGradientFontSpan(
    private val mTextGradientColor: IntArray,     /** 文字渐变颜色组 */
    private val mTextSize: Int? = null,     /** 文字渐变字体大小(字体大小不一致的话这里必须动态设置) */
    private val mTextGradientOrientation: Int? = LinearLayout.VERTICAL,    /** 文字渐变方向 */
    private val mTextGradientPositions: FloatArray? = null,   /** 文字渐变位置组 */
) : CommonFontSpan() {

    override fun onDraw(
        canvas: Canvas,
        paint: Paint,
        text: CharSequence,
        start: Int,
        end: Int,
        x: Float,
        top: Int,
        y: Int,
        bottom: Int
    ) {
        mTextSize?.let { paint.textSize = it.sp }
        val linearGradient = if (mTextGradientOrientation == LinearLayout.VERTICAL) {
            LinearGradient(0f, 0f, 0f, bottom.toFloat(), mTextGradientColor, mTextGradientPositions, Shader.TileMode.REPEAT)
        } else {
            LinearGradient(x, 0f, x + getMeasureTextWidth(), 0f, mTextGradientColor, mTextGradientPositions, Shader.TileMode.REPEAT)
        }
        paint.shader = linearGradient
        canvas.drawText(text, start, end, x, y.toFloat(), paint)
    }

    override fun getSize(
        paint: Paint,
        text: CharSequence?,
        start: Int,
        end: Int,
        fontMetricsInt: Paint.FontMetricsInt?
    ): Int {
        mTextSize?.let { paint.textSize = it.sp }
        return super.getSize(paint, text, start, end, fontMetricsInt)
    }
}

4.StrokeFontSpan

复制代码
class StrokeFontSpan(
    private val mTextStrokeColor: Int,
    private val mTextStrokeSize: Int,
    private val isSp: Boolean = true,
) : CommonFontSpan() {

    /** 描边画笔  */
    private val mStrokePaint = Paint()

    override fun onDraw(
        canvas: Canvas,
        paint: Paint,
        text: CharSequence,
        start: Int,
        end: Int,
        x: Float,
        top: Int,
        y: Int,
        bottom: Int
    ) {
        mStrokePaint.set(paint)
        // 设置抗锯齿
        mStrokePaint.isAntiAlias = true
        // 设置防抖动
        mStrokePaint.isDither = true
        mStrokePaint.textSize = paint.textSize
        // 描边宽度
        mStrokePaint.strokeWidth = if (isSp) mTextStrokeSize.dp else mTextStrokeSize.toFloat()
        mStrokePaint.style = Paint.Style.STROKE
        // 设置粗体
        paint.isFakeBoldText = paint.typeface === Typeface.DEFAULT_BOLD
        mStrokePaint.color = mTextStrokeColor
        canvas.drawText(text, start, end, x, y.toFloat(), mStrokePaint)
        canvas.drawText(text, start, end, x, y.toFloat(), paint)
    }
}

5.使用

复制代码
SpanUtils.with(mBindView.tvAnswerBtn1)
            .append("豆奶")
            .setSpans(StrokeFontSpan(Color.WHITE, 1))
            .append("好喝")
            .setSpans(LinearGradientFontSpan(intArrayOf(Color.RED, Color.BLUE)))
            .append("还要")
            .setSpans(
                MultiFontSpan(
                    StrokeFontSpan(Color.GREEN, 2),
                    LinearGradientFontSpan(intArrayOf(Color.WHITE, Color.YELLOW))
                )
            )
            .create()

设置渐变字体 大小不同

复制代码
 val colors = intArrayOf(Color.parseColor("#FDF4E9"), Color.parseColor("#FFDD63"))
        SpanUtils.with(mBindView.tvMaxMoney)
            .append("30")
            .setBold()
            .setSpans(LinearGradientFontSpan(colors, 100))
            .append("元")
            .setSpans(LinearGradientFontSpan(colors, 32))
            .create()
相关推荐
dalancon37 分钟前
SurfaceControl 的事务提交给 SurfaceFlinger,以及 SurfaceFlinger 如何将这些数据设置到对应 Layer 的完整流程
android
dalancon39 分钟前
SurfaceFlinger Layer 到 HWC 通信流程详解
android
cccccc语言我来了1 小时前
Linux(9)操作系统
android·java·linux
yige451 小时前
【MySQL】MySQL内置函数--日期函数字符串函数数学函数其他相关函数
android·mysql·adb
洞见前行2 小时前
AI 当逆向工程师:Claude Code 自主分析 APK 和 so 文件,解决 Unity 插件化启动崩溃
android·人工智能
努力进修2 小时前
旧安卓手机别扔!用KSWEB搭个人博客,搭配外网访问超香
android·智能手机·cpolar
范特西林3 小时前
一文看懂Android SELinux 策略,从“拒绝”到“允许”的距离
android
客卿1234 小时前
用两个栈实现队列
android·java·开发语言
studyForMokey4 小时前
【Android面试】Gradle专题
android·面试·职场和发展
向上_503582916 小时前
配置Protobuf输出Java文件或kotlin文件
android·java·开发语言·kotlin