Android自定義View實戰(zhàn)(圓餅圖的實現)

先看效果圖
piechart.png
pieview1.gif
piechart.gif

使用方法:

AndroidStudio引入(https://jitpack.io/

step1:Add it in your root build.gradle at the end of repositories:
allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }
step2:Add the dependency
dependencies {
            compile 'com.github.ithedan:HeDan_Piechart:v2.0'
    }
布局
 <com.hedan.piechart_library.PieChart_View
        android:id="@+id/pie_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:padding="50dp"
        app:isAnimation="true"
        app:isTouchFlag="true"
        app:animaDuration="5000"
        app:mRadius="140dp"
        app:nameColor="#f00"
        app:name="清明時節(jié)雨紛"
        app:nameSize="16sp"
        app:nameOrientation="horizontal"
        app:pieChartWidth="70dp"
        app:alphaWidth="10dp"
        app:alphaPieColor="#fff"
        app:alphaPieTran="100"
        app:segmentAngle="0.5"
        app:startAngle="180"
        app:textColor="#000"
        app:textSize="18sp"
        app:textOrientation="path"
        app:inCricleColor="#eeeeee"
        />

Activity中使用:

 pieView = (PieChart_View) findViewById(R.id.pie_view);
        ArrayList<PieChartBean> lists = new ArrayList<>();
        lists.add(new PieChartBean(Color.parseColor("#ee3c5d"), 300, "蘋果"));
        lists.add(new PieChartBean(Color.parseColor("#ffc12c"), 50, "香蕉"));
        lists.add(new PieChartBean(Color.parseColor("#1fde94"), 599, "哈密瓜"));
        lists.add(new PieChartBean(Color.parseColor("#f5a623"), 500, "橘子"));
        lists.add(new PieChartBean(Color.parseColor("#fa734e"), 1000, "桃子"));
        lists.add(new PieChartBean(Color.parseColor("#947ddf"), 300, "葡萄"));
        lists.add(new PieChartBean(Color.parseColor("#ee3c5d"), 50, "火龍果"));
        lists.add(new PieChartBean(Color.parseColor("#f7964f"), 500, "芒果"));
        lists.add(new PieChartBean(Color.parseColor("#ff4639"), 400, "西紅柿"));
        lists.add(new PieChartBean(Color.parseColor("#ff8053"), 300, "檸檬"));
        lists.add(new PieChartBean(Color.parseColor("#ee3c5d"), 5, "櫻桃"));
        lists.add(new PieChartBean(Color.parseColor("#1fde94"), 500, "西瓜"));
        pieView.setData(lists);
如果不需要文字 lists.add(new PieChartBean(Color.parseColor("#ee3c5d"), 300));這樣不會顯示文字

先看看自定義屬性attrs.xml

  <declare-styleable name="PieChart_View">
        <attr name="name" format="string"/>
        <attr name="nameSize" format="dimension"/>
        <attr name="nameOrientation" >
            <enum name="horizontal" value="0"/>
            <enum name="vertical" value="1"/>
        </attr>
        <attr name="nameColor" format="color"/>
        <attr name="textOrientation">
            <enum name="horizontal" value="0"/>
            <enum name="path" value="2"/>
        </attr>
        <attr name="textSize" format="dimension"/>
        <attr name="textColor" format="color"/>
        <attr name="pieChartWidth" format="dimension"/>
        <attr name="mRadius" format="dimension"/>
        <attr name="alphaWidth" format="dimension"/>
        <attr name="alphaPieTran" format="integer"/>
        <attr name="alphaPieColor" format="color"/>
        <attr name="inCricleColor" format="color"/>  
        <attr name="isAnimation" format="boolean"/>
        <attr name="animaDuration" format="integer"/>
        <attr name="isTouchFlag" format="boolean"/>
        <attr name="startAngle" format="integer"/>
        <attr name="segmentAngle" format="float"/>
    </declare-styleable>
