Android Imitates Huawei Weather Drawing Dial

Posted by nc_brox on Sun, 07 Jul 2019 00:50:54 +0200

Design sketch


You can see that this custom control combines color gradient, dynamic drawing scale and dynamic water polo effect. Next, let's see how this effect can be achieved step by step.

Start customizing controls

Like many custom controls, you need to base a View or a ViewGroup.
I chose View here, as follows:

public class HuaWeiView extends View {

    /**
     * Used to initialize brushes, etc.
     * @param context
     * @param attrs
     */
    public HuaWeiView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * Used to measure restricted view square
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    /**
     * Realize various rendering functions
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }
}

The construction method is used in the layout.
onMeasure() method is used to measure and limit view size
onDraw() method is used for specific rendering functions
For more information, please click:
Construction method
onMeasure()
MeasureSpec
onDraw()
After understanding the functions of the above methods, let's see how to use them.

1 Restrict View to a square using onMeasure() method

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

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width=MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        //A rectangle with a minimum value of a square
        len=Math.min(width,height);
        //Set the height and width of the measurement (must be invoked, otherwise no effect)
        setMeasuredDimension(len,len);
    }

MeasureSpec is used to get the width and height of user settings, and then the minimum value is taken out and set to our view, so that we can make a rectangle.
Now use in layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@color/colorPrimary"
    android:padding="20dp"
    tools:context="com.example.huaweiview.MainActivity">

    <com.example.huaweiview.HuaWeiView
        android:layout_gravity="center"
        android:background="@color/colorAccent"
        android:layout_width="200dp"
        android:layout_height="300dp"
        />

</LinearLayout>


The background of the parent layout is blue, the background of the control is pink, and the width and height of the control are different, but the display effect of the control is still a square, and the value is small. Our onMeasure() came into effect
Next is how to determine a circular area.

2onDraw() Draws a circular region

Before drawing, we need to understand the coordinate system in Android.

We all know that the top left corner of the screen is the origin of coordinates, the right is the X-axis, and the bottom is the Y-axis. Actually, the mobile page is the display interface of activity and also a View. Can we say that all Views have their own coordinate system when drawing graphics?
That is, each View has its own coordinate system, such as the custom View now:

Now we need to draw an arc in our custom view, so the radius of the arc is half the length of our custom view, that is:
radius=len/2;
So the coordinates of the center of the circle are just radius.

Next, start drawing.

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //The Method of Drawing Arc
        canvas.drawArc(oval, startAngle, sweepAngle, useCenter,paint);
    }

Introduce the method of drawing circular arc.

  • The parameter oval is a RectF object as a rectangle.
  • The parameter two startAngle is the starting angle of the arc.
  • Parametric 3 sweepAngle is the angle of arc (sweep angle)
  • The parameter 4 useCenter is a boolean value for a circle, a circle for true and a secant for false.
  • Parametric 5 paint is a brush object
    That is to say, as long as a rectangle is determined, an arc can be drawn by determining the angle at which it started and went (which you can test with a drawing board).

The next step is to initialize these parameters.

Initialize rectangle

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        //A rectangle with a minimum value of a square
        len = Math.min(width, height);
        //Instantiate rectangle
        oval=new RectF(0,0,len,len);
        //Set the height and width of the measurement (must be invoked, otherwise no effect)
        setMeasuredDimension(len, len);
    }

Drawing a rectangle requires determining the coordinates of the upper left corner and the lower right corner (which can be tested by the drawing board). The origin of the analytical coordinates above is the upper left corner of our view, and the coordinates of the lower right corner are of course len.

Next is the initialization start and pass angle.

private float startAngle=120;
private float sweepAngle=300;

It needs to be made clear that the downward axis is the Y axis, which is just the opposite of what you learned in school. That is to say, 90 degrees are below and - 90 degrees are above.

Initialization Brush

 public HuaWeiView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        paint =new Paint();
        //setpc
        paint.setColor(Color.WHITE);
        //Setting Brush Anti-aliasing
        paint.setAntiAlias(true);
        //Let the drawing be hollow (not filled)
        paint.setStyle(Paint.Style.STROKE);
    }
useCenter=false

It's not easy to get here, but it's useless to draw only an arc. What I want is a scale line. There's no way to draw a scale line in canvas. At this time, we need to write a scale line by ourselves.
By looking at the pictures, we can see that all the lines draw a straight line from the point on the arc to a certain direction. So how to determine these two points, we need to do two things:

moving coordinate system

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //The Method of Drawing Arc
        canvas.drawArc(oval, startAngle, sweepAngle, useCenter,paint);
        //The Method of Drawing Scale Line
        drawViewLine(canvas);
    }

    private void drawViewLine(Canvas canvas) {
        //Save the contents of canvas before you save them
        canvas.save();
        //Mobile canvas(X-axis moving distance, Y-axis moving distance)
        canvas.translate(radius,radius);
        //Restore state after completion of operation
        canvas.restore();
    }

We wrote a method of drawing scale lines and called it in onDraw(). Before moving the coordinate system, we need to save the previous canvas state, and then X and Y axes move the distance of the radius of the arc, respectively, as shown in the following figure:

canvas.translate(radius,radius); the method moves the coordinate system (obtained by actual effect and data search)
canvas.save() and canvas.restore() appear in pairs, as if the flow is closed when it is exhausted.

After the first thing is done, start the second thing, rotating the coordinate system.

It is still difficult to determine the coordinates of the arc point and the other point only by moving the coordinate system.
If only these two points were on the coordinate axis, the following realization would be achieved:

 private void drawViewLine(Canvas canvas) {
        //Save the contents of canvas before you save them
        canvas.save();
        //Mobile canvas(X-axis moving distance, Y-axis moving distance)
        canvas.translate(radius,radius);
        //Rotating coordinate system
        canvas.rotate(30);
        //Restore state after completion of operation
        canvas.restore();
    }

The method of drawing the scale line adds a code of 30 degrees rotation. What about the coordinate system after rotation?

Because the starting point is 30 degrees apart from 90 degrees, after rotation, the starting point just falls on the Y axis, so the coordinates of this point are well determined, that is (0,radius); if we find a point on the Y axis can not draw a scale, then what is its coordinates? Yes, it should be (0,radius-y), because we want to internalize the scale line, so we subtract a value. Let's try it quickly. The code is as follows:

 private void drawViewLine(Canvas canvas) {
        //Save the contents of canvas before you save them
        canvas.save();
        //Mobile canvas(X-axis moving distance, Y-axis moving distance)
        canvas.translate(radius,radius);
        //Rotating coordinate system
        canvas.rotate(30);
        Paint linePatin=new Paint();
        //setpc
        linePatin.setColor(Color.WHITE);
        //line width
        linePatin.setStrokeWidth(2);
        //Setting Brush Anti-aliasing
        linePatin.setAntiAlias(true);
        //Draw a scale line
        canvas.drawLine(0,radius,0,radius-40,linePatin);
        //Restore state after completion of operation
        canvas.restore();
    }

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

Of course, these points are all obtained by rotating the coordinate system 30 degrees. Here we draw a line. If we draw more than one line, or if we just thought that it would be better to rotate it at a small angle and draw a straight line every time, then how many degrees of rotation, such as here: sweepAngle=300 for the total sweeped angle; For bar scale, rotateAngle=sweepAngle/100 is required to rotate every time. The code is as follows:

 private void drawViewLine(Canvas canvas) {
        //Save the contents of canvas before you save them
        canvas.save();
        //Mobile canvas(X-axis moving distance, Y-axis moving distance)
        canvas.translate(radius,radius);
        //Rotating coordinate system
        canvas.rotate(30);
        Paint linePatin=new Paint();
        //setpc
        linePatin.setColor(Color.WHITE);
        //line width
        linePatin.setStrokeWidth(2);
        //Setting Brush Anti-aliasing
        linePatin.setAntiAlias(true);
        //Determine the angle of each rotation
        float rotateAngle=sweepAngle/99;
        for(int i=0;i<100;i++){
            //Draw a scale line
            canvas.drawLine(0,radius,0,radius-40,linePatin);
            canvas.rotate(rotateAngle);
        }

        //Restore state after completion of operation
        canvas.restore();
    }

For 100 scales, it takes 101 cycles to draw lines (see your watch). After drawing the lines, it rotates. Cycle in turn, as shown in Figure 1

After such a long time, we have finished the dial. Next, we need to determine what color is displayed from different angles. First, we need to determine the range of targetAngle to draw.

Draw colored parts

private void drawViewLine(Canvas canvas) {
        //Save the contents of canvas before you save them
        canvas.save();
        //Mobile canvas(X-axis moving distance, Y-axis moving distance)
        canvas.translate(radius,radius);
        //Rotating coordinate system
        canvas.rotate(30);
        Paint linePatin=new Paint();
        //setpc
        linePatin.setColor(Color.WHITE);
        //line width
        linePatin.setStrokeWidth(2);
        //Setting Brush Anti-aliasing
        linePatin.setAntiAlias(true);
        //Determine the angle of each rotation
        float rotateAngle=sweepAngle/100;
        //A brush that draws colored parts.
        Paint targetLinePatin=new Paint();
        targetLinePatin.setColor(Color.GREEN);
        targetLinePatin.setStrokeWidth(2);
        targetLinePatin.setAntiAlias(true);
        //Record the range of colored parts that have been drawn
        float hasDraw=0;
        for(int i=0;i<=100;i++){
            if(hasDraw<=targetAngle&&targetAngle!=0){//When the colored part needs to be drawn
                //Draw a scale line
                canvas.drawLine(0,radius,0,radius-40,targetLinePatin);
            }else {//No need to draw colored parts
                //Draw a scale line
                canvas.drawLine(0,radius,0,radius-40,linePatin);
            }
            //Cumulative plotted parts
            hasDraw+=rotateAngle;
            //rotate
            canvas.rotate(rotateAngle);
        }

        //Restore state after completion of operation
        canvas.restore();
    }

We need to keep track of the effective parts we have drawn, and the other parts are painted white.

Color gradient according to angle ratio

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

for(int i=0;i<=100;i++){
            if(hasDraw<=targetAngle&&targetAngle!=0){//When the colored part needs to be drawn
                //Calculate the proportion that has been drawn
                float percent=hasDraw/sweepAngle;
                int red= 255-(int) (255*percent);
                int green= (int) (255*percent);
                targetLinePatin.setARGB(255,red,green,0);
                //Draw a scale line
                canvas.drawLine(0,radius,0,radius-40,targetLinePatin);
            }else {//No need to draw colored parts
                //Draw a scale line
                canvas.drawLine(0,radius,0,radius-40,linePatin);
            }
            hasDraw+=rotateAngle;
            canvas.rotate(rotateAngle);
        }

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

Realizing Dynamic Display

First think about its movement, divided into forward state and backward state, if it is moving (a complete backward and forward useless end), it can not start the next movement, need two parameters, state and is Running.

//Judge whether it's moving or not
    private boolean isRunning;
    //Judging whether it is a backward state or a forward state
    private int state = 1;
    public void changeAngle(final float trueAngle) {
        if (isRunning){//If the action returns directly
            return;
        }
        final Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                switch (state) {
                    case 1://Backward state
                    isRunning=true;
                        targetAngle -= 3;
                        if (targetAngle <= 0) {//If you go back to 0
                            targetAngle = 0;
                            //Change to Progressive State
                            state = 2;
                        }
                        break;
                    case 2://Progressive state
                        targetAngle += 3;
                        if (targetAngle >= trueAngle) {//If increased to a specified angle
                            targetAngle = trueAngle;
                            //Change to a retrogressive state
                            state = 1;
                            isRunning=false;
                            //End the campaign
                            timer.cancel();
                        }
                        break;
                    default:
                        break;
                }
                //Rendering (methods used in sub-threads)
                postInvalidate();
            }
        }, 500, 30);
    }

Take advantage of the time task to execute the run method once every 30 milliseconds, redraw the picture each time, and then call the method in activity.

 HuaWeiView hwv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        hwv= (HuaWeiView) findViewById(R.id.hwv);
        hwv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Click on the event to invoke the active method
                hwv.changeAngle(200);
            }
        });

    }

See here, I believe you have a good understanding of the coordinate system and angle dynamic changes, as well as the drawing of the dial, more verification will help understand.

Next, we need to achieve background dynamic gradient.

Think about where gradients are used in our view. Yes, when drawing the colored part, if only we could transfer the value of the color gradient to the activity continuously. Now we need to use the interface to transfer the value to achieve this function.

  • Preferably declare an internal interface in a custom View:
  private OnAngleColorListener onAngleColorListener;

    public void setOnAngleColorListener(OnAngleColorListener onAngleColorListener) {
        this.onAngleColorListener = onAngleColorListener;
    }


    public interface OnAngleColorListener{
       void colorListener(int red,int green);
    }

We declare an internal interface in our custom View and a global interface object to 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 when we draw the color scale?

for (int i = 0; i <= 100; i++) {
            if (hasDraw <= targetAngle && targetAngle != 0) {//When the colored part needs to be drawn
                //Calculate the proportion that has been drawn
                float percent = hasDraw / sweepAngle;
                int red = 255 - (int) (255 * percent);
                int green = (int) (255 * percent);
                //Implement interface callback and transfer color values
                if (onAngleColorListener!=null){
                    onAngleColorListener.colorListener(red,green);
                }
                targetLinePatin.setARGB(255, red, green, 0);
                //Draw a scale line
                canvas.drawLine(0, radius, 0, radius - 40, targetLinePatin);
            } else {//No need to draw colored parts
                //Draw a scale line
                canvas.drawLine(0, radius, 0, radius - 40, linePatin);
            }

We implemented the interface callback while drawing, and then we implemented the interface in activity.

public class MainActivity extends AppCompatActivity {

    HuaWeiView hwv;
    LinearLayout ll_parent;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        hwv= (HuaWeiView) findViewById(R.id.hwv);
        //Instance parent layout
        ll_parent= (LinearLayout) findViewById(R.id.ll_parent);
        hwv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Click on the event to invoke the active method
                hwv.changeAngle(200);
            }
        });
        //Setting Angle Color Change Monitor
        hwv.setOnAngleColorListener(new HuaWeiView.OnAngleColorListener() {
            @Override
            public void colorListener(int red, int green) {
                Color color=new Color();
                //Converting RGB values to int types through Color objects
                int backColor=color.argb(100,red,green,0);
                //Parent Layout Settings Background
                ll_parent.setBackgroundColor(backColor);
            }
        });

    }
}

Layout an id to the parent and instantiate it. Set an angle Color change monitor for our custom control to get the value passed in the callback, then use the Color object to convert the RGB value to int value, and then set it to the parent layout background, where the background is slightly transparent. Design sketch:

Does it feel cool to be here? In fact, the function has been almost achieved. Next is to draw the contents.

Drawing Text

Of course, it's also possible not to draw text. You can add textview to the layout directly. Well, let's start with the process of drawing. There is a small circle in the inside of the dial, and then these words are in the small circle. To draw a small circle, you just need to make its radius smaller and OK.

/**
     * The method of drawing small circles and text, the color of small circles is also gradient.
     * @param canvas
     */
    private void drawScoreText(Canvas canvas) {
        //Draw a small circle first.
        Paint smallPaint = new Paint();
        smallPaint.setARGB(100,red,green,0);
        // Draw a small circle to specify the coordinates, radius and brush of the center of the circle.
        int smallRadius=radius-60;
        canvas.drawCircle(radius, radius, radius - 60, smallPaint);
        //Drawing text
        Paint textPaint=new Paint();
        //Setting text alignment
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setColor(Color.WHITE);
        textPaint.setTextSize(smallRadius/2);
        //score needs to be computed
        canvas.drawText(""+score,radius,radius,textPaint);
        //Draw the score, above the right of the score
        textPaint.setTextSize(smallRadius/6);
        canvas.drawText("branch",radius+smallRadius/2,radius-smallRadius/4,textPaint);
        //Draw click optimization below the score
        textPaint.setTextSize(smallRadius/6);
        canvas.drawText("Click optimization",radius,radius+smallRadius/2,textPaint);

    }

In this paper, the previous gradient red and green are taken as global variables. First, a small circle is drawn and the brush color is gradient. Then draw the text score score score that needs to be computed to

//Calculated scores
score=(int)(targetAngle/sweepAngle*100);
//Rendering (methods used in sub-threads)
postInvalidate();

In the time task, the score is calculated before each drawing, then a fixed value score is drawn on the upper right, and then a fixed content click on the optimization below (the coordinates at this time have returned to the original shape).

So far, the function has been almost written, and there is a water wave acceleration ball effect, write in the next blog.
Finally, for the bottom of the principle, I also need to learn, where there are mistakes, welcome to correct, thank you. The project has been uploaded to github
github Click Download

Topics: Android Mobile github xml