Question

Is it possible in android to set up a view in a way that it applies some color filter to everything below that's visible in its bounds? Like in this example:

filtering view

Just a simple rectangular view that inverts colors of everything below it. Of course when user scrolls the list it is also reflected in the inverted box. Is there some easy way to do it using color filters, PorterDuff modes, etc?

Was it helpful?

Solution

You're trying to solve this problem using a view hierarchy like this:

  • Parent
    • ListView
    • InverterView

Problem is, in this position, InverterView has no control over how ListView is drawn. But you know who does have control over how ListView is drawn? ListView's parent layout does. In other words, what you really want is a hierarchy like this:

  • Parent
    • InverterLayout
      • ListView

Now InverterLayout is responsible for drawing ListView, and can apply effects to it.

class InverterLayout extends FrameLayout
{
    // structure to hold our color filter
    private Paint paint = new Paint();
    // the color filter itself
    private ColorFilter cf;
    // the rectangle we want to invert
    private Rect inversion_rect = new Rect(100, 100, 300, 300);

    public InverterLayout(Context context)
    {
        super(context);
        // construct the inversion color matrix
        float[] mat = new float[]
        {
            -1,  0,  0, 0,  255,
             0, -1,  0, 0,  255,
             0,  0, -1, 0,  255,
             0,  0,  0, 1,  0
        };
        cf = new ColorMatrixColorFilter(new ColorMatrix(mat));
    }

    @Override protected void dispatchDraw(Canvas c)
    {
        // create a temporary bitmap to draw the child views
        Bitmap b = Bitmap.createBitmap(getWidth(), getHeight(), Config.ARGB_8888);
        Canvas cc = new Canvas(b);
        // draw them to that temporary bitmap
        super.dispatchDraw(cc);
        // copy the temporary bitmap to screen without the inversion filter
        paint.setColorFilter(null);
        c.drawBitmap(b, 0, 0, paint);
        // copy the inverted rectangle
        paint.setColorFilter(cf);
        c.drawBitmap(b, inversion_rect, inversion_rect, paint);
    }
}

When using this, ensure your child view has its own background. If the view is transparent and the window background shows through, that window background will not be inverted, because the InverterLayout has no control over how the window is drawn.

OTHER TIPS

If I could simply comment I would, because I'm not sure I'm correct here. So buyer beware, here it goes:

I think you may be able to achieve this effect by taking advantage of View.OnDragEve[]ntListener. Here's the documentation.

I think you should tweak the drag and drop trigger model used in android (ie the means of communicating between app and OS). How you tweak it will depend entirely upon the mechanism(s) within your app that forces one view into the bounds of another. Once you figure that out you can then do advanced color magic with: ColorMatrix, PorterDuffColorFilter, and several others.

This sounds a lot easier to me than the alternative Xfermode. Good luck!

Here is what I would do:

View hierarchy:

  • RelativeLayout (or FrameLayout)
    • ListView
      • CustomInverterView

InverterView gets positioned by OnTouch of the parent view (RelativeLayout) in the onTouchListener:

if (event.getAction() == MotionEvent.ACTION_MOVE)
           // Set Coordinates for the custom View.
           // Copy the bitmapCache of the ListView (jsut the portion you need
           // and send it to the customView.
           // Apply the filters you want.
           // Invalidate!

I implemented a magnifying glass using this method. It works like a charm. Maybe I can post some more code later, but I'm a bit busy at the moment.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top