Custom ring chart
In Android development, native controls can only meet the needs of most of the time. For some special styles, custom controls are needed to implement them. Next, a circular icon is implemented. The key classes needed are Paint,RectF,Matrix,Path
Basic thinking
1. rings
It can be seen that the circle has a sector area that does not pass through. We can understand that drawing two arcs and connecting the head and tail can form this shape
Path path = new Path();
path.addArc(mMaxRect, startAngle, allData.get(i).getAngle());
path.arcTo(mMinRect, startAngle + allData.get(i).getAngle(), -allData.get(i).getAngle());
path.close();
2. lines
Call drawLine() method
canvas.drawLine(startX, startY, endX, endY, mPaint);
3. character writing
Pay attention to the position of base class line when changing text
mPaint.setTextAlign(Paint.Align.LEFT);
canvas.drawText(tmpText, endX + defaultS, endY + mTmpRect.height() / 2, mPaint);
All code
/**
* Description: Circular pie
* Created by Buuu on 2018/04/27.
*/
public class CirclePieChart extends View {
private List<PieChartPart> allData;
private Paint mPaint;
private RectF mMinRect;
private RectF mMaxRect;
private Matrix mMatrix;
private List<Path> mPathsList;
private int mWH;
private int mMaxWH;
private int defaultSpace = 250;//Width around
private float startAngle = 0f; //Initial angle
private float lineLength = 50; //Length of indicator line
public CirclePieChart(Context context) {
this(context, null);
}
public CirclePieChart(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CirclePieChart(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initData();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mMatrix.reset();
if (allData != null && !allData.isEmpty()) {
mMaxWH = Math.min(w, h);
mWH = Math.min(w, h) - defaultSpace;
//Adjust the width by adjusting the size of the inner or outer ring
mMaxRect = new RectF(-mWH / 2, -mWH / 2, mWH / 2, mWH / 2);
mMinRect = new RectF(-mWH / 4, -mWH / 4, mWH / 4, mWH / 4);
//Draw a circular figure. If you need to click on an event, add it as required
for (int i = 0; i < allData.size(); i++) {
Path path = new Path();
path.addArc(mMaxRect, startAngle, allData.get(i).getAngle());
path.arcTo(mMinRect, startAngle + allData.get(i).getAngle(), -allData.get(i).getAngle());
path.close();
startAngle+=allData.get(i).getAngle();
mPathsList.add(path);
}
}
}
private void initData() {
mPathsList = new ArrayList<>();
mPaint = new Paint();
mPaint.setTextSize(28);
mPaint.setColor(Color.parseColor("#3300f0ff"));
mPaint.setTextAlign(Paint.Align.LEFT);
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(3);
mMatrix = new Matrix();
}
private Rect mTmpRect = new Rect();
private float defaultS = 10F;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.translate(mMaxWH / 2, mMaxWH / 2);
if (mMatrix.isIdentity()) {
canvas.getMatrix().invert(mMatrix);
}
for (int i = 0; i < mPathsList.size(); i++) {
mPaint.setColor(allData.get(i).getColor());
canvas.drawPath(mPathsList.get(i), mPaint);
}
float childStartAngle = startAngle;
//Lines and characters
for (int i = 0; i < allData.size(); i++) {
double cos = Math.cos(Math.toRadians(childStartAngle + (allData.get(i).getAngle() ) / 2));
double sin = Math.sin(Math.toRadians(childStartAngle + (allData.get(i).getAngle() ) / 2));
float startX = (float) (cos * mWH) / 2;
float endX = (float) (lineLength * cos + startX);
float startY = (float) (sin * mWH) / 2;
float endY = (float) (lineLength * sin + startY);
mPaint.setColor(allData.get(i).getColor());
canvas.drawLine(startX, startY, endX, endY, mPaint);
String tmpText = (int) Math.ceil(allData.get(i).getAngle()*100/360) +"%" ;
mPaint.getTextBounds(tmpText, 0, tmpText.length(), mTmpRect);
if (endY == 0 && endX > 0) {// x-axis positive direction
mPaint.setTextAlign(Paint.Align.LEFT);
canvas.drawText(tmpText, endX + defaultS, endY + mTmpRect.height() / 2, mPaint);
} else if (endX > 0 && endY > 0 && endX >= endY) {//x-axis positive direction 0 < angle < = 45
mPaint.setTextAlign(Paint.Align.LEFT);
canvas.drawText(tmpText, endX + defaultS, endY + mTmpRect.height() / 2, mPaint);
} else if (endX > 0 && endY > 0 && endX < endY) {//x-axis positive direction 45 < angle < 90
mPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(tmpText, endX, endY + mTmpRect.height() + defaultS, mPaint);
} else if (endX == 0 && endY > 0) { //Positive direction of y axis
mPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(tmpText, endX, endY + mTmpRect.height() + defaultS, mPaint);
} else if (endX < 0 && endY > 0 && Math.abs(endX) <= Math.abs(endY)) {//Positive direction of y axis 90 < a < = 135
mPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(tmpText, endX, endY + mTmpRect.height() + defaultS, mPaint);
} else if (endX < 0 && endY > 0 && Math.abs(endX) > Math.abs(endY)) {//Positive direction of y axis 135 < a < 180
mPaint.setTextAlign(Paint.Align.RIGHT);
canvas.drawText(tmpText, endX - defaultS, endY + mTmpRect.height() / 2, mPaint);
} else if (endX < 0 && endY == 0) {//x-axis negative reverse
mPaint.setTextAlign(Paint.Align.RIGHT);
canvas.drawText(tmpText, endX - defaultS, endY + mTmpRect.height() / 2, mPaint);
} else if (endX < 0 && endY < 0 && Math.abs(endX) >= Math.abs(endY)) {//x-axis negative reverse 180 < a < = 225
mPaint.setTextAlign(Paint.Align.RIGHT);
canvas.drawText(tmpText, endX - defaultS, endY + mTmpRect.height() / 2, mPaint);
} else if (endX < 0 && endY < 0 && Math.abs(endX) < Math.abs(endY)) {//x-axis negative reverse 225 < a < 270
mPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(tmpText, endX, endY - defaultS, mPaint);
} else if (endX == 0 && endY < 0) {//y-axis negative direction
mPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(tmpText, endX, endY - defaultS, mPaint);
} else if (endX > 0 && endY < 0 && Math.abs(endX) <= Math.abs(endY)) {//Negative direction of y axis 270 < a < = 315
mPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(tmpText, endX, endY - defaultS, mPaint);
} else if (endX > 0 && endY < 0 && Math.abs(endX) > Math.abs(endY)) {//y-axis negative direction 315 < a < 360
mPaint.setTextAlign(Paint.Align.LEFT);
canvas.drawText(tmpText, endX + defaultS, endY + mTmpRect.height() / 2, mPaint);
} else {//Other
mPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(tmpText, endX + defaultS, endY + defaultS, mPaint);
}
childStartAngle += allData.get(i).getAngle();
}
}
/**
* Setting data
*
* @param allDatas PieChartPart
*/
public void setData(List<PieChartPart> allDatas) {
this.allData = allDatas;
int numericalSum = 0;
int tempAngle;
int standardAngle = 360;
for (int i = 0; i < allData.size(); i++) {
numericalSum+=allData.get(i).getNumerical();
}
for (int i = 0; i < allData.size(); i++) {
tempAngle = (int) (allData.get(i).getNumerical() * 360 / numericalSum);
if(i>1){
tempAngle +=1;
}
if (allData.size()-1 == i){
tempAngle = standardAngle;
}else {
standardAngle -= tempAngle;
}
allData.get(i).setAngle(tempAngle);
}
invalidate();
}
}
//PieChartPart
public class PieChartPart {
private int color;
private float angle;
private float numerical;
public PieChartPart() {
}
public PieChartPart(int color, float numerical) {
this.color = color;
this.numerical = numerical;
}
public PieChartPart(int color, float numerical,float angle) {
this.color = color;
this.numerical = numerical;
this.angle = angle;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public float getAngle() {
return angle;
}
public void setAngle(float angle) {
this.angle = angle;
}
public float getNumerical() {
return numerical;
}
public void setNumerical(int numerical) {
this.numerical = numerical;
}
}
Basic thinking
The Path class is mainly used. For a better understanding, you can focus on this class. Oh, by the way, you can use the region class if the click event is involved