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

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()
相关推荐
二流小码农几秒前
鸿蒙开发:wrapBuilder传递参数
android·ios·harmonyos
ljx14000525501 小时前
推荐一个基于Koin, Ktor & Paging等组件的KMM Compose Multiplatform项目
android·kotlin
lrydnh2 小时前
数据库语句
android·数据库
去看全世界的云2 小时前
【Kotlin】Kotlin基础笔记
android·java·笔记·kotlin
tangweiguo030519872 小时前
Android打造易用的 WiFi 工具类:WifiUtils 封装实践
android·java·wifi
Legendary_0082 小时前
LDR6500:革新手机OTG充电体验的关键芯片
android·智能手机
今阳3 小时前
鸿蒙开发笔记-11-LazyForEach 数据懒加载
android·华为·harmonyos
pengyu3 小时前
系统化掌握Flutter组件之Transform:空间魔法师
android·flutter·dart
岸芷漫步3 小时前
retrofit框架分析
android
&有梦想的咸鱼&3 小时前
Android OKHttp缓存模块原理分析
android·okhttp·缓存