Progress bar masked by text

This custom view shows a progress bar in the foreground of a TextView. Check the Pixplicity Authenticator app for an example.

Simply call setProgress(value) on the view to set the position of the background bar.

ProgressTextView.java

package com.pixplicity.example.ui;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Shader;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.View;

import com.pixplicity.example.R;

public class ProgressTextView extends android.support.v7.widget.AppCompatTextView {

    private Paint mPaintBg, mPaintText;
    private int mColorBg, mColorFg, mColorWarning;
    private float mProgress;
    private Shader mShader;
    final private Rect mTextBounds = new Rect();

    public ProgressTextView(Context context) {
        super(context);
        init();
    }

    public ProgressTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ProgressTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mPaintBg = new Paint();
        mPaintBg.setColor(mColorBg);
        mPaintBg.setStyle(Paint.Style.FILL);

        mPaintText = getPaint();

        mColorBg = ContextCompat.getColor(getContext(), R.color.progress_background);
        mColorFg = ContextCompat.getColor(getContext(), R.color.progress_foreground);
        mColorWarning = ContextCompat.getColor(getContext(), R.color.progress_warning);

        setLayerType(View.LAYER_TYPE_SOFTWARE, null);

        setProgress(isInEditMode() ? 50 : 0);
    }

    @SuppressLint("WrongCall")
    public void setProgress(float percentage) {
        mProgress = percentage;

        mShader = null;

        invalidate();
    }

    @Override
    public void onDraw(Canvas canvas) {
        if (mShader == null) {
            Bitmap original = Bitmap.createBitmap(
                    getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
            Canvas originalCanvas = new Canvas(original);

            // Drawing progress bar onto temp canvas
            mPaintBg.setColor(mColorBg);
            originalCanvas.drawRect(0, 0, originalCanvas.getWidth(), originalCanvas.getHeight(), mPaintBg);
            mPaintBg.setColor(mProgress < 25 ? mColorWarning : mColorFg);
            mPaintText.getTextBounds(getText().toString(), 0, getText().length(), mTextBounds);
            final int textWidth = Math.min(mTextBounds.width(), originalCanvas.getWidth());
            originalCanvas.drawRect(getPaddingLeft(), 0, getPaddingLeft() + (mProgress * textWidth) / 100f, originalCanvas.getHeight(), mPaintBg);

            // Turn temp canvas into a shader
            mShader = new BitmapShader(original,
                    Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
            mPaintText.setShader(mShader);
        }

        // Draw text using the shader
        float bottom = .5f * (canvas.getHeight() + mTextBounds.height());
        canvas.drawText(
                getText().toString(),
                getPaddingLeft(),
                bottom,
                mPaintText);
    }
}