copolii
copolii

Reputation: 14506

ImageView clipped to Polygon

I want to create an ImageView that clips its content to inside a polygon (in this case a hexagon). I'm setting the view's layer type to software so I can use canvas.clipPath():

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    setLayerType (LAYER_TYPE_SOFTWARE, null);
}

The code that generates the hexagon seems to work fine:

Without clipping

Here's the code that calculates the vertices:

@Override public void calculateVertices () {
    width = 2f * radius;
    side = 1.5f * radius;
    height = (float) Math.sqrt (3f) * radius;

    vertices = new Point[6];

    int minX = Integer.MAX_VALUE;
    int minY = Integer.MAX_VALUE;
    int maxX = Integer.MIN_VALUE;
    int maxY = Integer.MIN_VALUE;

    final float[] p0 = new float[] {center.x - radius, center.y};

    final Matrix m = new Matrix ();
    final float r = getRotation ();

    for (int i = 0; i < vertices.length; i++) {
        final float ptRot = rotateBy (r, (float) i * 60f);
        final float[] point = new float[2];

        if (ptRot != 0f) {
            m.reset ();
            m.postRotate (ptRot, center.x, center.y);
            m.mapPoints (point, p0);
        } else {
            point[0] = p0[0];
            point[1] = p0[1];
        }

        if (point[0] < minX) {
            minX = Math.round (point[0]);
        } else if (point[0] > maxX) {
            maxX = Math.round (point[0]);
        }

        if (point[1] < minY) {
            minY = Math.round (point[1]);
        } else if (point[1] > maxY) {
            maxY = Math.round (point[1]);
        }

        vertices[i] = fromFloat (point);
    }

    path.reset ();
    clipPath.reset ();

    path.moveTo (vertices[0].x, vertices[0].y);
    clipPath.moveTo (vertices[0].x, vertices[0].y);

    for (int i = 1; i < vertices.length; i++) {
        path.lineTo (vertices[i].x, vertices[i].y);
        clipPath.lineTo (vertices[i].x, vertices[i].y);
    }

    path.lineTo (vertices[0].x, vertices[0].y);
    clipPath.lineTo (vertices[0].x, vertices[0].y);
    path.close ();
    clipPath.close ();
    enclosure.set (minX, minY, maxX, maxY);
}

As you can see above, the same method generates the bounding rectangle as well as the path that defines the polygon and the clipping path of the polygon.

In the constructor of this view, path and clipPath are defined as such

path = new Path ();
clipPath = new Path ();
clipPath.setFillType (Path.FillType.INVERSE_EVEN_ODD);

The onDraw method of the view is overridden as such:

@Override protected void onDraw (final Canvas canvas) {
    final int count = canvas.save ();
    canvas.clipPath (hexagon.getClipPath ());
    super.onDraw (canvas);
    hexagon.draw (canvas, selectBackgroundPaint ());
    canvas.restoreToCount (count);
}

As soon as I enable the line with canvas.clipPath (hexagon.getClipPath ()); The view displays as such:

Clipping enabled

2 of the 4 clipping points aren't even on my path!!

What am I doing wrong here? Is there a better way of doing this?

Ultimately I want everything outside of the polygon to be only transparent. Always. Including the selection highlighting.

I appreciate the help. I can't publish too much of the code (Company IP, etc), but if you need more details let me know and I will update.

Upvotes: 1

Views: 2200

Answers (2)

MilapTank
MilapTank

Reputation: 10076

i have answer one question exactly you want. @SceLus has also answer which is accepted is so good and and bounty winner so you can get help from it as link may change so i am copy paste that code

HexagonMaskView.java

public class HexagonMaskView extends View {
private Path hexagonPath;
private Path hexagonBorderPath;
private float radius;
private float width, height;
private int maskColor;

public HexagonMaskView(Context context) {
    super(context);
    init();
}

public HexagonMaskView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

public HexagonMaskView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
}

private void init() {
    hexagonPath = new Path();
    hexagonBorderPath = new Path();
    maskColor = 0xFF01FF77;
}

public void setRadius(float r) {
    this.radius = r;
    calculatePath();
}

public void setMaskColor(int color) {
    this.maskColor = color;
    invalidate();
}

private void calculatePath() {
    float triangleHeight = (float) (Math.sqrt(3) * radius / 2);
    float centerX = width / 2;
    float centerY = height / 2;
    hexagonPath.moveTo(centerX, centerY + radius);
    hexagonPath.lineTo(centerX - triangleHeight, centerY + radius / 2);
    hexagonPath.lineTo(centerX - triangleHeight, centerY - radius / 2);
    hexagonPath.lineTo(centerX, centerY - radius);
    hexagonPath.lineTo(centerX + triangleHeight, centerY - radius / 2);
    hexagonPath.lineTo(centerX + triangleHeight, centerY + radius / 2);
    hexagonPath.moveTo(centerX, centerY + radius);

    float radiusBorder = radius - 5;
    float triangleBorderHeight = (float) (Math.sqrt(3) * radiusBorder / 2);
    hexagonBorderPath.moveTo(centerX, centerY + radiusBorder);
    hexagonBorderPath.lineTo(centerX - triangleBorderHeight, centerY
            + radiusBorder / 2);
    hexagonBorderPath.lineTo(centerX - triangleBorderHeight, centerY
            - radiusBorder / 2);
    hexagonBorderPath.lineTo(centerX, centerY - radiusBorder);
    hexagonBorderPath.lineTo(centerX + triangleBorderHeight, centerY
            - radiusBorder / 2);
    hexagonBorderPath.lineTo(centerX + triangleBorderHeight, centerY
            + radiusBorder / 2);
    hexagonBorderPath.moveTo(centerX, centerY + radiusBorder);
    invalidate();
}

@Override
public void onDraw(Canvas c) {
    super.onDraw(c);
    c.clipPath(hexagonBorderPath, Region.Op.DIFFERENCE);
    c.drawColor(Color.WHITE);
    c.save();
    c.clipPath(hexagonPath, Region.Op.DIFFERENCE);
    c.drawColor(maskColor);
    c.save();
}

// getting the view size and default radius
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    width = MeasureSpec.getSize(widthMeasureSpec);
    height = MeasureSpec.getSize(heightMeasureSpec);
    radius = height / 2 - 10;
    calculatePath();
}
}

enter image description here

Upvotes: 1

Harsh Patel
Harsh Patel

Reputation: 1086

Dude here is the solution..

https://github.com/MostafaGazar/CustomShapeImageView For more detail https://coderwall.com/p/hmzf4w

Use this library u will find the circular shape in this library and also easy use.

U can also make your custom shape to this library like cloud etc. this library supports SVG file format and they made the SVG file of Rounded corner Imageview.

Just use and let me know if there is any problem

Upvotes: 0

Related Questions