Android imitates Huawei's weather drawing dial

Android imitates Huawei's weather drawing dial

[[191018]]

Rendering

You can see that this custom control combines color gradient, dynamic drawing scale, and dynamic water ball effect. Next, let's take a look at how this effect is achieved step by step.

Start customizing controls

Like many custom control methods, you need to base it on some kind of View or some kind of ViewGroup

I choose View here, as shown below:

  1. public class HuaWeiView extends View {
  2.  
  3. /**
  4. * Used to initialize brushes, etc.
  5. * @param context
  6. * @param attrs
  7. */
  8. public HuaWeiView(Context context, @Nullable AttributeSet attrs) {
  9. super(context, attrs);
  10. }
  11.  
  12. /**
  13. * Used to measure and limit the view to a square
  14. * @param widthMeasureSpec
  15. * @param heightMeasureSpec
  16. */
  17. @Override
  18. protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {
  19. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  20. }
  21.  
  22. /**
  23. * Implement various drawing functions
  24. * @param canvas
  25. */
  26. @Override
  27. protected void onDraw(Canvas canvas) {
  28. super.onDraw(canvas);
  29. }
  30. }

The construction method is used in layout.

The onMeasure() method is used to measure and limit the view size

The onDraw() method is used to perform specific drawing functions

1. Use the onMeasure() method to limit the View to a square

Only when a rectangle is determined can an ellipse be drawn. If the rectangle is a square, the ellipse will become a circle.

  1. @Override
  2. protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {
  3. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  4. int width=MeasureSpec.getSize(widthMeasureSpec);
  5. int height = MeasureSpec.getSize(heightMeasureSpec);
  6. //The minimum value is the length of the square
  7. len= Math.min (width,height);
  8. //Set the measured height and width (must be called, otherwise it will have no effect)
  9. setMeasuredDimension(len,len);
  10. }

We use MeasureSpec to get the width and height set by the user, and then take the minimum value and set it to our view, so that we can make a rectangle.

Now use it in the layout:

  1. <?xml version= "1.0" encoding= "utf-8" ?>
  2. <LinearLayout xmlns:android= "http://schemas.android.com/apk/res/android"  
  3. xmlns:app= "http://schemas.android.com/apk/res-auto"  
  4. xmlns:tools= "http://schemas.android.com/tools"  
  5. android:layout_width= "match_parent"  
  6. android:layout_height= "match_parent"  
  7. android:orientation= "vertical"  
  8. android:background= "@color/colorPrimary"  
  9. android:padding= "20dp"  
  10. tools:context= "com.example.huaweiview.MainActivity" >
  11.  
  12. <com.example.huaweiview.HuaWeiView
  13. android:layout_gravity= "center"  
  14. android:background= "@color/colorAccent"  
  15. android:layout_width= "200dp"  
  16. android:layout_height= "300dp"  
  17. />
  18.  
  19. </LinearLayout>

The background of the parent layout is blue, the background of the control is pink, and the width and height are set differently, but the display effect of the control is still a square, and the smaller value is used as the standard. Our onMeasure() is effective

The next step is how to determine a circular area.

2. onDraw() draws a circular area

Before drawing, we need to have an understanding of the coordinate system in Android

We all know that the upper left corner of the mobile phone screen is the coordinate origin, the right is the positive X axis, and the bottom is the positive Y axis. In fact, the mobile page is the display interface of the activity, and it is also a View. Can we say that all Views have their own coordinate system when drawing graphics? (My personal opinion...)

That is, each View has its own coordinate system, such as the current custom View:

Now we need to draw an arc in our custom view, then the radius of this arc is half the length of our custom view, that is:

radius=len/2;

Then the coordinates of the center of the circle are exactly (radius, radius)

Next, start drawing

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4. //How to draw an arc
  5. canvas.drawArc(oval, startAngle, sweepAngle, useCenter,paint);
  6. }

Here is how to draw an arc:

  • Parameter oval is a RectF object for a rectangle
  • Parameter 2 startAngle is the starting angle of the arc
  • Parameter three: sweepAngle is the angle of the arc (sweep angle)
  • Parameter 4 useCenter is a boolean value for the arc. When it is true, an arc is drawn. When it is false, a cutting arc is drawn.
  • Parameter five paint is a brush object

That is to say, as long as a rectangle is determined, an arc can be drawn by determining its starting and passing angles (you can use the drawing board to test this)

Next is to initialize these parameters

