After learning the basics of the previous 4 blogs, I can finally start writing PieChart.

I. Data requirements

To analyze, the user needs to provide what kind of data, first of all to have the value of the data, and then also need the corresponding data name, and color. What do you need to draw the PieChart? As you can see from the graph, you need percentage values, fan angles, block colors. So the total properties are:

public class PieData {
    private String name;
    private float value;
    private float percentage;
    private int color = 0;
    private float angle = 0;
}Copy the code

Set and GET of each attribute should be added by itself.

Constructors

In the constructor, add some XML Settings to create attrs.xml



    
        
        
        
    
Copy the code

This is a partial set of attributes, you can add them if you are obsessive-compulsive and want to set them all. Use TypedArray for property retrieval in PieChart. The following is recommended to avoid running the getXXX method without setting the property.

TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PieChart, defStyleAttr,defStyleRes);
int n = array.getIndexCount();
for (int i=0; iCopy the code

Animation function

Draw a complete circle, rotation Angle 360, animation time setable, default 5 seconds, listen for the animatedValue parameter, used to calculate when drawing. See the custom View — Canvas and ValueAnimator article for the meaning of the parameters involved in the ValueAnimator class.

private void initAnimator(long duration){ if (animator ! =null &&animator.isRunning()){ animator.cancel(); animator.start(); } else {animator. = ValueAnimator ofFloat (0360). SetDuration (duration); animator.setInterpolator(timeInterpolator); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { animatedValue = (float) animation.getAnimatedValue(); invalidate(); }}); animator.start(); }}Copy the code

Fourth, the onMeasure

The default onMeasure method of View does not adjust the layout width and height according to the measurement mode. Therefore, in order to adapt to the layout setting of WRAP_content, the onMeasure method needs to be rewritten.

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width = measureDimension(widthMeasureSpec);
    int height = measureDimension(heightMeasureSpec);
    setMeasuredDimension(width,height);
}Copy the code

The overwritten onMeasure method calls the custom measureDimension method to process the data and then passes it to the system’s setMeasuredDimension method. Take a look at the custom measureDimension method.

private int measureDimension(int measureSpec){ int size = measureWrap(mPaint); int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode){ case MeasureSpec.UNSPECIFIED: size = measureWrap(mPaint); break; case MeasureSpec.EXACTLY: size = specSize; break; Case MeasureSpec.AT_MOST: // Size = math.min (measureWrap(mPaint)); break; } return size; }Copy the code

MeasureDimension calculates the lengths of dimensions separately, depending on the type of measurement, and the meaning of each type was explained in the first article and will not be described here. EXACTLY is used in XML to define match_parent and the exact value, while AT_MOST is used in WRap_content. MeasureWrap method is used to calculate the minimum appropriate length for the current PieChart.

private int measureWrap(Paint paint){ float wrapSize; if (mPieData! =null&&mPieData.size()>1){ NumberFormat numberFormat =NumberFormat.getPercentInstance(); numberFormat.setMinimumFractionDigits(percentDecimal); paint.setTextSize(percentTextSize); float percentWidth = paint.measureText(numberFormat.format(mPieData.get(stringId).getPercentage())+""); paint.setTextSize(centerTextSize); float nameWidth = paint.measureText(name+""); WrapSize = (1.0 f) percentWidth * 4 + nameWidth * * (float) offsetScaleRadius; }else { wrapSize = 0; } return (int) wrapSize; }Copy the code

Width and height are measured in a manner similar to TextView, based on the width of the graph name and percentage text in the PieChart. Where stringId is the Id of the field with the longest character calculated during data processing.

As you can see from the code, the PieChart width and height in wrAP_content is equal to 4 times the percentage character length, plus the length of the graph name.

Five, the onSizeChanged

In this function, get the width and height of the current View and the basispaddingValue of the calculated width and height of the actual drawing area, along with the setting of the radius and layout position required by the PieChart drawing.





protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w-getPaddingLeft()-getPaddingRight(); // Adjust the padding setting mHeight = h-getPaddingTop() -getpaddingBottom (); // adjust the padding setting mViewWidth = w; mViewHeight = h; Math.min(mWidth,mHeight)/2*widthScaleRadius); Rectf.left = -r; rectf.left = -r; rectF.top = -r; rectF.right =r; rectF.bottom = r; RTra = (float) (r*radiusScaleTransparent); rectFTra.left = -rTra; rectFTra.top = -rTra; rectFTra.right = rTra; rectFTra.bottom = rTra; RWhite = (float) (r*radiusScaleInside); RF = (float) (math.min (mWidth,mHeight)/2*widthScaleRadius*offsetScaleRadius); Rectff. left = -rf; rectFF.top = -rF; rectFF.right = rF; rectFF.bottom = rF; . }Copy the code

Six, ontouch

OnDraw is divided into drawing sector, drawing text, drawing three parts. To draw the sector and text, calculate the value of Valueanimator’s listener to complete the animation. You also need to interact on Touch to animate the surface. Before detailed drawing, it is necessary to shift the origin of coordinates to the center and determine whether the data is empty.

1. Draw the sector

float currentStartAngle = 0; // The current starting Angle canvas.save(); canvas.rotate(mStartAngle); float drawAngle; for (int i=0; i=0){ drawAngle = Math.min(pie.getAngle()-1,animatedValue-currentStartAngle); }else { drawAngle = 0; } if (i==angleId){ drawArc(canvas,currentStartAngle,drawAngle,pie,rectFF,rectFTraF,reatFWhite,mPaint); }else { drawArc(canvas,currentStartAngle,drawAngle,pie,rectF,rectFTra,rectFIn,mPaint); } currentStartAngle += pie.getAngle(); } canvas.restore();Copy the code

  • Rotate the canvas based on the current initial Angle. Initialize the starting Angle of the sector, and calculate the next starting Angle by summing.
  • DrawArc is used to draw the sector, and the last annular picture is the same, through a large and a small two sectors to complement the operation, to obtain the radius and width of the circle, but there is a more translucent arc for three-dimensional effect.



  • When drawing the sector, use the current animation value minus the starting Angle and the Angle of the current sector to draw the smaller, as the current sector to draw the Angle. Minus 1 to survive the space between the sectors.
  • AngleId is used to display which sector is clicked on when touching, and the specific judgment will be made in the TouchEvent.

2. Draw text

CurrentStartAngle = mStartAngle; for (int i=0; ipieAngles[i]-pie.getAngle()/2&&percentFlag) { if (i == angleId) { drawText(canvas,pie,currentStartAngle,numberFormat,true); } else { if (pie.getAngle() > minAngle) { drawText(canvas,pie,currentStartAngle,numberFormat,false); } } currentStartAngle += pie.getAngle(); }}Copy the code

  • The text is directional and cannot be drawn after the canvas is rotated, so initialize the current sector at the starting Angle of the PieChart.
  • Then loop the text, and when the fan reaches 1/2 of the current region, start to draw the text of the current region. In order to prevent text from blocking the view, it is necessary to judge whether the Angle passed by the sector is greater than the minimum display Angle before drawing.
  • AngleId is used to display which sector is clicked on when touching, and the specific judgment will be made in the TouchEvent.
