How to Give Hexagon Shape to Imageview

How to give hexagon shape to ImageView

Try this View. You might want to adjust it for your specific needs, but it draws a hexagon mask with a border on top of a view. The background resource goes below the mask.

The result:

Sample Image

The code:

HexagonMaskView.java

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Path;
import android.graphics.Region;
import android.util.AttributeSet;
import android.view.View;

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();
}
}

Update 29.07.2016

A better way to only clip the source image without painting the whole view's background. Switched to an ImageView as a base class to benefit from the scaleType. I also did some code refactoring.

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.Region;
import android.util.AttributeSet;
import android.widget.ImageView;

public class HexagonMaskView extends ImageView {
private Path hexagonPath;
private Path hexagonBorderPath;
private Paint mBorderPaint;

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() {
this.hexagonPath = new Path();
this.hexagonBorderPath = new Path();

this.mBorderPaint = new Paint();
this.mBorderPaint.setColor(Color.WHITE);
this.mBorderPaint.setStrokeCap(Paint.Cap.ROUND);
this.mBorderPaint.setStrokeWidth(50f);
this.mBorderPaint.setStyle(Paint.Style.STROKE);
}

public void setRadius(float radius) {
calculatePath(radius);
}

public void setBorderColor(int color) {
this.mBorderPaint.setColor(color);
invalidate();
}

private void calculatePath(float radius) {
float halfRadius = radius / 2f;
float triangleHeight = (float) (Math.sqrt(3.0) * halfRadius);
float centerX = getMeasuredWidth() / 2f;
float centerY = getMeasuredHeight() / 2f;

this.hexagonPath.reset();
this.hexagonPath.moveTo(centerX, centerY + radius);
this.hexagonPath.lineTo(centerX - triangleHeight, centerY + halfRadius);
this.hexagonPath.lineTo(centerX - triangleHeight, centerY - halfRadius);
this.hexagonPath.lineTo(centerX, centerY - radius);
this.hexagonPath.lineTo(centerX + triangleHeight, centerY - halfRadius);
this.hexagonPath.lineTo(centerX + triangleHeight, centerY + halfRadius);
this.hexagonPath.close();

float radiusBorder = radius - 5f;
float halfRadiusBorder = radiusBorder / 2f;
float triangleBorderHeight = (float) (Math.sqrt(3.0) * halfRadiusBorder);

this.hexagonBorderPath.reset();
this.hexagonBorderPath.moveTo(centerX, centerY + radiusBorder);
this.hexagonBorderPath.lineTo(centerX - triangleBorderHeight, centerY + halfRadiusBorder);
this.hexagonBorderPath.lineTo(centerX - triangleBorderHeight, centerY - halfRadiusBorder);
this.hexagonBorderPath.lineTo(centerX, centerY - radiusBorder);
this.hexagonBorderPath.lineTo(centerX + triangleBorderHeight, centerY - halfRadiusBorder);
this.hexagonBorderPath.lineTo(centerX + triangleBorderHeight, centerY + halfRadiusBorder);
this.hexagonBorderPath.close();
invalidate();
}

@Override
public void onDraw(Canvas c) {
c.drawPath(hexagonBorderPath, mBorderPaint);
c.clipPath(hexagonPath, Region.Op.INTERSECT);
c.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
super.onDraw(c);
}

@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(width, height);
calculatePath(Math.min(width / 2f, height / 2f) - 10f);
}
}

Example layout:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:background="@android:color/holo_green_dark">

<com.scelus.hexagonmaskimproved.HexagonMaskView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/bear"
android:background="@android:color/holo_green_light"/>

</RelativeLayout>

New result

how to adjust the borders for hexagon shape to imageview

Check this

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/gradient"
>

<ImageView
android:id="@+id/imageView3"
android:layout_width="100dp"
android:layout_height="100dp"
android:rotation="90"
android:layout_centerHorizontal="true"
android:layout_marginTop="50dp"
app:srcCompat="@drawable/hexagon" />

</RelativeLayout>

Modified approach from this Answer

<vector android:height="24dp" android:viewportHeight="628.0"
android:viewportWidth="726.0" android:width="27dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFA3B3"
android:pathData="m723,314c-60,103.9 -120,207.8 -180,311.8 -120,0 -240,0 -360,0C123,521.8 63,417.9 3,314 63,210.1 123,106.2 183,2.2c120,0 240,0 360,0C603,106.2 663,210.1 723,314Z"
android:strokeColor="#FFA3B3" android:strokeWidth="4"/>

Make sure add this line vectorDrawables.useSupportLibrary = true in gradle file..

Output

Sample Image

EDITED

 <md.com.androidui.HexagonMaskView
android:id="@+id/imageView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:layout_centerHorizontal="true"
android:src="@drawable/photo_"
android:background="@android:color/transparent"/>

Changes in HexagonMaskView

  @Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(400, 450); //Change size according to your needs here or in xml