Initialize the rectangle

  1. @Override
  2. protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {
  3. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  4. int width = MeasureSpec.getSize(widthMeasureSpec);
  5. int height = MeasureSpec.getSize(heightMeasureSpec);
  6. //The minimum value is the length of the square
  7. len = Math. min (width, height);
  8. //Instantiate the rectangle
  9. oval=new RectF(0,0,len,len);
  10. //Set the measured height and width (must be called, otherwise it will have no effect)
  11. setMeasuredDimension(len, len);
  12. }

To draw a rectangle, you need to determine the coordinates of the upper left corner and the lower right corner (which can be tested through the drawing board). According to the above analysis, the origin of the coordinates is the upper left corner of our view, and the coordinates of the lower right corner are of course len.

Next is to initialize the starting and passing angles

  1. private float startAngle=120;
  2.  
  3. private float sweepAngle=300;

You need to understand that the positive axis of the Y axis is downward, which is exactly the opposite of what you learned in school, that is, 90 degrees is at the bottom and -90 degrees is at the top.

Initialize the brush

  1. public HuaWeiView(Context context, @Nullable AttributeSet attrs) {
  2. super(context, attrs);
  3. paint = new Paint();
  4. //Set the brush color
  5. paint.setColor(Color.WHITE);
  6. //Set the brush anti-aliasing
  7. paint.setAntiAlias( true );
  8. // Make the drawn shape hollow (not filled)
  9. paint.setStyle(Paint.Style.STROKE);
  10. }useCenter = false  

It is not easy to get here, but I find that it is useless to just draw an arc. What I want is a scale line, and there is no method to draw scale lines in canvas. At this time, we need to write a method to draw scale lines ourselves.

By observing the picture, we can see that all lines are drawn from a point on the arc to a certain direction. So how to determine these two points? We need to do two things:

Moving coordinate system

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4. //How to draw an arc
  5. canvas.drawArc(oval, startAngle, sweepAngle, useCenter,paint);
  6. //How to draw tick marks
  7. drawViewLine(canvas);
  8. }
  9.  
  10. private void drawViewLine(Canvas canvas) {
  11. //Save the previous canvas content first
  12. canvas.save();
  13. //Move canvas (X axis moving distance, Y axis moving distance)
  14. canvas.translate(radius,radius);
  15. //Restore the state after the operation is completed
  16. canvas.restore();
  17. }

We wrote a method to draw tick marks and called it in the onDraw() method. Before moving the coordinate system, we need to save the previous canvas state, and then move the X and Y axes by the radius of the arc, as shown below:

canvas.translate(radius,radius); method moves the coordinate system (obtained through actual effects and data search)

canvas.save() and canvas.restore() should appear in pairs, just like the stream needs to be closed after use.

After the first task is completed, start the second task, rotating the coordinate system