private void drawText(Canvas canvas, PieData pie ,float currentStartAngle, NumberFormat numberFormat,boolean flag){
    int textPathX = (int) (Math.cos(Math.toRadians(currentStartAngle + (pie.getAngle() / 2))) * (r + rTra) / 2);
    int textPathY = (int) (Math.sin(Math.toRadians(currentStartAngle + (pie.getAngle() / 2))) * (r + rTra) / 2);
    mPoint.x = textPathX;
    mPoint.y = textPathY;
    String[] strings;
    if (flag){
        strings = new String[]{pie.getName() + "", numberFormat.format(pie.getPercentage()) + ""};
    }else {
        strings = new String[]{numberFormat.format(pie.getPercentage()) + ""};
    }
    textCenter(strings, mPaint, canvas, mPoint, Paint.Align.CENTER);
}Copy the code



The drawText function takes the radius of the Pie Pie Pie Pie Pie Pie Pie Pie Pie Pie Pie Pie PieTextCenter multi-line textCenter functionPerform text drawing. Finally, the starting Angle of the current sector is accumulated for the next sector.

3. Draw the map name

MPaint. SetColor (centerTextColor); mPaint.setTextSize(centerTextSize); mPaint.setTextAlign(Paint.Align.CENTER); // Calculate the Y value mpoint. x=0 based on the Paint TextSize; mPoint.y=0; String[] strings = new String[]{name+""}; textCenter(strings,mPaint,canvas,mPoint, Paint.Align.CENTER);Copy the code

Drawing the name part is easier, just like we did when we drew a single Pie, taking the x and y coordinates of (0,0) and then using the textCenter multi-line text drawing function to draw the text.

Seven, onTouchEvent

OnTouchEvent is used to handle the current click event, as described in the first article, using ACTION_DOWN and ACTION_UP events.

public boolean onTouchEvent(MotionEvent event) { if (touchFlag&&mPieData.size()>0){ switch (event.getAction()){ case MotionEvent.ACTION_DOWN: float x = event.getX()-(mWidth/2); float y = event.getY()-(mHeight/2); float touchAngle = 0; if (x<0&&y<0){ touchangle="" +="180;" }else="" if="" (y<0&&x="">0){ touchAngle += 360; }else if (y>0&&x<0){ touchangle="" +="180;" }="" if="" (touchangle<0){="" float="" touchradius="(float)" math.sqrt(y*y+x*x); ="" (rtra<="" &&="" touchradius<="" r){="" angleid="-Arrays.binarySearch(pieAngles,(touchAngle))-1;" invalidate(); ="" return="" true; ="" case="" motionevent.action_up:="" super.ontouchevent(event); ="" }<="" code="">Copy the code

  • Before running, you need to determine whether PieChart has click effect enabled and whether the data is not empty.
  • When the user clicks, the current coordinates are obtained, and the distance and Angle of the point from the origin are calculated. The distance can be used to determine whether the fan area is clicked, and the Angle can be used to determine which area is clicked. Pass the judged zone Id to the angleId value and, as we said earlier in onDraw, redraw and surface the specified sector according to the angleId.
  • Reset angleId to the default value when the user’s finger leaves the screen and, using the invalidate() function, redraw the changed portion of onDraw.

Eight, summary

After the knowledge preparation of the previous four chapters, the concrete implementation of PieChart in this chapter is finally ushered in. In this article, I reviewed the previous drawing process functions, the VlaueAnimator function, as well as the use of Canvas, Path, and completed a custom pie chart drawing using these methods. In later articles, I will try out some graphs, such as the graph below.



If you have any questions during the reading, please feel free to contact me.

Blog:www.idtkm.com

GitHub:github.com/Idtk

Email address:[email protected]



PieChart sourcePlease click on