屬性 說明
name 中間文字
nameSize 中間文字大小dp(默認16dp)
nameOrientation 中間文字方向(水平or垂直膳汪,默認水平)
nameColor 中間文字顏色(默認黑色)
textOrientation 內容文字方向(水平or圓弧方向path醉旦,默認圓弧方向)
textSize 內容文字大小dp(默認16d'p)
textColor 內容文字顏色(默認黑色)
pieChartWidth 圓餅寬度(默認是半徑的一半)
mRadius 最大圓半徑(默認是View 寬高最小值的一半)
alphaWidth 中間透明圓寬度(默認是圓餅寬度的0.1)
alphaPieTran 中間透明圓透明度(0--255)(默認80)
alphaPieColor 中間透明圓顏色(默認白色)
inCricleColor 內圓顏色(默認透明)
isAnimation 是否開啟動畫(默認開啟true)
animaDuration 動畫持續(xù)時間(默認2000)
isTouchFlag 是否開啟觸摸切割效果(默認開啟true)
startAngle 圓餅起始角度(默認0)
segmentAngle 圓餅分割角度(0-0.9)(默認0)
主要方法

1、onMeasure (這個方法烟阐,只要你以前寫過自定義View,基本上就是一樣的套路:)

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int wideSize = MeasureSpec.getSize(widthMeasureSpec);
        int wideMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int width, height;
        if (wideMode == MeasureSpec.EXACTLY) { //精確值 或matchParent
            width = wideSize;
        } else {
            width = mRadius == 0 ? getWidth() : mRadius * 2 + getPaddingLeft() + getPaddingRight();
            if (wideMode == MeasureSpec.AT_MOST) {
                width = Math.min(width, wideSize);
            }
        }
        if (heightMode == MeasureSpec.EXACTLY) { //精確值 或matchParent
            height = heightSize;
        } else {
            height = mRadius == 0 ? getHeight() : mRadius * 2 + getPaddingTop() + getPaddingBottom();
            if (heightMode == MeasureSpec.AT_MOST) {
                height = Math.min(height, heightSize);
            }
        }
        setMeasuredDimension(width == 0 ? height : width, height == 0 ? width : height);
    }

2忍燥、onSizeChanged(這個方法主要確定一些相關的大信≡巍)

 @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
        mRadius = (int) (Math.min(w - getPaddingLeft() - getPaddingRight(),
                h - getPaddingTop() - getPaddingBottom()) * 1.0f / 2);
        centerX = (w + getPaddingLeft() - getPaddingRight()) / 2;
        centerY = (h + getPaddingTop() - getPaddingBottom()) / 2;
        if (pieChartWidth == 0) {
            pieChartWidth = mRadius * 0.5f;
        }
        if (alphaWidth == 0) {
            alphaWidth = pieChartWidth * 0.1f;
        }
        outRadius = mRadius - pieChartWidth / 2;
        outRecF = new RectF(-outRadius, -outRadius, outRadius, outRadius);
        touchOutRecF = new RectF(-outRadius - mRadius / 10, -outRadius - mRadius / 10, outRadius + mRadius / 10, outRadius + mRadius / 10);

        inRadius = mRadius - pieChartWidth;
        inRecF = new RectF(-inRadius, -inRadius, inRadius, inRadius);

        float alphaRadius = inRadius + alphaWidth / 2;
        alphaRecf = new RectF(-alphaRadius, -alphaRadius, alphaRadius, alphaRadius);
        touchAlphaRecF = new RectF(-alphaRadius - mRadius / 10, -alphaRadius - mRadius / 10, alphaRadius + mRadius / 10, alphaRadius + mRadius / 10);


        Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
        textPaintHeight = (-fontMetrics.ascent - fontMetrics.descent) / 2;

        outTextRecF = new RectF(-mRadius - textPaintHeight, -mRadius - textPaintHeight, mRadius + textPaintHeight, mRadius + textPaintHeight);
        touchOutTextRecF = new RectF(-mRadius - textPaintHeight - mRadius / 10, -mRadius - textPaintHeight - mRadius / 10, mRadius + textPaintHeight + mRadius / 10, mRadius + textPaintHeight + mRadius / 10);

        if (isAnimation) {
            initAnimator();
        } else {
            animatedValue = 360;
        }
    }

