自定義view之半圓環比例圖(邏輯簡潔,註釋詳細)

啥也不說先上圖,線看效果,代碼複製可用

第一:在values下創建 attrs.xml,寫需要的自定義屬性;
第二:在 自定義View第三個構造方法中,獲取自定義屬性;
第三:重寫onMeasure【不是必須的】;
第四:重寫onDraw【所有繪製代碼都寫在onDraw】;

一,定義線條寬度,間隔背景顏色,間隔角度,兩個textview的屬性等

<declare-styleable name="RoundRateView">
    <attr name="rrv_circleWidth" format="dimension" />
    <attr name="rrv_intervalColor" format="color" />
    <attr name="rrv_aboveTextColor" format="color" />
    <attr name="rrv_belowTextColor" format="color" />
    <attr name="rrv_intervalAngle" format="dimension" />
    <attr name="rrv_belowTextSize" format="dimension" />
    <attr name="rrv_aboveTextSize" format="dimension" />
    <attr name="rrv_isShowText" format="boolean" />
</declare-styleable>

二,如圖,具體看下面代碼

三,如圖,具體看代碼

四,如圖,具體看代碼

五,是view代碼:

public class RoundRateView extends View {
    private int mCircleWidth; //圓環寬度
    private float intervalAngle = 0.5f;//間隔角度
    private int belowTextSize = 60;//下面的文字字體大小
    private int aboveTextSize = 30;//上面的文字字體大小
    private int aboveTextColor = Color.GRAY;//上面的文字 默認灰色
    private int belowTextColor = Color.BLACK;//下面的文字 默認黑色
    private int intervalColor = Color.GRAY;//間隔顏色 默認灰色
    private int noMoneyColor = Color.DKGRAY;//總金額爲0 默認灰色
    private boolean isShowText = true; //是否顯示中間文字 默認顯示
    private Paint mPaint;//圓環畫筆
    private Paint textPaint;//文字畫筆
    private List<Float> angleList = new ArrayList<>(); //所有的角度 集合
    private List<Integer> colorList = new ArrayList<>(); //所有的色值 集合
    private RectF oval;//圓環所在區域
    private double allMoney;//總金額

    private int colors[] = {Color.parseColor("#41A8FF")
            , Color.parseColor("#86C8FF")
            , Color.parseColor("#FF8B13")
            , Color.parseColor("#FFB971")
            , Color.parseColor("#FF8A77")
            , Color.parseColor("#EEE685")
            , Color.parseColor("#EECBAD")
            , Color.parseColor("#EEAEEE")
            , Color.parseColor("#EE3B3B")
            , Color.parseColor("#EDEDED")};

    public RoundRateView(Context context) {
        super(context);
        init(context, null, 0);
    }

