Android 自定義View 繪制六邊形設(shè)置按鈕

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

酷安截圖.png

最終效果

Android 自定義View 繪制六邊形設(shè)置按鈕.png

原理

  1. 中心畫(huà)一個(gè)小圓
  2. 再畫(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" />
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容