calculatePath(Math.min(width / 2f, height / 2f) - 10f);
}

OR

 <md.com.androidui.HexagonMaskView
android:id="@+id/imageView3"
android:layout_width="150dp"
android:layout_height="170dp"
android:layout_marginTop="50dp"
android:layout_centerHorizontal="true"
android:src="@drawable/photo_"
android:background="@android:color/transparent"/>

OUTPUT

Sample Image

Android Hexagon ImageView

I at last solved my problem. I find a very useful library that specifically does the trick I wanted. It is masking the imageview with a svg type vector image.

Library:

CustomShapeImageView

Result:

Screenshot

Edit: I also want to share the hexagonal svg with you, in case of necessity.

<svg width="205" height="237" xmlns="http://www.w3.org/2000/svg">
<title>hexagon</title>
<metadata id="metadata3064">image/svg+xml</metadata>
<g>
<title>Layer 1</title>
<polygon points="0,59.27092742919922 0,177.8127899169922 102.66026306152344,237.08370971679688 205.3205108642578,177.8127899169922 205.3205108642578,59.27092742919922 102.66026306152344,0 " id="svg_1" fill="#000000"/>
</g>
</svg>

How to create Hexagon shape in .xml format

The best solution for you would be use VectorDrawable:

Hexagon shape as vector drawable:

<vector android:height="24dp" android:viewportHeight="628.0"
android:viewportWidth="726.0" android:width="27dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#00ffffff"
android:pathData="m723,314c-60,103.9 -120,207.8 -180,311.8 -120,0 -240,0 -360,0C123,521.8 63,417.9 3,314 63,210.1 123,106.2 183,2.2c120,0 240,0 360,0C603,106.2 663,210.1 723,314Z"
android:strokeColor="#000000" android:strokeWidth="4"/>
</vector>

Update(28:07.2016):

To support API below Lollipop use support library http://android-developers.blogspot.com/2016/02/android-support-library-232.html remember to use VectorDrawableCompat instead VectorDrawable

Create Hexagon ImageView shape in iOS

I refactored the path creation function in Swift to also take a rotationOffset argument, allowing to arbitrarily rotate the regular polygon.

I am not completely sure my function is equivalent to the one you have (as I use polar coordinates to draw the polygon), but the produced result looks similar to what you want.

public func roundedPolygonPath(rect: CGRect, lineWidth: CGFloat, sides: NSInteger, cornerRadius: CGFloat, rotationOffset: CGFloat = 0) -> UIBezierPath {
let path = UIBezierPath()
let theta: CGFloat = CGFloat(2.0 * M_PI) / CGFloat(sides) // How much to turn at every corner
let offset: CGFloat = cornerRadius * tan(theta / 2.0) // Offset from which to start rounding corners
let width = min(rect.size.width, rect.size.height) // Width of the square

let center = CGPoint(x: rect.origin.x + width / 2.0, y: rect.origin.y + width / 2.0)

// Radius of the circle that encircles the polygon
// Notice that the radius is adjusted for the corners, that way the largest outer
// dimension of the resulting shape is always exactly the width - linewidth
let radius = (width - lineWidth + cornerRadius - (cos(theta) * cornerRadius)) / 2.0

// Start drawing at a point, which by default is at the right hand edge
// but can be offset
var angle = CGFloat(rotationOffset)

let corner = CGPointMake(center.x + (radius - cornerRadius) * cos(angle), center.y + (radius - cornerRadius) * sin(angle))
path.moveToPoint(CGPointMake(corner.x + cornerRadius * cos(angle + theta), corner.y + cornerRadius * sin(angle + theta)))

for _ in 0..<sides {
angle += theta

let corner = CGPointMake(center.x + (radius - cornerRadius) * cos(angle), center.y + (radius - cornerRadius) * sin(angle))
let tip = CGPointMake(center.x + radius * cos(angle), center.y + radius * sin(angle))
let start = CGPointMake(corner.x + cornerRadius * cos(angle - theta), corner.y + cornerRadius * sin(angle - theta))
let end = CGPointMake(corner.x + cornerRadius * cos(angle + theta), corner.y + cornerRadius * sin(angle + theta))

path.addLineToPoint(start)
path.addQuadCurveToPoint(end, controlPoint: tip)
}

path.closePath()

// Move the path to the correct origins
let bounds = path.bounds
let transform = CGAffineTransformMakeTranslation(-bounds.origin.x + rect.origin.x + lineWidth / 2.0, -bounds.origin.y + rect.origin.y + lineWidth / 2.0)
path.applyTransform(transform)

return path
}

For example, with rotationOffset set to M_PI / 6.0, the produced shape will look something like this

Resulting polygon with M_PI / 6.0 rotation

Just in case, you can see the full playground I used here

UPDATE (March 14 2018):
Updated the gist for Swift 4 syntax, can be seen here.



Related Topics



Leave a reply



Submit