    public RoundRateView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs, 0);
    }

    public RoundRateView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr);
    }

    private void init(Context context, AttributeSet attrs, int defStyleAttr) {
        TypedArray array = context.getTheme().obtainStyledAttributes(attrs,R.styleable.RoundRateView,defStyleAttr,0);
        int n = array.getIndexCount();
        for(int i=0;i<n;i++){
            int attr  = array.getIndex(i);
            switch (attr){//這些屬性都在xml裏面設置。此處是默認
                case R.styleable.RoundRateView_rrv_circleWidth:
                    mCircleWidth = array.getDimensionPixelSize(attr, (int) dip2px(15));
                    if(mCircleWidth<2){//最小2
                        mCircleWidth = 2;
                    }
                    break;
                case R.styleable.RoundRateView_rrv_intervalAngle:
                    intervalAngle = array.getDimensionPixelSize(attr, (int) dip2px(1));
                    break;
                case R.styleable.RoundRateView_rrv_belowTextSize:
                    belowTextSize = array.getDimensionPixelSize(attr, (int) dip2px(60));
                    break;
                case R.styleable.RoundRateView_rrv_aboveTextSize:
                    aboveTextSize = array.getDimensionPixelSize(attr, (int) dip2px(30));
                    break;
                case R.styleable.RoundRateView_rrv_aboveTextColor:
                    aboveTextColor = array.getColor(attr, Color.GRAY);
                    break;
                case R.styleable.RoundRateView_rrv_belowTextColor:
                    belowTextColor = array.getColor(attr,Color.BLACK);
                    break;
                case R.styleable.RoundRateView_rrv_intervalColor:
                    intervalColor = array.getColor(attr,Color.GRAY);
                    break;
                case R.styleable.RoundRateView_rrv_isShowText:
                    isShowText = array.getBoolean(attr,true);
                    break;
            }
        }
        array.recycle();//定義後屬性對象回收

        mPaint = new Paint();//初始化圓環畫筆
        mPaint.setAntiAlias(true);//抗鋸齒
        mPaint.setStyle(Paint.Style.STROKE); //設置繪畫空心(比如畫圓時畫的是空心圓而不是實心圓)
        mPaint.setStrokeWidth(mCircleWidth);//設置畫筆線寬

        textPaint = new Paint();//文字畫筆
        textPaint.setAntiAlias(true);
        textPaint.setDither(true);//防抖動


    }

    /**
     * 設置數據
     * @param list
     */
    public void setList(List<Double> list) {
        float allIntervalAngle = 0f ;//所有間隔加起來的角度
        float allModuleAngle;  //所有模塊加起來的角度  allModuleAngle + allIntervalAngle=180;
        if(list.size()>colors.length){//不能大於定義的顏色數組長度
            return;
        }
        angleList.clear();
        colorList.clear();
        allMoney = 0d;//總資產
        for(int i=0;i<list.size();i++){
            allMoney = allMoney+list.get(i);//資產累加
        }
        if(list.size() == 1){//只有一條數據//如果只有一條數據,就不要間隔
            angleList.add(180f);//半圓
            colorList.add(colors[0]);
        }else{
            for(int i=0;i<list.size()-1;i++){
                if(list.get(i)!=0){//數據不爲0
                    allIntervalAngle+= intervalAngle; //間隔角度累加
                }
            }
            if(allIntervalAngle == intervalAngle){ //如果只有一條數據不爲0,就不要間隔顏色
                angleList.add(180f);
                colorList.add(colors[0]);
            }else{
                allModuleAngle = 180- allIntervalAngle;//所有的顏色模塊角度=180-所有間隔角度
                float angle = 0;//每個金額所佔角度累加
                for(int i=0;i<list.size();i++){
                    if(list.get(i)!=0){
                        float e = (float) (list.get(i)/allMoney * allModuleAngle);//每個金額所佔角度
                        if(i == list.size()-1){//如果是最後一個色塊,所佔角度就是剩餘全部的角度
                            this.angleList.add(allModuleAngle - angle);
                        }else{
                            angle+=e;
                            this.angleList.add(e);
                            this.angleList.add(intervalAngle);
                        }
                        this.colorList.add(colors[i]);
                        this.colorList.add(intervalColor);

                    }
                }
            }

        }
        invalidate();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthPixels = this.getResources().getDisplayMetrics().widthPixels;//獲取屏幕寬
        int heightPixels = this.getResources().getDisplayMetrics().heightPixels;//獲取屏幕高
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int hedight = MeasureSpec.getSize(heightMeasureSpec);
        int minWidth = Math.min(widthPixels, width);//控件寬高不超過屏幕寬高
        int minHedight = Math.min(heightPixels, hedight);//控件寬高不超過屏幕寬高
        setMeasuredDimension(Math.min(minWidth, minHedight), Math.min(minWidth, minHedight));//設置寬高中的最小數爲控件的實際寬高
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawCircle(canvas);
        if(isShowText){//是否顯示文本
            drawText(canvas);
        }
    }

    private void drawCircle(Canvas canvas) {
        if(oval==null){
            int min = Math.min(getWidth() - mCircleWidth/2,getHeight() - mCircleWidth/2);
            oval = new RectF(mCircleWidth/2,mCircleWidth/2,min,min);
        }
        float startAngle = -180f;//經過試驗,-180這個值就是9點方向的位置 -90這個值就是12點方向的位置


        if(allMoney==0){
            mPaint.setColor(noMoneyColor);//總金額爲0,給默認顏色
            mPaint.setStrokeWidth(mCircleWidth);
            mPaint.setStrokeCap(Paint.Cap.ROUND);//首位有弧度
            canvas.drawArc(oval, -180, 180, false, mPaint);//sweepAngle 跨度180畫半圓  360圓
            return;
        }else{
            //畫一個底層圓環,顏色和間隙顏色一樣,因爲間隙的色塊和其他色塊之間會有小縫隙
            mPaint.setColor(intervalColor);
            mPaint.setStrokeWidth(mCircleWidth - 1); //寬度減1是防止底色溢出
            mPaint.setStrokeCap(Paint.Cap.BUTT);
            canvas.drawArc(oval, -180, 180, false, mPaint);
            //左邊開始的圓弧(沒有設置線條開頭圓弧,結束矩形的api),所以首位畫兩個,沒找到其他好方法,有大神知道嗎請留言回覆
            mPaint.setColor(colorList.get(0));
            mPaint.setStrokeWidth(mCircleWidth);
            mPaint.setStrokeCap(Paint.Cap.ROUND);
            canvas.drawArc(oval, -180, 1, false, mPaint);
            //右邊結束的圓弧
            mPaint.setColor(colorList.get(angleList.size()-1));
            mPaint.setStrokeWidth(mCircleWidth);
            mPaint.setStrokeCap(Paint.Cap.ROUND);
            canvas.drawArc(oval, 0, 1, false, mPaint);
        }

        mPaint.setStrokeWidth(mCircleWidth);
        for(int i=0;i<angleList.size();i++){
            mPaint.setColor(colorList.get(i));
            mPaint.setStrokeCap(Paint.Cap.BUTT);
//            mPaint.setStrokeCap(Paint.Cap.ROUND);
//            if(i==0 || i==angleList.size()-1){
//                mPaint.setStrokeCap(Paint.Cap.ROUND);
//            }else{
//                mPaint.setStrokeCap(Paint.Cap.BUTT);
//            }
            if(i>0){
                startAngle += angleList.get(i-1);
            }
            canvas.drawArc(oval,startAngle,angleList.get(i),false,mPaint);
        }

    }

    private void drawText(Canvas canvas) {
        int center = getWidth()/2;

        String percent = "總資產(元)";
        textPaint.setTextSize(aboveTextSize);
        //防止文字邊界超過內環邊界  上面的文字大小減小 下面的文字大小也跟着減小
        while (textPaint.measureText(percent)>getWidth() - 2*mCircleWidth){
            aboveTextSize--;
            belowTextSize--;
            textPaint.setTextSize(aboveTextSize);
        }
        textPaint.setTextAlign(Paint.Align.CENTER);// 設置文字居中,文字的x座標要注意
        textPaint.setColor(aboveTextColor);
        textPaint.setStrokeWidth(0);// 注意此處一定要重新設置寬度爲0,否則繪製的文字會重疊
        Rect bounds = new Rect();// 文字邊框
        textPaint.getTextBounds(percent,0,percent.length(),bounds);// 獲得繪製文字的邊界矩形
        Paint.FontMetricsInt fontMetricsInt = textPaint.getFontMetricsInt();// 獲取繪製Text時的四條線
        int baseline = (int) (center - dip2px(20f));//字體所在位置,在中心點上方20dp
//        int baseline = center+ (fontMetricsInt.bottom - fontMetricsInt.top) / 2 -fontMetricsInt.bottom;
        canvas.drawText(percent,center,baseline,textPaint);

        //總金額
        percent = String.valueOf(allMoney);
        textPaint.setColor(belowTextColor); // 設置文字顏色
        textPaint.setTextSize(belowTextSize);
        textPaint.setFakeBoldText(true);//加粗
        //防止下面的文字超出內環邊界
        while (textPaint.measureText(percent)>getWidth()-2f*mCircleWidth) {
            belowTextSize--;
            textPaint.setTextSize(belowTextSize);
        }
        textPaint.getTextBounds(percent, 0, percent.length(), bounds); // 獲得繪製文字的邊界矩形
        Paint.FontMetricsInt fontMetrics1 = textPaint.getFontMetricsInt(); // 獲取繪製Text時的四條線
        int baseline1 = center + (fontMetrics1.bottom - fontMetrics1.top) / 2 - fontMetrics1.bottom;
//        int baseline1 = center + (fontMetrics1.bottom - fontMetrics1.top) / 2 - fontMetrics1.bottom+38*2;
        canvas.drawText(percent, center, baseline1, textPaint); // 繪製文字
    }

    public static float dip2px(float dipValue) {
        final float scale = Resources.getSystem().getDisplayMetrics().density;
        return (dipValue * scale + 0.5f);
    }
    public void setIsShowMoney(boolean isShowMoney) {
        this.isShowText = isShowMoney;
        invalidate();
    }

}

六,使用方法

 

 

 


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章