I've seen many questions about transparent
background on child Views
from a custom ViewGroup
on SO, however no one seems to have this problem.
Background:
I created a custom FrameLayout
; this container has Views added dynamically. Its children should have a transparent background, but the other surface of the container must have a background color. The children Views can be drag'n'dropped
anywhere into this container.
What I do:
I override dispatchDraw()
, create a Bitmap
and a new Canvas
, then I fill the new Canvas with a white background.
I make a loop on children Views, create a new Paint
, a Rect
from child's dimensions and use PorterDuff.Mode.DST_OUT
to clear the child's area. For each child, I add the Paint and Rect to the new Canvas.
Finally, I use drawBitmap
on the main Canvas given by dispatchDraw()
, by passing the created Bitmap.
Problem:
This works well: the children have a transparent background and the rest is filled with white background. However, when I add a DragListener
to the children, the "cutting" area is not updated (while dispatchDraw
is correctly recalled): in other words, when I drag the child view, it's well dropped but the transparent area is still at the same place.
Code:
The custom FrameLayout
:
@Override
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
drawCanvas(canvas);
}
private void drawCanvas(Canvas canvas) {
// Create an off-screen bitmap and its canvas
Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas auxCanvas = new Canvas(bitmap);
// Fill the canvas with the desired outside color
auxCanvas.drawColor(Color.WHITE);
// Create a paint for each child into parent
for (int i = 0; i < getChildCount(); ++i) {
// Create a transparent area for the Rect child
View child = this.getChildAt(i);
Paint childPaint = new Paint();
childPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
Rect childRect = new Rect(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
auxCanvas.drawRect(childRect, childPaint);
}
// Draw the bitmap into the original canvas
canvas.drawBitmap(bitmap, 0, 0, null);
}
The DragListener
with ACTION_DROP
's event:
case DragEvent.ACTION_DROP:
x = event.getX();
y = event.getY();
FrameLayout frame = (FrameLayout) v;
View view = (View) event.getLocalState();
view.setX(x - (view.getWidth()/2));
view.setY(y - (view.getHeight()/2));
frame.invalidate();
break;
Screenshots:
I tried so many things regarding all the Q&A I found, but nothing seems to work.
Any help would be very appreciate.
Finally, I found the clue: the transparent Paint
isn't getting the right values for x and y axis after the update.
I guess that getLeft()
, getTop()
, getRight()
and getBottom()
don't change when a drop occurs. Weirdly, in my logs, these values seem to be updated. So instead, I tried with the updated value in DragEvent.ACTION_DROP
by using getX()
and getY()
, and it changed correctly the coordinates of the transparent area.
The solution for the children in the loop:
Paint childPaint = new Paint();
childPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
// Use the x and y axis (plus the width and height)
Rect childRect = new Rect(
(int) child.getX(),
(int) child.getY(),
(int) child.getX() + child.getWidth(),
(int) child.getY() + child.getHeight()
);
auxCanvas.drawRect(childRect, childPaint);