3、動畫

 /**
     * 自定義屬性動畫
     */
    private void initAnimator() {
        ValueAnimator anim = ValueAnimator.ofFloat(0, 360);
        anim.setDuration(animaDuration);
        anim.setInterpolator(timeInterpolator);
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                animatedValue = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        anim.start();
    }

4梅垄、onDraw

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(centerX, centerY);//一定畫布原點到中間
        canvas.save();
        canvas.rotate(getStartangle(drawStartAngle));
        drawPieChart(canvas);
        drawText(canvas);
        canvas.restore();
        drawCenterText(TextUtils.isEmpty(pieName) ? "HeDan" : pieName, canvas, textPaint);
    }

5厂捞、 繪制餅圖

 private void drawPieChart(Canvas canvas) {
        if (mDatas.size() == 0) {
            return;
        }
        float startAngle = 0;
        float sumPercentage = 0;
        for (int i = 0; i < mDatas.size(); i++) {
            PieChartBean ben = mDatas.get(i);
            arcPaint.setColor(ben.getPieColor());
            arcPaint.setStrokeWidth(pieChartWidth);

            alphaPait.setStrokeWidth(alphaWidth);
            alphaPait.setColor(alphaPieColor);
            alphaPait.setAlpha(getAlphaPieTran(alphaPieTran));

            float sweepAngle = ben.getPercentage();
            sumPercentage += sweepAngle;
            pieAngles[i] = sumPercentage;
            float drawAngle = animatedValue - startAngle;
            if (Math.min(sweepAngle, drawAngle) >= 0) {
                float sweepNewAngle = Math.min(sweepAngle, drawAngle) - segmentAngle;
                drawInCricle(canvas,startAngle, Math.min(sweepAngle, drawAngle),i);
                if (angleTag == i) {
                    canvas.drawArc(touchOutRecF, startAngle,  sweepNewAngle, false, arcPaint);
                    canvas.drawArc(touchAlphaRecF, startAngle,  sweepNewAngle, false, alphaPait);
                } else {
                    canvas.drawArc(outRecF, startAngle, sweepNewAngle, false, arcPaint);
                    canvas.drawArc(alphaRecf, startAngle, sweepNewAngle, false, alphaPait);
                }
            }
            startAngle += sweepAngle;
        }
    }

6、觸摸分割

@Override
   public boolean onTouchEvent(MotionEvent event) {
       if (isTouchFlag && mDatas.size() > 0) {
           switch (event.getAction()) {
               case MotionEvent.ACTION_DOWN:
                   float eventX = event.getX() - (mWidth / 2);
                   float eventY = event.getY() - (mHeight / 2);
                   float touchAngle = 0;
                   if (eventX < 0 && eventY < 0) {//180-270
                       touchAngle += 360;
                   } else if (eventX > 0 && eventY < 0) {//270-360
                       touchAngle += 360;
                   }
                   touchAngle += (float) Math.toDegrees(Math.atan2(eventY, eventX));
                   touchAngle = touchAngle - getStartangle(drawStartAngle);
                   if (touchAngle < 0) {
                       touchAngle = touchAngle + 360;
                   }
                   float touchRadius = (float) Math.sqrt(eventX * eventX + eventY * eventY);
                   if (touchRadius < mRadius && touchRadius > inRadius) {
                       angleTag = -Arrays.binarySearch(pieAngles, touchAngle) - 1;
                       invalidate();
                   }
                   return true;
               case MotionEvent.ACTION_UP:
                   angleTag = -1;
                   invalidate();
                   return true;
           }
       }
       return super.onTouchEvent(event);
   }

