Home » Android » java – Android Custom View doesn't handle transparency/alpha the right way

java – Android Custom View doesn't handle transparency/alpha the right way

Posted by: admin June 15, 2020 Leave a comment

Questions:

I’m drawing a custom view. In this view I use two different paint and path objects to paint to the canvas. I’m basically drawing two shapes that overlap. After I add alpha, the part of the view that is overlapped is darker than the rest of the image. This is undesired, but I’m not sure how to fix it.

This is a clipping of my code to show how I’m using alpha in my NewButtonView.java

Paint paint = new Paint();
int color = 0x33ffffff;
int borderColor = 0xFF000000;

paint.setColor(color);
paint.setAntiAlias(true);
paint.setStrokeWidth(strokeWidth);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStyle(Paint.Style.FILL);

About 31 minutes into this Google I/O video… they show my desired effect.

They basically show this image:
enter image description here

Add transparency and get this image: UNDESIRED RESULT

enter image description here

They end up with this: DESIRED RESULT

enter image description here

Does anyone have any idea on how to get this desired affect?

How to&Answers:

As mentioned in the video, you would use Canvas#saveLayerAlpha(....) for this. You can also get a similar effect without using it. I’ll discuss that later on.

Let’s create a sample view:

public class SampleView extends View {

    // Declare Paint objects
    Paint paintColor, paintBorder;

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

        // Initialize and set up Paint objects
        paintColor = new Paint();
        paintBorder = new Paint();

        paintColor.setAntiAlias(true);
        paintBorder.setAntiAlias(true);

        paintBorder.setColor(Color.BLACK);
        paintBorder.setStyle(Style.STROKE);
        paintBorder.setStrokeWidth(10);

        // Just a random image to 'see' the difference
        setBackground(getResources().getDrawable(R.drawable.hor_lines));
    }

    @Override 
    protected void onDraw(Canvas canvas) {

        // Save layer alpha for Rect that covers the view : alpha is 90 / 255
        canvas.saveLayerAlpha(0, 0, getWidth(), getHeight(), 90, 
                                             Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);

        // Draw first circle, and then the border
        paintColor.setColor(Color.RED);
        canvas.drawCircle(getWidth() / 3, getHeight() / 2, 
                                           getWidth() / 4 - 20, paintColor);

        canvas.drawCircle(getWidth() / 3, getHeight() / 2, 
                                           getWidth() / 4 - 15, paintBorder);

        // Draw second circle, and then the border
        paintColor.setColor(Color.BLUE);
        canvas.drawCircle(2 * getWidth() / 3, getHeight() / 2, 
                                           getWidth() / 4 - 20, paintColor);
        canvas.drawCircle(2 * getWidth() / 3, getHeight() / 2, 
                                           getWidth() / 4 - 15, paintBorder);

        // Finally, restore the canvas
        canvas.restore();
    }
}

What happens:

  1. An off-screen bitmap is allocated when saveLayerAlpha(....) is called.

  2. All drawing operations happen on this bitmap.

  3. When canvas.restore() is called, this bitmap is transferred to the on-screen canvas, and the alpha value we supplied in saveLayerAlpha(....) is applied to the off-screen bitmap.

(I think) The following is an equivalent way of creating this effect without using saveLayerAlpha(....):

public class SView extends View {

    Paint paintColor, paintBorder, paintAlpha;

    Bitmap toDrawOn;

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

        paintAlpha = new Paint();

        paintAlpha.setColor(Color.parseColor("#90FFFFFF"));
        paintAlpha.setAntiAlias(true);

        ....
        ....

    }

    @Override
    protected void onDraw(Canvas canvas) {

        if (toDrawOn == null) {

            // Create a new Bitmap
            toDrawOn = Bitmap.createBitmap(getWidth(), getHeight(), 
                                                    Config.ARGB_8888);

            // Create a new Canvas; drawing operations 
            // will happen on 'toDrawOn'
            Canvas offScreen = new Canvas(toDrawOn);

            // First circle
            paintColor.setColor(Color.RED);
            offScreenCanvas.drawCircle(getWidth() / 3, getHeight() / 2, 
                                           getWidth() / 4 - 20, paintColor);
            offScreenCanvas.drawCircle(getWidth() / 3, getHeight() / 2, 
                                           getWidth() / 4 - 15, paintBorder);

            // Second circle
            paintColor.setColor(Color.BLUE);
            offScreenCanvas.drawCircle(2 * getWidth() / 3, getHeight() / 2, 
                                           getWidth() / 4 - 20, paintColor);
            offScreenCanvas.drawCircle(2 * getWidth() / 3, getHeight() / 2, 
                                           getWidth() / 4 - 15, paintBorder);

            // Draw bitmap 'toDrawOn' to canvas using 'paintAlpha'
            canvas.drawBitmap(toDrawOn, 0, 0, paintAlpha);

        } else {

            // 'toDrawOn' is not null; draw it
            canvas.drawBitmap(toDrawOn, 0, 0, paintAlpha);
        }
    }
}

Output:

enter image description here

Just for reference, the base container in the image above is a LinearLayout with background set to this jpeg: Link.

And, the drawable used as the background of SampleView:

// Just a random image to 'see' the difference
setBackground(getResources().getDrawable(R.drawable.hor_lines));

is taken from: here.

Answer:

You could draw everything in a bitmap with full alpha, and then change the bitmap’s alpha

(Sorry, this is more a comment than an answer but stack overflow won’t allow me to post comments)