Reputation: 2490
Ok, I need to crop an ImageView in a particular shape, and I can't do this by adding over a png, because the background can be variable (ex. a pattern). So, I need that the area outside the shape is transparent.
The shape must be this:
I thought to use Path() to draw this shape and use it to mask the ImageView, but I have absolutely no idea how to draw a complex shape like this with Path().
Many thanks.
Upvotes: 1
Views: 936
Reputation: 134714
So I was bored and this looked like fun, so I've thrown together a simple Drawable you can use to do this. You could get fancier and add strokes and whatnot to it, but this works for the basic case you've suggested, and allows you to set the arrow to be pointing to any of the corners, and will also scale your image to fit the bounds of the Drawable. Here's the result:
You can use it by:
BubbleDrawable bubbleDrawable = new BubbleDrawable(
this, R.drawable.your_image, BubbleDrawable.Corner.TOP_RIGHT);
myImageView.setImageDrawable(bubbleDrawable);
And here's the code for BubbleDrawable:
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import static android.graphics.Matrix.ScaleToFit.FILL;
import static android.graphics.Paint.ANTI_ALIAS_FLAG;
import static android.graphics.Path.Direction.CW;
import static android.graphics.PixelFormat.TRANSPARENT;
import static android.graphics.Shader.TileMode.CLAMP;
import static test.com.testrotationanimation.BubbleDrawable.Corner.TOP_LEFT;
public final class BubbleDrawable extends Drawable {
private final Matrix mMatrix = new Matrix();
private final Paint mPaint = new Paint(ANTI_ALIAS_FLAG);
private final Path mPath = new Path();
private final RectF mSrcRect = new RectF();
private final RectF mDstRect = new RectF();
private final Shader mShader;
private Corner mArrowCorner = TOP_LEFT;
public BubbleDrawable(Bitmap bitmap, Corner arrowCorner) {
// Initialize a BitmapShader with the image you wish to draw
// (you can use other TileModes like REPEAT or MIRROR if you prefer)
mShader = new BitmapShader(bitmap, CLAMP, CLAMP);
mPaint.setShader(mShader);
// Save the bounds of the bitmap as the src rectangle -- will
// be used later to update the matrix when the bounds change
// so that the image fits within the bounds of this drawable
mSrcRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
// Set the corner in which the arrow will be drawn
mArrowCorner = arrowCorner;
}
public BubbleDrawable(Context ctx, int drawableResource, Corner arrowCorner) {
this(BitmapFactory.decodeResource(ctx.getResources(), drawableResource), arrowCorner);
}
public Corner getArrowCorner() {
return mArrowCorner;
}
public void setArrowCorner(Corner corner) {
mArrowCorner = corner;
updatePath();
invalidateSelf();
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
updateMatrix(bounds);
updatePath();
}
private void updateMatrix(Rect bounds) {
// Set the destination rectangle for the bitmap to be the
// new drawable bounds
mDstRect.set(bounds);
// Scale the bitmap's rectangle to the bounds of this drawable
mMatrix.setRectToRect(mSrcRect, mDstRect, FILL);
// Update the shader's matrix (to draw the bitmap at the right size)
mShader.setLocalMatrix(mMatrix);
}
private void updatePath() {
final Rect bounds = getBounds();
final float x = bounds.exactCenterX();
final float y = bounds.exactCenterY();
// Draw the initial circle (same for all corners)
mPath.reset();
mPath.addCircle(x, y, Math.min(x, y), CW);
// Add the rectangle which intersects with the center,
// based on the corner in which the arrow should draw
switch (mArrowCorner) {
case TOP_LEFT:
mPath.addRect(bounds.left, bounds.top, x, y, CW);
break;
case TOP_RIGHT:
mPath.addRect(x, bounds.top, bounds.right, y, CW);
break;
case BOTTOM_LEFT:
mPath.addRect(bounds.left, y, x, bounds.bottom, CW);
break;
case BOTTOM_RIGHT:
mPath.addRect(x, y, bounds.right, bounds.bottom, CW);
break;
}
}
@Override
public void draw(Canvas canvas) {
// Easy enough, just draw the path using the paint.
// It already has the BitmapShader applied which
// will do the work for you.
canvas.drawPath(mPath, mPaint);
}
@Override
public int getOpacity() {
// Indicate that this Drawable has fully-transparent pixel values
return TRANSPARENT;
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
// Yay, you can even support color filters for your drawable
mPaint.setColorFilter(colorFilter);
}
@Override
public void setAlpha(int i) {
// You could do this by doing some canvas magic but I'm
// lazy and don't feel like it. Exercise for the reader. :)
throw new UnsupportedOperationException("Not implemented.");
}
public enum Corner {
TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT
}
}
Upvotes: 6