先看效果圖
使用方法:
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);
}*/
}