7、繪制文本

  //繪制文本
    private void drawText(Canvas canvas) {
        if (mDatas.size() == 0) {
            return;
        }
        float startAngle = 0;
        for (int i = 0; i < mDatas.size(); i++) {
            PieChartBean ben = mDatas.get(i);
            float sweepAngle = ben.getPercentage();
            if (isAnimation) {
                float textAngle = getTextAngle(ben.getPieString());
                float startNewAngle = (startAngle + sweepAngle / 2) - textAngle / 2;
                if (animatedValue >= startNewAngle) {
                    drawText(startAngle, ben.getPercentage(), canvas, ben.getPieString(), i);//繪制文本
                }
            } else {
                drawText(startAngle, ben.getPercentage(), canvas, ben.getPieString(), i);//繪制文本
            }
            startAngle += sweepAngle;
        }
    }

    //繪制中心名稱
    private void drawCenterText(String text, Canvas canvas, Paint paint) {
        TextPaint textPaint=new TextPaint();
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setColor(nameColor);
        textPaint.setTextSize(nameSize);
        int width=(int)Math.sqrt(((2*inRadius)*(2*inRadius))/2);
        StaticLayout staticLayout=new StaticLayout(text,textPaint,width, Layout.Alignment.ALIGN_NORMAL,1.1f,0,false);
        if (animatedValue == 360) {
            if (nameOrientation == HORIZONTAL) {
                canvas.save();
                canvas.translate(0,-staticLayout.getHeight()/2);
                staticLayout.draw(canvas);
                canvas.restore();
            } else if (nameOrientation == VERTICAL) {
                drawVTextView(text, canvas, paint);
            }
        }
    }

    //繪制垂直方向文字
    private void drawVTextView(String texts, Canvas canvas, Paint paint) {
        paint.setTextAlign(Paint.Align.CENTER);
        paint.setColor(nameColor);
        paint.setTextSize(nameSize);
        //每個字符居中
        Paint.FontMetrics fontMetrics = paint.getFontMetrics();
        float top=fontMetrics.ascent;
        float bottom=fontMetrics.bottom;
        char[] chars1 = texts.toCharArray();
        int length=chars1.length;
        float total=(length-1)*(-top+bottom)+(-fontMetrics.ascent+fontMetrics.descent);
        float offset=total/2-bottom;
        for(int i=0;i<length;i++){
            float yAxis=-(length-i-1)*(-top+bottom)+offset;
            canvas.drawText(chars1[i]+"",0,yAxis,paint);
        }

    }


    //繪制文字
    private void drawText(float startAngle, float sweepAngle, Canvas canvas, String text, int i) {
        if (TextUtils.isEmpty(text)) {
            return;
        }
        textPaint.setColor(textColor);
        textPaint.setTextSize(textSize);
        textPaint.setTextAlign(Paint.Align.CENTER);
        if (textOrientation == PiePath) {
            float needAngle = getNeedAngle(sweepAngle, text);
            if (angleTag == i) {
                textPath.addArc(needAngle == sweepAngle ? touchOutRecF : touchOutTextRecF, needAngle == sweepAngle ? startAngle : (startAngle + sweepAngle / 2) - needAngle / 2, needAngle == sweepAngle ? sweepAngle : needAngle);
            } else {
                textPath.addArc(needAngle == sweepAngle ? outRecF : outTextRecF, needAngle == sweepAngle ? startAngle : (startAngle + sweepAngle / 2) - needAngle / 2, needAngle == sweepAngle ? sweepAngle : needAngle);
            }
            canvas.drawTextOnPath(text, textPath, 0, 0, textPaint);
            textPath.reset();
        } else if (textOrientation == HORIZONTAL) {
            float r = outRadius;
            float offsetY = textPaintHeight;
            if (getNeedAngle(sweepAngle, text) != sweepAngle) {
                r = mRadius;
                if (startAngle > 0 && startAngle < 90) {
                    textPaint.setTextAlign(Paint.Align.LEFT);
                    offsetY += offsetY;
                } else if (startAngle > 90 && startAngle < 180) {
                    textPaint.setTextAlign(Paint.Align.RIGHT);
                    offsetY += offsetY;
                } else if (startAngle > 180 && startAngle < 270) {
                    textPaint.setTextAlign(Paint.Align.RIGHT);
                    offsetY = 0;
                } else if (startAngle > 270 && startAngle < 360) {
                    textPaint.setTextAlign(Paint.Align.LEFT);
                    offsetY = 0;
                } else {
                    offsetY = textPaintHeight;
                }
            }
            if (angleTag == i) {
                r = r + mRadius / 10;
            }
            int textCX = (int) (Math.cos(Math.toRadians(startAngle + sweepAngle / 2)) * r);
            int textCY = (int) (Math.sin(Math.toRadians(startAngle + sweepAngle / 2)) * r);
            canvas.drawText(text, textCX, textCY + offsetY, textPaint);
        }


       /* float r = outRadius;
        float textWidth = textWidth(text, textPaint);
        if (arcLength(sweepAngle, outRadius) < textWidth) {
            r = mRadius;
        }
        int textCX = (int) (Math.cos(Math.toRadians(startAngle + sweepAngle / 2)) * r);
        int textCY = (int) (Math.sin(Math.toRadians(startAngle + sweepAngle / 2)) * r);
        canvas.drawText(text, textCX, textCY + textPaintHeight, textPaint);

        float textWidth = textWidth(text, textPaint);
        if (arcLength(sweepAngle, outRadius) < textWidth) {
            float angle = (float) (textWidth * 360 / (2 * 3.14 * outRadius));
            float offsetAngle = angle / 2;
            textPath.addArc(outTextRecF, (startAngle + sweepAngle / 2) - offsetAngle, angle);
        } else {
            textPath.addArc(outRecF, startAngle, sweepAngle);
        }*/

    }

