Search code examples
javaandroidtextviewextendoutline

How to correctly draw text in an extended class for TextView?


I'm currently working on extending a TextView, adding an outline around the text. Thus far, the only problem I've been having is my inability to position the "outline" correctly behind a text. If I code the extended class like the one portrayed below, I get a label that looks like this:

Note: in the above screenshot, I set the fill color to white, and the stroke color to black.

What am I doing wrong?

public class OutlinedTextView extends TextView {
    /* ===========================================================
     * Constants
     * =========================================================== */
    private static final float OUTLINE_PROPORTION = 0.1f;

    /* ===========================================================
     * Members
     * =========================================================== */
    private final Paint mStrokePaint = new Paint();
    private int mOutlineColor = Color.TRANSPARENT;

    /* ===========================================================
     * Constructors
     * =========================================================== */
    public OutlinedTextView(Context context) {
        super(context);
        this.setupPaint();
    }
    public OutlinedTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.setupPaint();
        this.setupAttributes(context, attrs);
    }
    public OutlinedTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.setupPaint();
        this.setupAttributes(context, attrs);
    }

    /* ===========================================================
     * Overrides
     * =========================================================== */
    @Override
    protected void onDraw(Canvas canvas) {
        // Get the text to print
        final float textSize = super.getTextSize();
        final String text = super.getText().toString();

        // setup stroke
        mStrokePaint.setColor(mOutlineColor);
        mStrokePaint.setStrokeWidth(textSize * OUTLINE_PROPORTION);
        mStrokePaint.setTextSize(textSize);
        mStrokePaint.setFlags(super.getPaintFlags());
        mStrokePaint.setTypeface(super.getTypeface());

        // Figure out the drawing coordinates
        //mStrokePaint.getTextBounds(text, 0, text.length(), mTextBounds);

        // draw everything
        canvas.drawText(text,
                super.getWidth() * 0.5f, super.getBottom() * 0.5f,
                mStrokePaint);
        super.onDraw(canvas);
    }

    /* ===========================================================
     * Private/Protected Methods
     * =========================================================== */
    private final void setupPaint() {
        mStrokePaint.setAntiAlias(true);
        mStrokePaint.setStyle(Paint.Style.STROKE);
        mStrokePaint.setTextAlign(Paint.Align.CENTER);
    }
    private final void setupAttributes(Context context, AttributeSet attrs) {
        final TypedArray array = context.obtainStyledAttributes(attrs,
                R.styleable.OutlinedTextView);
        mOutlineColor = array.getColor(
                R.styleable.OutlinedTextView_outlineColor, 0x00000000);
        array.recycle(); 

        // Force this text label to be centered
        super.setGravity(Gravity.CENTER_HORIZONTAL);
    }
}

Solution

  • Bah, that was stupid of me. I just needed to change-up that commented-out line:

    super.getPaint().getTextBounds(text, 0, text.length(), mTextBounds);
    

    In addition, for actually rendering the text, I need to average this view's height and the text's height:

    // draw everything
    canvas.drawText(text,
        super.getWidth() * 0.5f, (super.getHeight() + mTextBounds.height()) * 0.5f,
        mStrokePaint);
    

    The entire code now reads as follows:

    public class OutlinedTextView extends TextView {
        /* ===========================================================
         * Constants
         * =========================================================== */
        private static final float OUTLINE_PROPORTION = 0.1f;
    
        /* ===========================================================
         * Members
         * =========================================================== */
        private final Paint mStrokePaint = new Paint();
        private final Rect mTextBounds = new Rect();
        private int mOutlineColor = Color.TRANSPARENT;
    
        /* ===========================================================
         * Constructors
         * =========================================================== */
        public OutlinedTextView(Context context) {
            super(context);
            this.setupPaint();
        }
        public OutlinedTextView(Context context, AttributeSet attrs) {
            super(context, attrs);
            this.setupPaint();
            this.setupAttributes(context, attrs);
        }
        public OutlinedTextView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            this.setupPaint();
            this.setupAttributes(context, attrs);
        }
    
        /* ===========================================================
         * Overrides
         * =========================================================== */
        @Override
        protected void onDraw(Canvas canvas) {
            // Get the text to print
            final float textSize = super.getTextSize();
            final String text = super.getText().toString();
    
            // setup stroke
            mStrokePaint.setColor(mOutlineColor);
            mStrokePaint.setStrokeWidth(textSize * OUTLINE_PROPORTION);
            mStrokePaint.setTextSize(textSize);
            mStrokePaint.setFlags(super.getPaintFlags());
            mStrokePaint.setTypeface(super.getTypeface());
    
            // Figure out the drawing coordinates
            super.getPaint().getTextBounds(text, 0, text.length(), mTextBounds);
    
            // draw everything
            canvas.drawText(text,
                    super.getWidth() * 0.5f, (super.getHeight() + mTextBounds.height()) * 0.5f,
                    mStrokePaint);
            super.onDraw(canvas);
        }
    
        /* ===========================================================
         * Private/Protected Methods
         * =========================================================== */
        private final void setupPaint() {
            mStrokePaint.setAntiAlias(true);
            mStrokePaint.setStyle(Paint.Style.STROKE);
            mStrokePaint.setTextAlign(Paint.Align.CENTER);
        }
        private final void setupAttributes(Context context, AttributeSet attrs) {
            final TypedArray array = context.obtainStyledAttributes(attrs,
                    R.styleable.OutlinedTextView);
            mOutlineColor = array.getColor(
                    R.styleable.OutlinedTextView_outlineColor, 0x00000000);
            array.recycle(); 
    
            // Force this text label to be centered
            super.setGravity(Gravity.CENTER_HORIZONTAL);
        }
    }