androidanimationporter-duffcolorfilter

Swap Android ViewGroup Child Colors in dispatchDraw


I have a ViewGroup (specifically a FrameLayout) that is supposed to have a fluid fill effect that I have animated with a path that reveals the child views underneath nicely.

The problem is that ABOVE the liquid fill line I need to show the children with the two colors of the view reversed. (Black<->White) I can't see a way to achieve this without simply iterating through the bitmap, which provides a very poor copy with lots of aliasing, and renders too slowly.

Here is a boiled down version of my current functionality in dispatchDraw:

@Override
protected void dispatchDraw(Canvas canvas)
{
    filter.setXfermode(null);
    if (subBitmap == null || mLastWidth != mWidth) {
        subBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
        subCanvas = new Canvas(subBitmap);
    }
    super.dispatchDraw(subCanvas);
    if (mLiquidPaint == null || mLiquidPath == null || waveOval.size() == 0 || bitmapPixels == null || !mLiquidAnimator.isRunning()) {
        canvas.drawBitmap(subBitmap, 0, 0, filter);
        return;
    }

    if (revealBitmap == null || mLastWidth != mWidth) {
        revealBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
        revealCanvas = new Canvas(revealBitmap);
    }
    if (mLastWidth != mWidth) {
        calculateLiquid();
        mLastWidth = mWidth;
    }
    int yOffset = mFillOffset;
    int xOffset = ellipseWidth * -1;
    PointF firstPoint = new PointF();
    PointF lastPoint = new PointF();
    PointF nextPoint = new PointF();
    Pair<Integer, Float> nextWavePoint = wavePoints.get(0);
    PointF ovalPoint = waveOval.get((nextWavePoint.first + mWaveOffset) % waveOval.size());
    lastPoint.set(xOffset + nextWavePoint.second + ovalPoint.x, yOffset + ovalPoint.y);
    mLiquidPath.moveTo(lastPoint.x, lastPoint.y);
    firstPoint.set(lastPoint);
    for (int i = 1; i < wavePoints.size(); i++) {
        nextWavePoint = wavePoints.get(i);
        ovalPoint = waveOval.get((nextWavePoint.first + mWaveOffset) % waveOval.size());
        nextPoint.set(xOffset + nextWavePoint.second + ovalPoint.x, ovalPoint.y + yOffset);
        mLiquidPath.lineTo(nextPoint.x, nextPoint.y);
        lastPoint.set(nextPoint);
    }
    mLiquidPath.lineTo(getWidth(), lastPoint.y);
    mLiquidPath.lineTo(getWidth(), getHeight());
    mLiquidPath.lineTo(0, getHeight());
    mLiquidPath.lineTo(firstPoint.x, firstPoint.y);

    //This definitely draws a nice liquid path that fills from the bottom up.
    revealCanvas.drawPath(mLiquidPath, mLiquidPaint);
    //The next two lines successfully reveal the views underneath
    filter.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
    revealCanvas.drawBitmap(subBitmap, 0, 0, filter);

    filter.setXfermode(null);
    filter.setColorFilter(null);
    canvas.drawBitmap(revealBitmap, 0, 0, filter);
}

I have tried a myriad of different ColorFilters and Transfer Modes to no avail. And the children can't leave the background transparent for me to do more simple color substitution.


Solution

  • Finally figured it out using a ColorMatrixColorFilter as outlined below:

    For two colors color1 and color2 you swap them with the following ColorMatrix:

    float[] colorSwapMatrix = new float[] {
        -1, 0, 0, 0, Color.red(color1) + Color.red(color2),    //Red
        0, -1, 0, 0, Color.green(color1) + Color.green(color2),//Green
        0, 0, -1, 0, Color.blue(color1) + Color.blue(color2),  //Blue
        0, 0, 0, 1f, 0                                         //Alpha
    }
    

    This is applied after drawing the wave path above as follows:

        //Draw the swapped image above the wave
        filter.setColorFilter(new ColorMatrixColorFilter(colorSwapMatrix));
        filter.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
        revealCanvas.drawBitmap(subBitmap, 0, 0, filter);
    
        //Draw the regular image below the wave
        filter.setColorFilter(null);
        filter.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
        revealCanvas.drawBitmap(subBitmap, 0, 0, filter);
    
        //Draw the combined image onto the canvas
        filter.setXfermode(null);
        filter.setColorFilter(null);
        canvas.drawBitmap(revealBitmap, 0, 0, filter);