First of all, according to the old rules, no picture, no truth, look first:




2. GIF

Doesn’t it look like that? What’s the implementation, even though it’s one in general. Calculate the value of each variable (remember that it changes with the size of the entire View). 2. Secondly, make good use of the method canvas.translate() to calculate the size of the origin of moving the canvas. The last thing is to call the various methods provided by the API to draw a picture. Isn’t that too simplistic? Okay, let’s do it now

Let’s take a look at that. Let’s start by looking at what parameters XML has

 
 Copy the code

The parameters are explained as follows:

// time private String time; Private int allStop; Private int friendAverageStep; // private int averageStep; // private String ranking; // private Bitmap champion_icon; // Private String champion;Copy the code

The code snippet then initializes all parameters:

TypedArray mTypedArray=context.getTheme().obtainStyledAttributes(attrs,R.styleable.BesselCurveView,defStyleAttr,0); int numCount=mTypedArray.getIndexCount(); for(int i=0; iCopy the code

These are the equivalent templates that every custom has to initialize the parameters, you can see that. It is also easy to initialize variables such as brushes to make it easier to draw later:

public void initValue(){ animSet=new AnimatorSet(); MCirclePaint =new Paint(paint.anti_alias_flag); // mCirclePaint=new Paint(paint.anti_alias_flag); mCirclePaint.setStyle(Paint.Style.STROKE); mCirclePaint.setStrokeWidth(radius/10); mCirclePaint.setStrokeJoin(Paint.Join.ROUND); mCirclePaint.setStrokeCap(Paint.Cap.ROUND); mCirclePaint.setAntiAlias(true); MCenterTextPaint =new Paint(); mCenterTextPaint.setColor(mBesselCurveColor); mCenterTextPaint.setTextSize(radius/5); mCenterTextPaint.setAntiAlias(true); MTextPaint =new Paint(); mTextPaint.setAntiAlias(true); MBottomRectPaint =new Paint(paint.anti_alias_flag); mBottomRectPaint.setColor(mBesselCurveColor); mBottomRectPaint.setAntiAlias(true); MDottedLinePaint = new Paint(); mDottedLinePaint.setAntiAlias(true); mDottedLinePaint.setStyle(Paint.Style.STROKE); mDottedLinePaint.setStrokeWidth(2); mDottedLinePaint.setColor(mBesselCurveColor); MDottedLinePaint. SetPathEffect (new DashPathEffect (new float [] {5, 5}, 1)); WavylinesPaint=new Paint(); WavylinesPaint = new Paint(Paint.ANTI_ALIAS_FLAG); WavylinesPaint.setColor(wavyColor); WavylinesPaint.setStyle(Paint.Style.FILL_AND_STROKE); MDottedLinePath =new Path(); WavyLinePath=new Path(); MorePath =new Path(); MWaveCount = (int) math. round(widthView/mWaveLength + 1.5); marginBottomText=radius/4; }Copy the code

Now that we're done with the most important initialization, it's time to draw (draw), post all the code for the drawing and go through it one by one:

protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.save(); canvas.translate(widthView/2,(heightView*((float)2/3))/2); // Draw the inner circle McIrclepaint.setcolor (besselColorText); RectF mCircleRectF=new RectF(-radius,-radius,radius,radius); Canvas. DrawArc (mCircleRectF, 120300, false, mCirclePaint); // Outside circle McIrclepaint.setcolor (mBesselCurveColor); canvas.drawArc(mCircleRectF,120,mCircleNum,false,mCirclePaint); Rect mCenterRect=new Rect(); String tempAllStop=mCenterNum+""; mCenterTextPaint.getTextBounds(tempAllStop,0,tempAllStop.length(),mCenterRect); int halfWidthText=(mCenterRect.right-mCenterRect.left)/2; int halfHeightText=(mCenterRect.bottom-mCenterRect.top)/2; canvas.drawText(tempAllStop,-halfWidthText,halfHeightText,mCenterTextPaint); // Draw the text above mtextpaint.setcolor (besselColorText); mTextPaint.setTextSize(radius/6); String tempFriendAverageStep=stringTemplate(R.string.besselTime,time); Rect mTopRect=new Rect(); mTextPaint.getTextBounds(tempFriendAverageStep,0,tempFriendAverageStep.length(),mTopRect); int halfTopWidthText=(mTopRect.right-mTopRect.left)/2; canvas.drawText(tempFriendAverageStep,-halfTopWidthText,-(halfHeightText+marginText),mTextPaint); / / text String at the bottom of the painting tempAverageStep = stringTemplate (R.s tring. FriendAverageStep friendAverageStep + ""); Rect mBottomRect=new Rect(); mTextPaint.getTextBounds(tempAverageStep,0,tempAverageStep.length(),mBottomRect); int halfBottomWidthText=(mBottomRect.right-mBottomRect.left)/2; int mBottomHeightText=(mBottomRect.bottom-mBottomRect.top); canvas.drawText(tempAverageStep,- halfBottomWidthText,mBottomHeightText+halfHeightText+marginText,mTextPaint); Rect mNumRect=new Rect(); mCenterTextPaint.getTextBounds(ranking,0,ranking.length(),mNumRect); int halfNum=(mNumRect.right-mNumRect.left)/2; mCenterTextPaint.setTextSize(40); canvas.drawText(ranking,- halfNum,radius,mCenterTextPaint); String rankingLeft=getContext().getResources().getString(R.string.ranking_left); mTextPaint.getTextBounds(rankingLeft,0,rankingLeft.length(),mNumRect); canvas.drawText(rankingLeft,-halfNum-(mNumRect.right- mNumRect.left)/2-20,radius,mTextPaint); canvas.drawText(getContext().getResources().getString(R.string.ranking_right),halfNum+10,radius,mTextPaint); canvas.restore(); // Draw the last seven days and the average movement mtextpaint.settextSize (radius/9); canvas.save(); canvas.translate(0,heightView*((float)2/3)); canvas.drawText(getContext().getResources().getString(R.string.nextSevenDay),marginLi neChart,0,mTextPaint); Rect mPercentRect=new Rect(); String mPercentText=stringTemplate(R.string.averageStep,averageStep+""); mTextPaint.getTextBounds(mPercentText,0,mPercentText.length(),mPercentRect); canvas.drawText(mPercentText,widthView-marginLineChart-(mPercentRect.right- mPercentRect.left),0,mTextPaint); / / draw a dotted line mDottedLinePath. MoveTo (marginLineChart marginBottomText); mDottedLinePath.lineTo(widthView-marginLineChart,marginBottomText); canvas.drawPath(mDottedLinePath,mDottedLinePaint); MTextPaint. SetTextSize (radius/9); int lineWidth=(widthView-marginLineChart*2)/8; mCalendar.setTime(new Date()); RectF mRecf=null; if(mListStep.size()>0){ for(int i=mListStep.size(); i>=1; i--){ if(mListStep.get(i-1)! =0){ int startX=marginLineChart+lineWidth*i-radius/23; int endX=marginLineChart+lineWidth*i+radius/23; If (mliststep.get (i-1)>mStandardStop){mtextPaint.setcolor (mBesselCurveColor); int exceed=mListStep.get(i-1)-mStandardStop; float standard=(float) (mCircleRectHeight*Double.valueOf(exceed/Double.valueOf(mStandardStop))); mRecf=new RectF(startX,marginBottomText-(standard>mCircleRectHeight? mCircleRectHeight:standard) ,endX,marginBottomText+mCircleRectHeight); Canvas. DrawRoundRect (mRecf, 50, 50, mTextPaint); }else{// mTextpaint.setcolor (besselColorText); float noStandard=(float)(mCircleRectHeight*Double.valueOf(mListStep.get(i-1)/Double.valueOf(mStandardStop))); mRecf=new RectF(startX,marginBottomText,endX,marginBottomText+( noStandard>mCircleRectHeight? mCircleRectHeight:noStandard)); Canvas. DrawRoundRect (mRecf, 50, 50, mTextPaint); }} // Draw the date at the bottom mTextpaint.setcolor (besselColorText); mCalendar.set(Calendar.DAY_OF_MONTH,mCalendar.get(Calendar.DAY_OF_MONTH)-1); Rect rect =new Rect(); String number=stringTemplate(R.string.day,mCalendar.get(Calendar.DAY_OF_MONTH)+""); mTextPaint.getTextBounds(number,0,number.length(),rect); canvas.drawText(number,(marginLineChart+lineWidth*i)-(rect.right-rect.left)/2,marginBottomText+70,mTextPaint); } } canvas.restore(); // Draw the wave image canvas.save(); float mWavyHeight=heightView*((float)4/5)+50; canvas.translate(0,mWavyHeight); WavyLinePath.reset(); WavyLinePath.moveTo(-mWaveLength+ mOffset,0); int wHeight=radius/5; for(int i=0; iCopy the code

Isn't there a lot of code? There's a lot of stuff you can't draw. Ok, at the beginning, I said to move the origin of the canvas, didn't I? Look, I moved it at the beginning:

  super.onDraw(canvas);
  canvas.save();
  canvas.translate(widthView/2,(heightView*((float)2/3))/2);Copy the code

1. Move the origin to the center of the whole arc, where widthView is the width of the whole view and heightView is the height of the whole view, as shown below:




center.PNG

That blue dot right there is the origin right now. And then I'm going to draw an arc around the origin, like this

// Draw the inner circle McIrclepaint.setcolor (besselColorText); RectF mCircleRectF=new RectF(-radius,-radius,radius,radius); Canvas. DrawArc (mCircleRectF, 120300, false, mCirclePaint); // Outside circle McIrclepaint.setcolor (mBesselCurveColor); canvas.drawArc(mCircleRectF,120,mCircleNum,false,mCirclePaint);Copy the code

The mCircleNum is for animation, and we'll talk about that later, so that the arc is done. The effect is similar to the image above. 2. Draw today's total distance at the center point, with the code as follows:

Rect mCenterRect=new Rect(); String tempAllStop=mCenterNum+""; mCenterTextPaint.getTextBounds(tempAllStop,0,tempAllStop.length(),mCenterRect); int halfWidthText=(mCenterRect.right-mCenterRect.left)/2; int halfHeightText=(mCenterRect.bottom-mCenterRect.top)/2; canvas.drawText(tempAllStop,-halfWidthText,halfHeightText,mCenterTextPaint);Copy the code

The basic idea is to use Rect in this class to calculate the size of the text you want to draw, and then draw in the original point, but remember that x and y points are below the origin to the left of the origin, see the link here and then this is the drawing time and the average number of steps of your friends, in fact, the implementation principle is the same, but at the top height is

canvas.drawText(tempFriendAverageStep,-halfTopWidthText,-(halfHeightText+marginText),mTextPaint);Copy the code

Is half the height of the total center steps plus the interval, and the following is:

canvas.drawText(tempAverageStep,-halfBottomWidthText,mBottomHeightText+halfHeightText+marginText,mTextPaint);Copy the code

It's the total height of the text below plus half the height of the total center steps plus the interval. Now it looks like this:




img1.PNG

Then is the painting ranking, first or routine:

Rect mNumRect=new Rect(); 
mCenterTextPaint.getTextBounds(ranking,0,ranking.length(),mNumRect); 
int halfNum=(mNumRect.right-mNumRect.left)/2; 
mCenterTextPaint.setTextSize(40); 
canvas.drawText(ranking,-halfNum,radius,mCenterTextPaint);Copy the code

Calculate the size of the ranking text, and then draw the ranking on the center origin x axis is half of the ranking text, and the y axis is the radius. The effect picture is as follows:




img2.PNG

Then draw text at both ends of the ranking, with the following code:

String rankingLeft=getContext().getResources().getString(R.string.ranking_left); 
mTextPaint.getTextBounds(rankingLeft,0,rankingLeft.length(),mNumRect); 
canvas.drawText(rankingLeft,-halfNum-(mNumRect.right-mNumRect.left)/2-20,radius,mTextPaint); 
canvas.drawText(getContext().getResources().getString(R.string.ranking_right),halfNum+10,radius,mTextPaint);Copy the code

Same idea, I won't say. The effect of




img3.PNG

To draw the bottom bar, first use canvas.restore(); Restore the origin to the state of (0,0) with Canvas. Translate (0,heightView*((float)2/3)); Move the origin below the arc, and then you can draw it again.

// Draw the last seven days and the average movement mtextpaint.settextSize (radius/9); canvas.save(); canvas.translate(0,heightView*((float)2/3)); canvas.drawText(getContext().getResources().getString(R.string.nextSevenDay),marginLineChart,0,mTextPaint); Rect mPercentRect=new Rect(); String mPercentText=stringTemplate(R.string.averageStep,averageStep+""); mTextPaint.getTextBounds(mPercentText,0,mPercentText.length(),mPercentRect); canvas.drawText(mPercentText,widthView-marginLineChart-(mPercentRect.right-mPercentRect.left),0,mTextPaint); / / draw a dotted line mDottedLinePath. MoveTo (marginLineChart marginBottomText); mDottedLinePath.lineTo(widthView-marginLineChart,marginBottomText); canvas.drawPath(mDottedLinePath,mDottedLinePaint);Copy the code

The effect is as follows:




img4.PNG

Int lineWidth=(widthView-marginlinechart *2)/8; Calculate the spacing between each point




img5.PNG

if(mListStep.size()>0){ for(int i=mListStep.size(); i>=1; i--){ if(mListStep.get(i-1)! Int startX=marginLineChart+lineWidth*i-radius/23; int endX=marginLineChart+lineWidth*i+radius/23; If (mliststep.get (i-1)>mStandardStop){mtextPaint.setcolor (mBesselCurveColor); Int exceed= mliststep.get (i-1) -mstandardStop; Float standard=(float) (McLerectheight * double.valueof (exceed/ double.valueof (mStandardStop))); mRecf=new RectF(startX,marginBottomText-(standard>mCircleRectHeight? mCircleRectHeight:standard) ,endX,marginBottomText+mCircleRectHeight); Canvas. DrawRoundRect (mRecf, 50, 50, mTextPaint); }else{// mTextpaint.setcolor (besselColorText); // Calculate the size of the column float noStandard=(float)(mCircleRectHeight*Double.valueOf(mListStep.get(i-1)/Double.valueOf(mStandardStop))); mRecf=new RectF(startX,marginBottomText,endX,marginBottomText+( noStandard>mCircleRectHeight? mCircleRectHeight:noStandard)); Canvas. DrawRoundRect (mRecf, 50, 50, mTextPaint); }} // Draw the date at the bottom mTextpaint.setcolor (besselColorText); mCalendar.set(Calendar.DAY_OF_MONTH,mCalendar.get(Calendar.DAY_OF_MONTH)-1); Rect rect =new Rect(); String number=stringTemplate(R.string.day,mCalendar.get(Calendar.DAY_OF_MONTH)+""); mTextPaint.getTextBounds(number,0,number.length(),rect); canvas.drawText(number,(marginLineChart+lineWidth*i)-(rect.right-rect.left)/2,marginBottomText+70,mTextPaint); }}Copy the code

MStandardStop is the standard number, if it's less than mStandardStop it's not the standard number, so the bar graph is going to be below the dotted line, MCircleRectHeight is the high float half of the bar graph standard=(float)(mCircleRectHeight*Double.valueOf(exceed/Double.valueOf(mStandardStop))); NoStandard > McIrectheight? MCircleRectHeight: noStandard when, but with mCircleRectHeight when histogram is greater than the mCircleRectHeight or according to the calculation of numerical value. Int exceed=mListStep. Get (i-1) -mstandardStop; float standard=(float)(mCircleRectHeight*Double.valueOf(exceed/Double.valueOf(mStandardStop))); Exceed is to calculate the exceed part, and then take the exceed part to calculate the specific size, the rest and less than the same, when standard is greater than the maximum McIrectheight is used McIrectheight, otherwise use standard. The following dates are drawn in a loop using Calendar to get the dates of the previous 7 days. The idea is the same as above. The effect is as follows:




img6.PNG

The next step is to draw waves, which are drawn using the method of Bezier curves. If you don't know bezier curves, please refer to this link to write content, which is also my reference to learn Bezier curves. Restore (); restore(); With a float mWavyHeight = heightView * (4/5) (float) + 50; canvas.translate(0,mWavyHeight); Moving this position is to fit.

WavyLinePath.reset(); WavyLinePath.moveTo(-mWaveLength+ mOffset,0); int wHeight=radius/5; for(int i=0; iCopy the code

WavyLinePath. QuadTo is the Bessel curve tuner method, for loop several times to form a wave shape, remember the same WavyLinePath. LineTo (). There are some places down there that I can't draw. The idea is that you set a control point up and you set a control point in a direction to form a sine function. Learn bezier curves. The effect picture at this time:




img7.PNG

Finally, draw the bottom rectangle with the head and text. The most worth talking about is the picture I thought at the beginning of the transfer Url, but this way to do the network aspect of the code work, this will break the function of the single principle of the class, so finally I have to external transmission of a bitmap, bitmap processing to make its rounded corners. The rest is just the painting of the text, the above has said enough, not to speak. Oh, and finally, there's an initial animation effect.

public void startAnimator(){ ValueAnimator mCircleAminator=ValueAnimator.ofFloat(0f,300f); mCircleAminator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCircleNum=(float)animation.getAnimatedValue(); postInvalidate(); }}); ValueAnimator mCenterText=ValueAnimator.ofInt(0,allStop); mCenterText.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mCenterNum=(int)animation.getAnimatedValue(); postInvalidate(); }}); ValueAnimator mWavyAnimator = ValueAnimator.ofInt(0, mWaveLength); mWavyAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mOffset = (int) animation.getAnimatedValue(); postInvalidate(); }}); animSet.setDuration(2000); animSet.playTogether(mCircleAminator,mCenterText,mWavyAnimator); animSet.start(); } // String concatenation public String stringTemplate(int template,String content){return String.format(getContext().getResources().getString(template),content); }Copy the code

It is simply a matter of setting ValueAnimator to generate a value change within a specified period of time, and then calling postInvalidate(). Refresh the View interface to achieve the animation effect.

Finally, to study the source code, only a good look at the source code to learn more.

If it helps you please give me a star or like it.