Android Custom View three: Add "smooth" animations to custom views

Source: Internet
Author: User
Tags message queue

In the second part we implement a simple line chart. This assumes that you have read the previous article. Below we will continue to contribute to this line chart.

I want to add three buttons to the top of the graph so that users can click on different buttons to view different categories of data. For example, users can view walking, running, and cycling. The user points different buttons, we will be different with the motion data displayed in the graph.

We implemented a button click, set different coordinate point data, and then run the app. You will find that, although the method setChartData() has been called, the graph does not change at all. Why is it? Because we didn't notice the line chart redraw. This can be achieved by invoking a invalidate() method. However, such a different category of data switching is very abrupt, if there is a transition of the animation will be much better.

If we want to add a transition animation of different categories of data to a line chart, there are two issues to resolve:
1. We need to change the value of the line chart from old to new step.
2. We need to update the view after the modification of each step when the value of the previous step is modified.

Let's start with the first question. There are many ways to change the value of a point. The simplest one is a simple linear interpolator, supplemented by some advanced interpolator. What we're going to do here is slightly different.

How to move it.

We put the logic mentioned above in a Dynamics class called. An Dynamics object contains the position of a point, the speed of the point, and the target position of the point. Use this object's update() method to update the position and speed of the current point. update()The method looks like this:

fun update(now: Long) {    val dt = Math.min(now - lastTime, 50)    velocity += (targetPosition - position) * springiness    velocity *= 1 - damping    position += velocity * dt / 1000    now}

The first thing we need to do in this method is to calculate the time step, which is basically the time from the last update to the present. and ensure that the longest time is not longer than 50 milliseconds. This is done because the transition delays the animation's update time by avoiding what happens during the animation.

We then update the speed based on the distance from the current point to the target point. At the same time, the animation to achieve a spring effect, so at the time of updating the speed will be considered spring "elastic constant." The speed is reduced by a constant of "damping factor (greater than 0, less than 1)" and finally to 0.

We then use the speed to update the location of the point and record the current update time to facilitate the calculation of the next time step.

In this way, the trajectory of the point is as if it were tied to a spring. This point will rush to the target position and oscillate near that position. If we increase the damping coefficient , the acceleration of the point becomes smaller and if the damping coefficient is large enough, the points will not oscillate at the target position.

This animation and the use of the interpolator are slightly different. The interpolator needs to be set to a duration (duration) when used. The interpolation operation executes within the specified time. However, we only care about the final end time of the animation execution, or under what conditions it ends. Therefore, we add the following method:

fun isAtRest(): Boolean {    val standingStill = Math.abs(velocity) < TOLERANCE    val isAtTarget = targetPosition - position < TOLERANCE    return standingStill && isAtTarget}

Returns trueif the point is already in the target position and the speed is 0. Compared to floating-point numbers is not a good idea, so we detect whether the speed value is close to 0. So TOLERANCE the value is 0.01, which in our case is a reasonable threshold.

Using Dynamics

LineChartViewIt is very easy to update the code before you put Dynamics it in. However, I'm still trying to create a line chart, although the code for this line chart is exactly the same as the code in the previous section. This is mainly to facilitate the reader to view the different chapters of the code. The custom of this heart is called AnimLineChartView the attempt. So, the features of this animation are focused on AnimLineChartView this class.

In the previous section, the last code we drew is this:

var maxValue = getMax(this.points)var path = Path()path.moveTo(getXPos(0), getYPos(this.points[0], maxValue))forin1.1)) {    path.lineTo(getXPos(i), getYPos(points[i], maxValue))}

After use, Dynamics this is the following:

