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

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()
相关推荐
wu_android13 分钟前
Android 线性布局中常见的冲突属性总结
android
恋猫de小郭37 分钟前
为什么跨平台框架可以适配鸿蒙,它们的技术原理是什么?
android·前端·flutter
张风捷特烈2 小时前
每日一题 Flutter#5,6 | 两道 Widget 选择题
android·flutter
移动开发者1号2 小时前
App主界面点击与跳转启动方式区别
android·kotlin
移动开发者1号2 小时前
我用Intent传大图片时竟然崩了,怎么回事啊
android·kotlin
androidwork14 小时前
Android LinearLayout、FrameLayout、RelativeLayout、ConstraintLayout大混战
android·java·kotlin·androidx
每次的天空14 小时前
Android第十三次面试总结基础
android·面试·职场和发展
wu_android14 小时前
Android 相对布局管理器(RelativeLayout)
android
李斯维16 小时前
循序渐进 Android Binder(二):传递自定义对象和 AIDL 回调
android·java·android studio
androidwork16 小时前
OkHttp 3.0源码解析:从设计理念到核心实现
android·java·okhttp·kotlin