androiddrag-and-dropandroid-canvaspaintporter-duff

Drawing transparent area not updating with drag listener


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.


Solution

  • 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);