以上為主要方法和計算蔫敲,源碼地址https://github.com/ithedan/HeDan_Piechart

如有什么問題饲嗽,敬請?zhí)岢觯指兄x奈嘿!希望越來越好貌虾,謝謝!如果喜歡裙犹,還請點擊start尽狠,喜歡支持一下了,謝謝O(∩_∩)O~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末叶圃,一起剝皮案震驚了整個濱河市袄膏,隨后出現的幾起案子,更是在濱河造成了極大的恐慌掺冠,老刑警劉巖沉馆,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異德崭,居然都是意外死亡斥黑,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門眉厨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锌奴,“玉大人,你說我怎么就攤上這事憾股÷故瘢” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵服球,是天一觀的道長茴恰。 經常有香客問我,道長斩熊,這世上最難降的妖魔是什么琐簇? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮座享,結果婚禮上,老公的妹妹穿的比我還像新娘似忧。我一直安慰自己渣叛,他們只是感情好,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布盯捌。 她就那樣靜靜地躺著淳衙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上箫攀,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天肠牲,我揣著相機與錄音,去河邊找鬼靴跛。 笑死缀雳,一個胖子當著我的面吹牛,可吹牛的內容都是我干的梢睛。 我是一名探鬼主播肥印,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼绝葡!你這毒婦竟也來了深碱?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤藏畅,失蹤者是張志新(化名)和其女友劉穎敷硅,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體愉阎,經...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡绞蹦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了诫硕。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坦辟。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖章办,靈堂內的尸體忽然破棺而出锉走,到底是詐尸還是另有隱情,我是刑警寧澤藕届,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布挪蹭,位于F島的核電站,受9級特大地震影響休偶,放射性物質發(fā)生泄漏梁厉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一踏兜、第九天 我趴在偏房一處隱蔽的房頂上張望词顾。 院中可真熱鬧,春花似錦碱妆、人聲如沸肉盹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽上忍。三九已至骤肛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間窍蓝,已是汗流浹背腋颠。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吓笙,地道東北人淑玫。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像观蓄,于是被迫代替她去往敵國和親混移。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內容