如何使用不同的描边和填充颜色绘制文本?

33

我想在我的应用程序中以以下方式显示文本。 我正在使用Paint类,并使用样式FILL_AND_STROKE来实现此目的。 但是,只有一个方法setColor()可用于设置颜色。

如何设置不同的描边和填充颜色?

具有不同描边和填充颜色的文本

6个回答

20

在自定义的TextView内(不适用于EditText):

@Override
public void onDraw(Canvas canvas)
{
    final ColorStateList textColor = getTextColors();

    TextPaint paint = this.getPaint();

    paint.setStyle(Style.STROKE);
    paint.setStrokeJoin(Join.ROUND);
    paint.setStrokeMiter(10);
    this.setTextColor(strokeColor);
    paint.setStrokeWidth(strokeWidth);

    super.onDraw(canvas);
    paint.setStyle(Style.FILL);

    setTextColor(textColor);
    super.onDraw(canvas);
}

如果您在自定义EditText中实现它,则此方法无效,这种情况下只会显示最后一个setTextColor,您有什么想法为什么会这样? - TilalHusain
1
@Architact 很可能是因为 EditText 中的 onDraw 实现默认绘制了背景,因此第二次调用会覆盖第一次。这仅适用于透明背景。如果背景不透明,则需要从超类中复制整个 onDraw,并仅绘制文本两次。 - Trilarion
它将永远循环执行 onDraw。 - user924
3
警告!这将无限循环onDraw函数,递归。 - user924

20

不要使用FILL_AND_STROKE。先用FILL绘制一次,然后更改颜色,并使用STROKE再绘制一次。

(这适用于矩形。我不确定STROKE是否适用于文本。您需要尝试并找出结果。)


我尝试过了,但当笔画宽度较大时,结果的文本看起来不太好。 - Yugandhar Babu
2
对于更高的文本大小和笔画宽度,结果看起来很丑。 - Yugandhar Babu
我强烈建议先使用STROKE,然后再使用FILL。这样文本看起来会更清晰锐利。 - Simon

1
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Paint.Join
import android.graphics.Rect
import android.util.AttributeSet
import android.widget.TextView

@SuppressLint("AppCompatCustomView")
class BorderTextView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : TextView(context, attrs, defStyleAttr) {
    private var strokeWidth: Float = 0F
    private val paintStroke = Paint(Paint.ANTI_ALIAS_FLAG)

    init {
        paint.color = currentTextColor
        paint.typeface = typeface

        if (attrs != null) {
            val a = context.obtainStyledAttributes(attrs, R.styleable.BorderTextView)
            if (a.hasValue(R.styleable.BorderTextView_strokeColor)) {
                strokeWidth =
                    a.getDimensionPixelSize(R.styleable.BorderTextView_strokeWidth, 1).toFloat()
                val strokeColor = a.getColor(R.styleable.BorderTextView_strokeColor, 0)
                val strokeMiter =
                    a.getDimensionPixelSize(R.styleable.BorderTextView_strokeMiter, 10).toFloat()
                var strokeJoin: Join? = null
                when (a.getInt(R.styleable.BorderTextView_strokeJoinStyle, 2)) {
                    0 -> strokeJoin = Join.MITER
                    1 -> strokeJoin = Join.BEVEL
                    2 -> strokeJoin = Join.ROUND
                }
                setStroke(strokeWidth, strokeColor, strokeJoin, strokeMiter)
            }
            a.recycle()
        }
    }