It is still difficult to determine the coordinates of the arc point and the coordinates of another point only by moving the coordinate system. It would be great if both points were on the coordinate axis. The following is implemented:

  1. private void drawViewLine(Canvas canvas) {
  2. //Save the previous canvas content first
  3. canvas.save();
  4. //Move canvas (X axis moving distance, Y axis moving distance)
  5. canvas.translate(radius,radius);
  6. //Rotate coordinate system
  7. canvas.rotate(30);
  8. //Restore the state after the operation is completed
  9. canvas.restore();   

The method of drawing tick marks adds a code for rotating 30 degrees. What should the coordinate system look like after the rotation?

Because the starting point is 30 degrees away from 90 degrees, after the rotation, the starting point just falls on the Y axis, so the coordinates of this point are easy to determine, that's right, (0, radius); if we find a point on the Y axis, can't we draw a tick mark, then what are its coordinates? Yes, it should be (0, radius-y), because we want to internalize the tick mark, so we subtract a value, try it quickly, the code is as follows:

  1. private void drawViewLine(Canvas canvas) {
  2. //Save the previous canvas content first
  3. canvas.save();
  4. //Move canvas (X axis moving distance, Y axis moving distance)
  5. canvas.translate(radius,radius);
  6. //Rotate coordinate system
  7. canvas.rotate(30);
  8. Paint linePatin=new Paint();
  9. //Set the brush color
  10. linePatin.setColor(Color.WHITE);
  11. //Line width
  12. linePatin.setStrokeWidth(2);
  13. //Set the brush anti-aliasing
  14. linePatin.setAntiAlias( true );
  15. //Draw a tick mark
  16. canvas.drawLine(0,radius,0,radius-40,linePatin);
  17. //Restore the state after the operation is completed
  18. canvas.restore();
  19. }

According to the coordinates of the two points obtained, draw a white line, as shown in the figure:

Of course, these points are obtained by rotating the coordinate system by 30 degrees after the movement. A line is drawn here. If you want to draw more lines, you can still use the previous idea to rotate it by a small angle each time and then draw a straight line. So how many degrees should it be rotated? For example, here: the total swept angle sweepAngle=300; 100 scales are needed, so the angle to be rotated each time rotateAngle=sweepAngle/100, the specific code is as follows:

  1. private void drawViewLine(Canvas canvas) {
  2. //Save the previous canvas content first
  3. canvas.save();
  4. //Move canvas (X axis moving distance, Y axis moving distance)
  5. canvas.translate(radius,radius);
  6. //Rotate coordinate system
  7. canvas.rotate(30);
  8. Paint linePatin=new Paint();
  9. //Set the brush color
  10. linePatin.setColor(Color.WHITE);
  11. //Line width
  12. linePatin.setStrokeWidth(2);
  13. //Set the brush anti-aliasing
  14. linePatin.setAntiAlias( true );
  15. //Determine the angle of each rotation
  16. float rotateAngle=sweepAngle/99;
  17. for ( int i=0;i<100;i++){
  18. //Draw a tick mark
  19. canvas.drawLine(0,radius,0,radius-40,linePatin);
  20. canvas.rotate(rotateAngle);
  21. }
  22.  
  23. //Restore state after the operation is completed
  24. canvas.restore();
  25. }

100 scales require 101 cycles of drawing lines (please check your watch), and the line will rotate after drawing. Cycle in sequence, as shown in the figure

After so much time, the dial is finally completed. The next step is to determine what color to display at different angles. First, we need to determine the range to be drawn, targetAngle:

Draw the colored part

  1. private void drawViewLine(Canvas canvas) {
  2. //Save the previous canvas content first
  3. canvas.save();
  4. //Move canvas (X axis moving distance, Y axis moving distance)
  5. canvas.translate(radius,radius);
  6. //Rotate coordinate system
  7. canvas.rotate(30);
  8. Paint linePatin=new Paint();
  9. //Set the brush color
  10. linePatin.setColor(Color.WHITE);
  11. //Line width
  12. linePatin.setStrokeWidth(2);
  13. //Set the brush anti-aliasing
  14. linePatin.setAntiAlias( true );
  15. //Determine the angle of each rotation
  16. float rotateAngle=sweepAngle/100;
  17. //Draw the brush for the colored part
  18. Paint targetLinePatin=new Paint();
  19. targetLinePatin.setColor(Color.GREEN);
  20. targetLinePatin.setStrokeWidth(2);
  21. targetLinePatin.setAntiAlias( true );
  22. //Record the range of the colored part that has been drawn
  23. float hasDraw=0;
  24. for ( int i=0;i<=100;i++){
  25. if(hasDraw<=targetAngle&&targetAngle!=0){//When you need to draw the colored part
  26. //Draw a tick mark
  27. canvas.drawLine(0,radius,0,radius-40,targetLinePatin);
  28. } else {//No need to draw the colored part
  29. //Draw a tick mark
  30. canvas.drawLine(0,radius,0,radius-40,linePatin);
  31. }
  32. //The accumulated drawn parts
  33. hasDraw+=rotateAngle;
  34. //Rotation
  35. canvas.rotate(rotateAngle);
  36. }
  37.  
  38. //Restore state after the operation is completed
  39. canvas.restore();
  40. }

We need to constantly record the valid parts that have been drawn, and draw the rest in white.

Color gradient according to the angle ratio

It is necessary to calculate the ratio of the angle that has been drawn to the total angle (300)

  1. for ( int i=0;i<=100;i++){
  2. if(hasDraw<=targetAngle&&targetAngle!=0){//When you need to draw the colored part
  3. //Calculate the proportion of the drawing
  4. float percent=hasDraw/sweepAngle;
  5. int red= 255-( int ) (255*percent);
  6. int green= ( int ) (255*percent);
  7. targetLinePatin.setARGB(255,red,green,0);
  8. //Draw a tick mark
  9. canvas.drawLine(0,radius,0,radius-40,targetLinePatin);
  10. } else {//No need to draw the colored part
  11. //Draw a tick mark
  12. canvas.drawLine(0,radius,0,radius-40,linePatin);
  13. }
  14. hasDraw+=rotateAngle;
  15. canvas.rotate(rotateAngle);
  16. }

Only when drawing the colored part, the three elements are used to achieve the gradient. The lower the proportion, the greater the red value, anyway, the greater the green value.

Implementing dynamic display

First think about its movement, which is divided into forward state and backward state. If it is moving (a complete backward and forward movement is not completed), it cannot start the next movement. Two parameters are required, state and isRunning

  1. // Determine if it is moving
  2. private boolean isRunning;
  3. //Judge whether it is a backward or forward state
  4. private int state = 1;
  5. public void changeAngle(final float trueAngle) {
  6. if (isRunning){//If it is running, return directly
  7. return ;
  8. }
  9. final Timer timer = new Timer();
  10. timer.schedule(new TimerTask() {
  11. @Override
  12. public void run() {
  13. switch (state) {
  14. Case 1://Back state
  15. isRunning= true ;
  16. targetAngle -= 3;
  17. if (targetAngle <= 0) {//If it falls back to 0
  18. targetAngle = 0;
  19. //Change to forward state
  20. state = 2;
  21. }
  22. break;
  23. Case 2: //Forward state
  24. targetAngle += 3;
  25. if (targetAngle >= trueAngle) {//If it increases to the specified angle
  26. targetAngle = trueAngle;
  27. //Change to back state
  28. state = 1;
  29. isRunning= false ;
  30. //End this exercise
  31. timer.cancel();
  32. }
  33. break;
  34. default :
  35. break;
  36. }
  37. //Redraw (method used in child thread)
  38. postInvalidate();
  39. }
  40. }, 500, 30);
  41. }

Use the time task to execute the run method every 30 milliseconds, redraw the picture each time, and then call this method in the activity

  1. HuaWeiView hwv;
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_main);
  6. hwv= (HuaWeiView) findViewById(R.id.hwv);
  7. hwv.setOnClickListener(new View .OnClickListener() {
  8. @Override
  9. public void onClick( View v) {
  10. //In the click event, call the dynamic method
  11. hwv.changeAngle(200);
  12. }
  13. });
  14.  
  15. }

