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