    private fun setStroke(width: Float, color: Int, join: Join?, miter: Float) {
        paintStroke.strokeJoin = join
        paintStroke.strokeMiter = miter
        paintStroke.strokeWidth = width
        paintStroke.style = Paint.Style.STROKE
        paintStroke.color = color
        paintStroke.textSize = textSize
        paintStroke.typeface = typeface
        paintStroke.letterSpacing = letterSpacing
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        this.setMeasuredDimension(measuredWidth + strokeWidth.toInt(), measuredHeight);
    }
    override fun onDraw(canvas: Canvas) {
        val r = Rect()
        paint.getTextBounds(text.toString(), 0, text.length, r)
        val desc = paint.descent()
        val asc = paint.ascent()
        val y = (height.toFloat() - (1 + asc + desc / 2F)) / 2F
        val x = width / 2f - r.width() / 2f - r.left
        canvas.drawText(text.toString(), x, y, paintStroke)
        canvas.drawText(text.toString(), x, y, paint)
    }
}

1

先画线条,再写文字。

警告:setTextColor会递归调用onDraw方法,因此需要避免这种情况,参见“callInvalidate”标志。

class StrokeTextView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = android.R.attr.textViewStyle
) : AppCompatTextView(context, attrs, defStyleAttr) {

    private val realTextColor: ColorStateList = textColors
    private var strokeColor: ColorStateList? = null
    private var strokeWidth: Float? = null

    private var callInvalidate = true

    init {
        context.obtainStyledAttributes(attrs, R.styleable.StrokeTextView, defStyleAttr, 0).use {
            strokeColor = it.getColorStateList(R.styleable.StrokeTextView_stroke_color)
            strokeWidth = it.getDimension(R.styleable.StrokeTextView_stroke_width, 0F)
        }
    }

    override fun onDraw(canvas: Canvas) {
        if (strokeWidth != null && strokeWidth!! > 0 && strokeColor != null) {
            //stroke
            setTextColorOnDraw(strokeColor!!)
            paint.style = Paint.Style.STROKE
            paint.strokeWidth = strokeWidth!!
            super.onDraw(canvas)
            //text
            setTextColorOnDraw(realTextColor)
            paint.style = Paint.Style.FILL
            paint.strokeWidth = 0F
            super.onDraw(canvas)
        } else {
            //default
            super.onDraw(canvas)
        }
    }

    override fun invalidate() {
        if (callInvalidate) {
            super.invalidate()
        }
    }

    /**
     * Call setTextColor in OnDraw.
     */
    private fun setTextColorOnDraw(colors: ColorStateList) {
        callInvalidate = false
        setTextColor(colors)
        callInvalidate = true
    }

1
我使用上述第一种解决方案得出了这个想法:先绘制一个较大的STROKE文本,然后在其上覆盖一个较小的FILL_AND_STROKE文本。
mScorePaint = new TextPaint();
mScorePaint.setTextSize(63);
mScorePaint.setStyle(Style.STROKE);
mScorePaint.setStrokeJoin(Join.ROUND);
mScorePaint.setStrokeMiter(10.0f);
mScorePaint.setStrokeWidth(frameWidth/50.0f); // about 12
mScorePaint.setColor(0xffff0000); // black

c.drawText(Integer.toString(mScore), x, y, mScorePaint);  // red first

mScorePaint.setStrokeWidth(frameWidth/125.0f); // about 5
mScorePaint.setColor(0xff000000); // red

c.drawText(Integer.toString(mScore), x, y, mScorePaint);  // black on top

由于仅使用FILL未能看到任何Stroke属性,并且呈现出非常细的效果。

0

不是很确定,但也许你可以使用这个:

链接

 TextView test = (TextView) findViewById(R.id.test);

 test.setShadowLayer(float, float, float, int);

3
阴影层是不同的,它会在文字后面放置一种指定的颜色,使得文字看起来稍微模糊了一些。 - Yugandhar Babu
嗯,我想我知道你的意思了,它不会创建干净的边缘,对吧?我在考虑将文本颜色设置为黑色,阴影设置为红色或其他颜色。 - FabianCook
看看“Hi”边缘的背景颜色不模糊,这就是我所说的干净的边缘。 - FabianCook
1
不,阴影不会产生清晰的边缘,它会显示模糊的颜色。 - Yugandhar Babu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接