1

I'm trying to mask a FrameLayout with a mask defined as a nine patch. However, although it works fine on 5.0+ on older versions (such as 4.4.4), the patch leaves an opaque black background. Is there anything that can be done to avoid this other than drawing to an off screen bitmap before rendering to the screen or reverting to software layers?

public class MaskedLayout extends FrameLayout {

    private final static PorterDuffXfermode DST_IN = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private NinePatchDrawable mMask;

    private boolean mShowTail = true;
    private boolean mReverseLayout;

    public ChatBubbleLayout(Context context) {
        this(context, null);
    }

    public ChatBubbleLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ChatBubbleLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setWillNotDraw(false);
        setLayerType(LAYER_TYPE_HARDWARE, mPaint);

        mMask = createMask(R.drawable.mask);
    }

    private NinePatchDrawable createMask(@DrawableRes int res) {
        final Bitmap maskBitmap = BitmapFactory.decodeResource(getResources(), res);
        final NinePatch patch = new NinePatch(maskBitmap, maskBitmap.getNinePatchChunk(), "Mask");
        return new NinePatchDrawable(getResources(), patch);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        if (w != oldw || h != oldh) {
            mMask.setBounds(0, 0, w, h);
        }
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        mMask.getPaint().setXfermode(DST_IN);
        mMask.draw(canvas);
    }
}
Kingamajick
  • 2,281
  • 1
  • 16
  • 19

1 Answers1

4

try this:

public class MaskedLayout extends FrameLayout {

    private NinePatchDrawable mMask;

    public MaskedLayout(Context context) {
        this(context, null);
    }

    public MaskedLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MaskedLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mMask = (NinePatchDrawable) getResources().getDrawable(R.drawable.mask);
        mMask.getPaint().setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mMask.setBounds(0, 0, w, h);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG);
        super.dispatchDraw(canvas);
        mMask.draw(canvas);
        canvas.restore();
    }
}
pskink
  • 23,874
  • 6
  • 66
  • 77
  • and if you want any `Drawable` to "mask" your `FrameLayout` you would need another "layer" (`saveLayer()`) as there is no way to access `Drawable`s `Paint` object – pskink Jun 04 '16 at 11:34
  • Thanks, I'll give this a go shortly. Will using saveLayer have a performance cost on the view rendering? And do you know why the code works on 5.0 but not 4.x? – Kingamajick Jun 04 '16 at 12:03
  • there shouldn't be any significant impact, on pre v4 devices it was used for alpha animations for example, which were called with 20+ fps, and those devices were not speed daemons, i have no idea why it didn't work on 4.4 – pskink Jun 04 '16 at 17:39
  • Sorry for the delay, I didn't have chance to get try it over the weekend. It appears to work on 4.4.4, but now I have the strange issue that the background color of the MaskedLayout is drawn in place of the mask transparancy – Kingamajick Jun 06 '16 at 10:00