var maxValue = getMax(this.points)var path = Path()path.moveTo(getXPos(0), getYPos(this.points[0in1..(points.count1)) {    path.lineTo(getXPos(i), getYPos(points[i].position, maxValue))}

The main reason is that the point is no longer represented by float an array, but by Dynamics an array of types:

privatevarnull//    private var _points: List<Dynamics>? = null//    var points: List<Dynamics>//        get() = if (_points == null) listOf<Dynamics>() else _points!!//        set(value) {//            _points = value//        }

_dynamicPoints: ArrayList<Dynamics>?Instead of var points: List<Dynamics> . The value of the value of the object that was previously used directly in the float type would need to be replaced Dynamics position .

Start processing animations

What we need to do now is to constantly invoke upate() the method to update _dynamicPoints and trigger the redraw of the view. We use it Runnable to implement the above functions. An runnable example is an executable command that is typically used to perform some task on another thread. But we used it to update the view on the UI thread.

The runnable we're going to use is this:

private  var  animator: Runnable = object : Runnable {override  Fun Run () { var  neednewframe = false  var  now = Animationutils.currentanimationtimemillis () for  (d in  this  @AnimLineChartView. _dynamicpoints!!) {d.update (now) if  (D.isatrest ()) {neednewframe = true } } if  (neednewframe) {postdelayed (this, 20 )} invalidate ()}} 

In the Runnable only way run() , we traverse _dynamicPoints all the points (which are now Dynamics types) and invoke the update() method. If there is a "dot" that does not stop, we set a new animation (Schedulenewframe). Setting up a new animation is through this sentence: postDelayed(this, 20) to achieve. That is, whenever a new animation needs to be set, it is called after a certain period of time Runnable . Finally, the method is called invalidate() to trigger the redraw.

So what if you animator do it again before the next draw? After all, it's more than 15ms before we start the next draw, we can't control it. It is interesting to note that the Runnable object is wrapped in a message and added to MessageQueue (Message Queuing), where the message queue is in the UI thread Looper . invalidate()method is also the case. The UI thread Looper then distributes the various messages and ensures that the redraw and execution of the Runnable objects is performed sequentially. Essentially, in the UI thread, the Looper sequential distribution executes all Message , so each Message object is executed in a different order from the time of the post.

Dynamicsand Runnable The combination is a very good choice for dealing with animations. It's easy to animate custom views that were previously animated. I always start with the drawing and interacting code, add Dynamic properties, and Runnable animate the view.

Look at the setChartData() method:

Fun Setchartdata (newpoints:list<float>) {varnow = Animationutils.currentanimationtimemillis ()if( This. _dynamicpoints = =NULL|| This. _dynamicpoints?. COUNT ()! = Newpoints.count ()) { This. _dynamicpoints =NULL         This. _dynamicpoints = arraylist<dynamics> () for(I:intinch 0.. (Newpoints.count ()-1)) {varDynamicpoint = Dynamics ( -F0.30f) dynamicpoint.setposition (Newpoints[i], now) dynamicpoint.settargetposition (Newpoints[i], now) This. _dynamicpoints?. Add (Dynamicpoint)} invalidate ()}Else{ for(I:intinch 0.. (Newpoints.count ()-1)) { This. _dynamicpoints?.Get(i)?. Settargetposition (Newpoints[i], now) removecallbacks (animator) post (Animator)}}}

There are two situations in which we need to deal with:
1. If we do not have the data before, or the previous data has expired (and the current number of new data is different). At this point we'll create a new Dynamics array and initialize them. We position specify the value as the Y value of the point and velocity specify 0 (the default). We then targetPosition specify the same value. The last Call invalidate() method triggers the redraw.
2. Another situation is that we already have some data. All we need to do is targetPosition replace the new value and start the animation. We post(r: Runnable) can start the animation by invoking the method. However, the animation may already be running, so remove the runnable that might have been added before post a runnable animation. This is also easy to debug some. The only value modified in this method is targetPosition . The current position update() will not change until the method is called.

The results are as follows:

Smooth as silk

There is one more thing to deal with, and that is the figure is too angular. We substituted the method of drawing the line chart path.lineTo(x, y) cublicTo() . This will draw from one point to another using Bezier curves. Of course, we also need to calculate the coordinates of the other two control points required by the Bezier curve.

How the control point coordinates are calculated. The main calculation is the control point of the current point and the next point. So assuming the current point is the I point, the next point of I is the (i+i) point, and the first point of I is the (i-1) point. This is easy to understand. At the time of calculation, the control point of I points is the x+ of the I point (X-Point (i-1) of the Point (i+1)) * The smooth constant, the Y value is similar. Point (I+i) control points are: X of Point (i+1)-(X of Point (i+2)) * Smooth constant. The Y-value of the point (i+1) control points is similarly available.

Let's go back to the animation section again, assuming you have an app with a button and a picture. After you click the button, the image will blur until it disappears (fade out). Then click the button picture in the Blur to full display (fade in). This can be achieved entirely using Alpha animation . But what happens if you click the button first to make the picture fade in, and then click the button fade out without waiting for the animation to execute completely? This image will be displayed immediately alpha=1 and then executed fade out animation.

Then look at our custom Line chart animation, arbitrarily switch between different categories, the individual data lines will not suddenly change, but very smooth animation into the next category of data.

Stay tuned to my next episode!

Android Custom View three: Add "smooth" animations to custom views

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.