After reading this, I believe you have a good understanding of the coordinate system, the dynamic changes of angles, and the drawing of the dial. More verification will help with your understanding.

Next, we need to implement dynamic background gradient

Think about where the gradient is used in our view? Yes, when drawing the colored part, it would be great if we could continuously pass the color gradient value to the activity. Next, we will use the interface to pass the value to achieve this function:

  • ***Declare an internal interface in the custom View:
  1. private OnAngleColorListener onAngleColorListener;
  2.  
  3. public void setOnAngleColorListener(OnAngleColorListener onAngleColorListener) {
  4. this.onAngleColorListener = onAngleColorListener;
  5. }
  6.  
  7. public interface OnAngleColorListener{
  8. void colorListener( int red, int green);
  9. }

We declare an internal interface in the custom View, declare a global interface object, and provide a set method

There is a method in the interface to get the color value

The next step is to call this method in the right place, so where is it? It is called when we draw the color scale:

  1. for ( int i = 0; i <= 100; i++) {
  2. if (hasDraw <= targetAngle && targetAngle != 0) {//When you need to draw the colored part
  3. //Calculate the proportion of the drawing
  4. float percent = hasDraw / sweepAngle;
  5. int red = 255 - ( int ) (255 * percent);
  6. int green = ( int ) (255 * percent);
  7. //Implement interface callback and pass color value
  8. if (onAngleColorListener!= null ){
  9. onAngleColorListener.colorListener(red,green);
  10. }
  11. targetLinePatin.setARGB(255, red, green, 0);
  12. //Draw a tick mark
  13. canvas.drawLine(0, radius, 0, radius - 40, targetLinePatin);
  14. } else {//No need to draw the colored part
  15. //Draw a tick mark
  16. canvas.drawLine(0, radius, 0, radius - 40, linePatin);
  17. }

We implemented the interface callback when drawing, and then implemented the interface in the activity

  1. public class MainActivity extends AppCompatActivity {
  2.  
  3. HuaWeiView hwv;
  4. LinearLayout ll_parent;
  5. @Override
  6. protected void onCreate(Bundle savedInstanceState) {
  7. super.onCreate(savedInstanceState);
  8. setContentView(R.layout.activity_main);
  9. hwv= (HuaWeiView) findViewById(R.id.hwv);
  10. //Instance parent layout
  11. ll_parent= (LinearLayout) findViewById(R.id.ll_parent);
  12. hwv.setOnClickListener(new View .OnClickListener() {
  13. @Override
  14. public void onClick( View v) {
  15. //In the click event, call the dynamic method
  16. hwv.changeAngle(200);
  17. }
  18. });
  19. //Set the angle color change monitor
  20. hwv.setOnAngleColorListener(new HuaWeiView.OnAngleColorListener() {
  21. @Override
  22. public void colorListener( int red, int green) {
  23. Color color=new Color();
  24. //Convert RGB value to int type through Color object
  25. int backColor=color.argb(100,red,green,0);
  26. //Set the background of the parent layout
  27. ll_parent.setBackgroundColor(backColor);
  28. }
  29. });
  30.  
  31. }
  32. }

Give the parent layout an id and then instantiate it. Set an angle color change listener for our custom control to get the value passed in the callback, then use the Color object to convert the RGB value to an int value, and then set it to the background of the parent layout. Here the background is slightly transparent. Effect diagram:

Now it feels a lot cooler, in fact, the functions have been almost realized, the next step is to draw the content inside

Draw Text

Of course, you don't need to draw text, you can directly add textview to the layout. Without further ado, let's analyze the drawing process. There is a small circle inside the dial, and the text is inside the small circle. To draw the small circle, you only need to make its radius smaller.

  1. /**
  2. * How to draw a small circle and text. The color of the small circle also changes gradually.
  3. * @param canvas
  4. */
  5. private void drawScoreText(Canvas canvas) {
  6. // Draw a small circle first
  7. Paint smallPaint = new Paint();
  8. smallPaint.setARGB(100,red,green,0);
  9. // Draw a small circle and specify the center coordinates, radius, and brush
  10. int smallRadius=radius-60;
  11. canvas.drawCircle(radius, radius, radius - 60, smallPaint);
  12. //Draw the text
  13. Paint textPaint=new Paint();
  14. //Set the text to center
  15. textPaint.setTextAlign(Paint.Align.CENTER);
  16. textPaint.setColor(Color.WHITE);
  17. textPaint.setTextSize(smallRadius/2);
  18. //score needs to be calculated
  19. canvas.drawText( "" +score,radius,radius,textPaint);
  20. //Draw the points, in the upper right corner of the score
  21. textPaint.setTextSize(smallRadius/6);
  22. canvas.drawText( "分" ,radius+smallRadius/2,radius-smallRadius/4,textPaint);
  23. //Draw click optimization below the score
  24. textPaint.setTextSize(smallRadius/6);
  25. canvas.drawText( "Click to optimize" , radius, radius+smallRadius/2, textPaint);
  26.  
  27. }

Here, the previously gradient red and green are taken as global variables. First, draw a small circle with a gradient brush color. Then draw the text score, which needs to be calculated.

  1. //Calculated score
  2. score=( int )(targetAngle/sweepAngle*100);
  3. //Redraw (method used in child thread)
  4. postInvalidate();

In the time task, calculate the score before each drawing, then draw a fixed value score in the upper right corner, and then click Optimize on the fixed content below (the coordinates at this time have returned to their original appearance)

So far, the functions have been almost written. There is also a water wave acceleration ball effect, which will be written in the next blog.

<<:  Android dynamic proxy and using dynamic proxy to implement ServiceHook

>>:  A brief introduction to MVP's practical exercises to make the code structure simpler~

Recommend

How did we gain 1.5 million new WeChat followers through one event?

When friends talk about Tencent's products, t...

How are those who always do manicures doing now?

Manicure has become an indispensable part of wome...

How to deal with the sluggish growth of TikTok accounts?

User visual fatigue, slow content iteration, and ...

A guide to creating a marketing plan!

This article is dedicated to those who are strugg...

The technical ideals of old programmers

[[133852]] When I was a child, my teacher asked m...

Interpretation of the user incentive system of Station B

This article will start from the perspective of B...

The sales data analysis ideas you must sort out before "Double 11"

The case is this: An e-commerce company that sell...

There is a kind of battery that can keep your mobile phone powered for 100 years

Produced by: Science Popularization China Author:...

New Oriental F2020 General Doctoral English Full Course Baidu Cloud Download

New Oriental F2020 General Doctoral English Full ...