• 作者:老汪软件技巧
  • 发表时间:2024-11-20 17:03
  • 浏览量:

在工作中遇到一个实现文本描边效果的需求,这里记录一下。

image.png

Android 中文本的描边效果,我们可以看成是两个文本相互叠加起来。这样最容易想到的方案就有两种:

方案一的代码如下:

class StrokeTextView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyle: Int = 0
) : AppCompatTextView(context, attrs, defStyle) {
    private val strokePaint = Paint().apply {
        style = Paint.Style.STROKE
        strokeWidth = 5f // 描边宽度
        color = Color.RED // 描边颜色,白色
        typeface = this@StrokeTextView.typeface
        isAntiAlias = true
    }
    override fun onDraw(canvas: Canvas) {
        // 设置文本大小
        strokePaint.textSize = textSize
        // 获取文本内容
        val text = text.toString()
        val fontMetrics = strokePaint.fontMetricsInt
        val baseline = (height - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top
        // 绘制描边
        canvas.drawText(text, paddingLeft.toFloat(), baseline.toFloat(), strokePaint)
        // 绘制文本内容
        super.onDraw(canvas)
    }
}

效果如下所示:

使用方案一的效果图

但是对于多行文本,就有问题了。这是因为我们没有在代码中处理这些情况,而且计算文本的换行也比较麻烦,因此不建议使用方案一。

接下来来试试方案二,用两个 TextView 来叠加实现描边效果。代码示例如下:


class StrokeTextView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyle: Int = 0
) : AppCompatTextView(context, attrs, defStyle) {
    //用于描边的TextView
    private var backGroundText = TextView(context, attrs, defStyle)
    override fun setLayoutParams(params: ViewGroup.LayoutParams?) {
        //同步布局参数
        backGroundText.layoutParams = params
        super.setLayoutParams(params)
    }
    override fun onMeasure(
        widthMeasureSpec: Int,
        heightMeasureSpec: Int
    ) {
        val tt = backGroundText.text
        //两个TextView上的文字必须一致
        if (tt == null || tt != this.text) {
            backGroundText.text = text
            this.postInvalidate()
        }
        backGroundText.measure(widthMeasureSpec, heightMeasureSpec)
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }
    override fun onLayout(
        changed: Boolean,
        left: Int,
        top: Int,
        right: Int,
        bottom: Int
    ) {
        backGroundText.layout(left, top, right, bottom)
        super.onLayout(changed, left, top, right, bottom)
    }

_文字描边css_文本效果文本2

override fun onDraw(canvas: Canvas) { val tp1 = backGroundText.paint //设置描边宽度 tp1.strokeWidth = 2f //背景描边并填充全部 tp1.style = Paint.Style.FILL_AND_STROKE //设置描边颜色 backGroundText.setTextColor(Color.RED) backGroundText.gravity = gravity backGroundText.draw(canvas) super.onDraw(canvas) } }

效果如下所示:

使用方案二的效果图

可以看到,如果使用两个 TextView 叠加绘制,就不用考虑换行,以及多行的计算问题了,方便了很多。但是这个方案还是有点缺陷,当文本的颜色为半透明的时候,文本的背景会是描边的颜色,而不是原背景。效果如下所示:

文本的颜色为半透明的效果

如果文本是半透明的情况,上面的叠加方案就无法使用了,需要考虑新的方案。要想半透明的文本效果不被影响,我们只有绘制文本的描边,正好 Paint 提供了 getTextPath 方法来获取文本的 path 路径。通过获取的 path 路径,我们就可以正常的绘制描边了,代码示例如下:

class StrokeTextView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null,
    defStyle: Int = 0
) : AppCompatTextView(context, attrs, defStyle) {
    // 描边画笔
    private val outlinePaint = Paint().apply {
        isAntiAlias = true
    }
    // 描边路径
    private val outlinePath = Path()
    private var strokeWidth = 1f
    /**
     * 初始化画笔属性
     */
    init {
        outlinePaint.strokeWidth = 1f
        outlinePaint.style = Paint.Style.STROKE
        outlinePaint.color = Color.parseColor("#CCFFFFFF")
    }
    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        // 布局变化时更新描边路径
        val xOffset = layout.getLineLeft(0) + paddingLeft
        val baseline = layout.getLineBaseline(0) + paddingTop.toFloat()
        paint.getTextPath(text.toString(), 0, text.length, xOffset, baseline, outlinePath)
    }
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        if (strokeWidth > 0) {
            canvas.save()
            // 确保不在字符内部绘制
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
                canvas.clipPath(outlinePath, android.graphics.Region.Op.DIFFERENCE)
            } else {
                canvas.clipOutPath(outlinePath)
            }
            canvas.drawPath(outlinePath, outlinePaint)
            canvas.restore()
        }
    }
}

效果如下所示:

image.png

获取路径的方案虽然满足半透明文本的需求,但是对于多行,还是需要我们自己来处理,比较麻烦。因此对于没有半透明文本的描边效果的需求,还是推荐使用 使用两个 TextView 叠加 的方案;而半透明文本描边的需求,则需要考虑是否是多行,来做相应的处理。


上一条查看详情 +HarmonyOS Next对称密钥加解密算法全解析
下一条 查看详情 +没有了