12

I need to display a TextView over a gradient background. The TextView itself should have a plain white background, and the text should be transparent.

However, setting a transparent color (#00000000) to the text doesn't work: it only shows a white rectangle, the background doesn't show up where the text is (the text takes the same color as the TextView background).

How can I display a transparent text with a background color on my TextView?

Marc Plano-Lesay
  • 6,808
  • 10
  • 44
  • 75
  • it seems you want the text to go through and get the color of the gradient background. then set the color of the text as the color of the gradient, doesnt it give you the same result?! – Coderji Nov 14 '13 at 09:42
  • No, as the gradient is displayed as the `Activity` background. The text should display only a small subset of the gradient, as it's not taking the whole `Activity` size. – Marc Plano-Lesay Nov 14 '13 at 09:45
  • Osama is right.Simple solution is set color of text as the color of gradient. here your textview seems transparent. that why you are seeing white background. to confirm it, your can set red color and see, it will show you red. – Connecting life with Android Nov 14 '13 at 10:04
  • As I said, no, it's not the solution. On the background (`Activity`'s), the gradient is spread accross the whole screen. If I set the gradient as my text color, it will be spread only accross the TextView. The difference is clearly visible as every color in my gradient is shown on a much smaller area. – Marc Plano-Lesay Nov 14 '13 at 10:11

4 Answers4

18

Update, Jan 30, 2016

I made a small library and written a blog post out of this answer, so you don't need to copy and paste code and I do the maintenance for you. :)

Use the view in xml as:

<it.gilvegliach.android.transparenttexttextview.TransparentTextTextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/view_bg"
    android:text="Hello World" />

Gradle dependency:

 compile 'it.gilvegliach.android:transparent-text-textview:1.0.3'

Original Answer

This is how you can achieve that effect:

  1. you render the text over a transparent background on a bitmap
  2. you use that bitmap to clip the text shape out of the solid white background

Here is a simple subclass of TextView that does that.

final public class SeeThroughTextView extends TextView
{
    Bitmap mMaskBitmap;
    Canvas mMaskCanvas;
    Paint mPaint;

    Drawable mBackground;
    Bitmap mBackgroundBitmap;
    Canvas mBackgroundCanvas;
    boolean mSetBoundsOnSizeAvailable = false;

    public SeeThroughTextView(Context context)
    {
        super(context);

        mPaint = new Paint();
        mPaint.setXfermode(new PorterDuffXfermode(Mode.DST_OUT));
        super.setTextColor(Color.BLACK);
        super.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
    }

    @Override
    @Deprecated
    public void setBackgroundDrawable(Drawable bg)
    {
        mBackground = bg;
        int w = bg.getIntrinsicWidth();
        int h = bg.getIntrinsicHeight();

        // Drawable has no dimensions, retrieve View's dimensions
        if (w == -1 || h == -1)
        {
            w = getWidth();
            h = getHeight();
        }

        // Layout has not run
        if (w == 0 || h == 0)
        {
            mSetBoundsOnSizeAvailable = true;
            return;
        }

        mBackground.setBounds(0, 0, w, h);
        invalidate();
    }

    @Override
    public void setBackgroundColor(int color)
    {
        setBackgroundDrawable(new ColorDrawable(color));
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        super.onSizeChanged(w, h, oldw, oldh);
        mBackgroundBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        mBackgroundCanvas = new Canvas(mBackgroundBitmap);
        mMaskBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        mMaskCanvas = new Canvas(mMaskBitmap);

        if (mSetBoundsOnSizeAvailable)
        {
            mBackground.setBounds(0, 0, w, h);
            mSetBoundsOnSizeAvailable = false;
        }
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        // Draw background
        mBackground.draw(mBackgroundCanvas);

        // Draw mask
        mMaskCanvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
        super.onDraw(mMaskCanvas);

        mBackgroundCanvas.drawBitmap(mMaskBitmap, 0.f, 0.f, mPaint);
        canvas.drawBitmap(mBackgroundBitmap, 0.f, 0.f, null);
    }
}

Example screenshot: indigo pattern for activity background, pink solid fill for TextView background.

This works both for solid color backgrounds and general drawables. Anyway, this is only a BASIC implementation, some feature such as tiling are not supported.

