今天逛酷安的時(shí)候,發(fā)現(xiàn)酷安的設(shè)置按鈕(截圖的右上角),是一個(gè)六邊形 + 中心圓的圖標(biāo),所以又是一個(gè)自定義View練習(xí)對(duì)象了。畫(huà)圓很簡(jiǎn)單,知道半徑即可,而重點(diǎn)就在畫(huà)出六邊形。

酷安截圖.png
最終效果

Android 自定義View 繪制六邊形設(shè)置按鈕.png
原理
- 中心畫(huà)一個(gè)小圓
- 再畫(huà)一個(gè)六邊形(難點(diǎn))
怎樣才能畫(huà)六邊形,?只要我們計(jì)算出6個(gè)角的坐標(biāo)點(diǎn),用Path連接即可。至于怎么計(jì)算,就要使用三角函數(shù)。我們這樣分析:
在六邊形中心畫(huà)一個(gè)內(nèi)切圓,6個(gè)角都在圓上。360被六邊形的6個(gè)角平分,每個(gè)角就為60度,而每個(gè)角的2條腰的長(zhǎng)都是圓的半徑。如下圖所示:

中心角.png
至于三角函數(shù),可能我們會(huì)忘記,我們對(duì)著這張圖解圖來(lái)回憶一下。

三角函數(shù)圖解.jpg
只要我們計(jì)算出中心角的角度,用三角函數(shù)中的cos()就可以計(jì)算出點(diǎn)的x坐標(biāo),sin()就可以計(jì)算出y坐標(biāo)(注意這個(gè)2個(gè)方法傳入的角度需要轉(zhuǎn)為弧度)。重點(diǎn)計(jì)算代碼在drawPolygon()方法。
完整代碼
- 自定義屬性
<declare-styleable name="PolygonSettingView">
<!-- 開(kāi)始顏色 -->
<attr name="psv_color" format="color|reference" />
<!-- 多邊形多少條邊,至少3條邊,如果小于3條,會(huì)強(qiáng)制3條 -->
<attr name="psv_num" format="integer" />
<!-- 邊的線(xiàn)寬 -->
<attr name="psv_line_width" format="float|dimension|reference" />
</declare-styleable>
- Java代碼
public class PolygonSettingView extends View {
/**
* View默認(rèn)最小寬度
*/
private static final int DEFAULT_MIN_WIDTH = 100;
/**
* 畫(huà)筆
*/
private Paint mPaint;
/**
* 控件寬
*/
private int mViewWidth;
/**
* 控件高
*/
private int mViewHeight;
/**
* 多邊形的邊數(shù)
*/
private int mNum;
/**
* 最小的多邊形的半徑
*/
private float mRadius;
/**
* 360度對(duì)應(yīng)的弧度(為什么2π就是360度?弧度的定義:弧長(zhǎng) / 半徑,一個(gè)圓的周長(zhǎng)是2πr,如果是一個(gè)360度的圓,它的弧長(zhǎng)就是2πr,如果這個(gè)圓的半徑r長(zhǎng)度為1,那么它的弧度就是,2πr / r = 2π)
*/
private final double mPiDouble = 2 * Math.PI;
/**
* 多邊形中心角的角度(每個(gè)多邊形的內(nèi)角和為360度,一個(gè)多邊形2個(gè)相鄰角頂點(diǎn)和中心的連線(xiàn)所組成的角為中心角
* 中心角的角度都是一樣的,所以360度除以多邊形的邊數(shù),就是一個(gè)中心角的角度),這里注意,因?yàn)楹罄m(xù)要用到Math類(lèi)的三角函數(shù)
* Math類(lèi)的sin和cos需要傳入的角度值是弧度制,所以這里的中心角的角度,也是弧度制的弧度
*/
private float mCenterAngle;
/**
* 顏色
*/
private int mColor;
/**
* 中心小圓的半徑
*/
private float mSmallCircleRadius;
/**
* 線(xiàn)寬
*/
private float mLineWidth;
public PolygonSettingView(Context context) {
this(context, null);
}
public PolygonSettingView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public PolygonSettingView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
initAttr(context, attrs, defStyleAttr);
//取消硬件加速
setLayerType(LAYER_TYPE_SOFTWARE, null);
//畫(huà)筆
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(mColor);
mPaint.setStrokeWidth(mLineWidth);
}
private void initAttr(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
//默認(rèn)邊數(shù)和最小邊數(shù)
int defaultNum = 6;
int minNum = 3;
int defaultColor = Color.argb(255, 0, 0, 0);
int defaultLineWidth = dip2px(context, 1.5f);
if (attrs != null) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PolygonSettingView, defStyleAttr, 0);
mColor = array.getColor(R.styleable.PolygonSettingView_psv_color, defaultColor);
int num = array.getInt(R.styleable.PolygonSettingView_psv_num, defaultNum);
mNum = num <= minNum ? minNum : num;
mLineWidth = array.getDimension(R.styleable.PolygonSettingView_psv_line_width, defaultLineWidth);
array.recycle();
} else {
mColor = defaultColor;
mNum = defaultNum;
}
//計(jì)算中心角弧度
mCenterAngle = (float) (mPiDouble / mNum);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
//計(jì)算最小的多邊形的半徑
mRadius = (Math.min(mViewWidth, mViewHeight) / 2f) * 0.95f;
//計(jì)算中心小圓的半徑
mSmallCircleRadius = (Math.min(mViewWidth, mViewHeight) / 2f) * 0.3f;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//將畫(huà)布中心移動(dòng)到中心點(diǎn)
canvas.translate(mViewWidth / 2, mViewHeight / 2);
//畫(huà)小圓
drawSmallCircle(canvas);
//畫(huà)多邊形
drawPolygon(canvas);
}
/**
* 畫(huà)小圓
*/
private void drawSmallCircle(Canvas canvas) {
canvas.drawCircle(0, 0, mSmallCircleRadius, mPaint);
}
/**
* 畫(huà)多邊形
*/
private void drawPolygon(Canvas canvas) {
//多邊形邊角頂點(diǎn)的x坐標(biāo)
float pointX;
//多邊形邊角頂點(diǎn)的y坐標(biāo)
float pointY;
//總的圓的半徑,就是全部多邊形的半徑之和
Path path = new Path();
//畫(huà)前先重置路徑
path.reset();
for (int i = 1; i <= mNum; i++) {
//cos三角函數(shù),中心角的鄰邊 / 斜邊,斜邊的值剛好就是半徑,cos值乘以斜邊,就能求出鄰邊,而這個(gè)鄰邊的長(zhǎng)度,就是點(diǎn)的x坐標(biāo)
pointX = (float) (Math.cos(i * mCenterAngle) * mRadius);
//sin三角函數(shù),中心角的對(duì)邊 / 斜邊,斜邊的值剛好就是半徑,sin值乘以斜邊,就能求出對(duì)邊,而這個(gè)對(duì)邊的長(zhǎng)度,就是點(diǎn)的y坐標(biāo)
pointY = (float) (Math.sin(i * mCenterAngle) * mRadius);
//如果是一個(gè)點(diǎn),則移動(dòng)到這個(gè)點(diǎn),作為起點(diǎn)
if (i == 1) {
path.moveTo(pointX, pointY);
} else {
//其他的點(diǎn),就可以連線(xiàn)了
path.lineTo(pointX, pointY);
}
}
path.close();
canvas.drawPath(path, mPaint);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(handleMeasure(widthMeasureSpec), handleMeasure(heightMeasureSpec));
}
/**
* 處理MeasureSpec
*/
private int handleMeasure(int measureSpec) {
int result = DEFAULT_MIN_WIDTH;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
//處理wrap_content的情況
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
public static int dip2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
}
- 簡(jiǎn)單使用
<com.zh.cavas.sample.widget.PolygonSettingView
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_margin="10dp"
app:psv_color="@android:color/black"
app:psv_line_width="1.5dp"
app:psv_num="6" />