33

I want to display text as below in my app. I am using Paint class with style FILL_AND_STROKE to achieve this. But only one method setColor() is available to set the color.

How do I set different stroke and fill colors?

text with different stroke and fill colors

Cœur
  • 37,241
  • 25
  • 195
  • 267
Yugandhar Babu
  • 10,311
  • 9
  • 42
  • 67

6 Answers6

20

Inside custom TextView (does not work in 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);
}
NoDataDumpNoContribution
  • 10,591
  • 9
  • 64
  • 104
Sergei S
  • 2,553
  • 27
  • 36
  • This does not work if you implement it in custom EditText, in that case on last setTextColor is shown, any idea why? – TilalHusain Jan 23 '14 at 17:15
  • 1
    @Architact Most probably because the implementation of `onDraw` from `EditText` draws a background by default and therefore the second call paints over the first. This only works for transparent background. What you have to do in case of non-transparent background is copying the whole onDraw from super and only draw the text two times. – NoDataDumpNoContribution Jan 05 '16 at 20:42
  • it will loop onDraw forever – user924 Nov 18 '17 at 11:24
  • 3
    WARNING! This will loop onDraw function forever, recursive – user924 Aug 10 '20 at 08:13
20

Don't use FILL_AND_STROKE. Draw once with FILL and then change the color and draw with STROKE.

(That works for rectangles. I'm not sure STROKE works at all for text. You'll have to try it and find out.)

Reuben Scratton
  • 38,595
  • 9
  • 77
  • 86
1

I used the first solution above to come up with this idea: put down a larger STROKE, text and then overlay it with a smaller FILL_AND_STROKE text:

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

Because the FILL alone was not seeing any of the Stroke attributes and was coming out very thin.

Amy McBlane
  • 164
  • 3
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

First draw stroke, then draw text.

WARNING: setTextColor will call onDraw recursively, so you need avoid this, see 'callInvalidate' flag.

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
    }
xymelon
  • 315
  • 2
  • 7
0

not perfectly sure, but maybe you could use this:

link

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

 test.setShadowLayer(float, float, float, int);
FabianCook
  • 20,269
  • 16
  • 67
  • 115
  • 3
    The shadow layer is different, it will put a specified color behind the text, then the text will appear as some what blurred. – Yugandhar Babu Jan 28 '12 at 11:18
  • Hmm, I think I know what you mean, It wouldn't create the clean edge would it? I was thinking using setting the text color black and like the shadow red or something – FabianCook Jan 28 '12 at 12:15
  • See how about on "Hi" the edge of the background color is not blurry, that's what I mean by a clean edge – FabianCook Jan 28 '12 at 12:18
  • 1
    no the shadow won't give clean edge, it will display a blurred color. – Yugandhar Babu Jan 28 '12 at 12:19