Gil Vegliach
  • 3,542
  • 2
  • 25
  • 37
  • That's nearly perfect, thanks. However, you are overriding `setBackgroundColor` and not using the `TextView` background. Would you see any way to reuse the `TextView` background drawable? – Marc Plano-Lesay Jun 18 '14 at 09:48
  • I tried but I didn't manage to make it work. I'm still working on it. Btw I updated the code, so you call invalidate() after you set the background color. (Otherwise a draw cycle is not guaranteed to be triggered) – Gil Vegliach Jun 18 '14 at 10:13
  • OK. I'm trying on my side too. I'll post here if I manage to get it working. Thanks again for the code anyway :-) – Marc Plano-Lesay Jun 18 '14 at 11:33
  • I can't get it working. I have either a fully transparent text-view, or a fully-colored one, and I'm unable to see the text. – Marc Plano-Lesay Jun 18 '14 at 12:34
  • What is background drawable like? If it is too small you won't see the text. Is it a color or some other drawable? The screenshot was generated with that code – Gil Vegliach Jun 18 '14 at 12:42
  • PS: note also that some XML attributes could mess up the logic. For instance I wrote some similar code for work and the attribute singleLine was misteriously setting the scrollX attribute, so that the clip was shifted and thus the text was not drawn. Test it in a basic setting – Gil Vegliach Jun 18 '14 at 12:47
  • 1
    Well, sorry, it does work. I tried to change the background color directly in the constructor to test, bad idea. I'll try to improve it, I'll share some code if I have anything better. Meanwhile, this is the only answer which gave me a working result, hence I accept it. Thanks again! – Marc Plano-Lesay Jun 18 '14 at 12:49
  • 1
    Awesome library! Thanks! – imike Nov 10 '16 at 16:28
2

I have not tried this, but you might be able to do this by (against all documentation advice) getting the TextPaint through TextView.getTextPaint() and call setXferMode(new PorterDuffXferMode(PorterDuff.Mode.MULTIPLY)), in order to clear the alpha bits on the background while rendering.

Otherwise, implement your own text view where you are in full control of the rendering.

David Burström
  • 1,592
  • 14
  • 14
  • Setting the `MULTIPLY` mode doesn't work: every character is surrounded by a black square. That's the only change with and without it. – Marc Plano-Lesay Nov 14 '13 at 10:02
0

Base on Gil Vegliach's answer, this's for kotlin that worked, hope can help s.o:

class SeeThroughTextView : AppCompatTextView {
    private var mMaskBitmap: Bitmap? = null
    private var mMaskCanvas: Canvas? = null
    private var mPaint: Paint? = null
    private var mBackground: Drawable? = null
    private var mBackgroundBitmap: Bitmap? = null
    private var mBackgroundCanvas: Canvas? = null
    private var mSetBoundsOnSizeAvailable = false

    constructor(context: Context) : super(context) {
        init()
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        init()
    }

    constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) {
        init()
    }

    private fun init() {
        mPaint = Paint()
        mPaint!!.xfermode = PorterDuffXfermode(Mode.DST_OUT)
        super.setTextColor(Color.BLACK)
        super.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
    }
    @Deprecated("Deprecated in Java")
    override fun setBackgroundDrawable(bg: Drawable) {
        mBackground = bg
        val w = bg.intrinsicWidth
        val h = bg.intrinsicHeight
        // Drawable has no dimensions, retrieve View's dimensions
        if (w == -1 || h == -1) {
            mSetBoundsOnSizeAvailable = true
            return
        }

        bg.setBounds(0, 0, w, h)
        invalidate()
    }

    override fun setBackgroundColor(color: Int) {
        setBackgroundDrawable(ColorDrawable(color))
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        mBackgroundBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
        mBackgroundCanvas = Canvas(mBackgroundBitmap!!)
        mMaskBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
        mMaskCanvas = Canvas(mMaskBitmap!!)

        if (mSetBoundsOnSizeAvailable) {
            mBackground!!.setBounds(0, 0, w, h)
            mSetBoundsOnSizeAvailable = false
        }
    }

    override fun onDraw(canvas: Canvas) {
        // Draw background
        mBackground?.draw(mBackgroundCanvas!!)
        // Draw mask
        mMaskCanvas!!.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR)
        super.onDraw(mMaskCanvas!!)

        mBackgroundCanvas!!.drawBitmap(mMaskBitmap!!, 0f, 0f, mPaint!!)
        canvas.drawBitmap(mBackgroundBitmap!!, 0f, 0f, null)
    }
}

KiluSs
  • 421
  • 4
  • 13
-2

Add this code to your textview tag:

 android:background="#07000000"
yahya.can
  • 1,790
  • 1
  • 11
  • 9