• Non ci sono risultati.

3.5 Ahead-Of-Time Compiling

4.1.3 Animations

Animations are an important part of the User Experience (UX) when building a mobile application because they improve the UI making it more intuitive and polished. Flutter supports developers in building any kind of animation types: indeed, most of the Material widgets already provide standard animation effects in their specifications; nevertheless, Flutter can offer developers full customization.

The Flutter team has realized a very helpful flowchart in order to properly find the best tool for animating a widget. Generally Flutter animations can be classified into two categories: drawing-based animation and code-based animation; while the latter is directly focused on the widget and is still bounded to standard layout primitives (e.g.

columns, rows, color), the former looks like it has been drawn and it usually is the core of gaming characters or transformations that otherwise would be difficult to express in code. Drawing-based animations can be obtained by exploiting third-party tools which help building the animation from a graphic point of view and then exporting it into Flutter: these animations are too complex for the purpose of this project, so they won’t be addressed. Instead, code-based animations are simpler to realize and don’t require any external tool to integrate. These animations are further divided into implicit and explicit animations: implicit animations are based on simply setting a new value for a widget’s property and consequently having Flutter taking care of animating it from the current value to the new value; alternatively, explicit animations must be explicitly told when to start, hence they require an animation controller, and they can do basically the same as the implicit ones at the price of managing the lifecycle of the animation controller (inside a Statef ulW idget). Explicit animations must be used:

• when the animation needs to repeat many times (potentially forever) until a certain condition is true

• when the animation is discontinuous, like a circle repeatedly growing in size from small to large without shrinking back again

Study and development of a participative system for monitoring air pollution data

• when multiple widgets are animating together in a coordinated way

• when it is important to decide the moment when to start the animation;

for all other cases an implicit animation is enough. In particular, implicit animations are often obtained by using built-in implicit animation widgets, typically named with the

Animated− prefix followed by the name of the property to animate; also, the AnimatedContainer class is quite powerful in realizing these kinds of animation; however, if no built-in

im-plicit animation widget fits the animation needs, the T weenAnimationBuilder class provides the right tools for creating a custom implicit animation. Also explicit anima-tions are provided by Flutter as built-in explicit animation widgets, which usually use

−T ransition suffix preceded by the name of the property to animate; yet, in case these widgets are not suitable for the animation, the Flutter team suggests either to extend the AnimatedW idget class when preferring a standalone custom explicit animation or to use the AnimatedBuilder class otherwise. Finally, in case of UI performance issues, it is recommended to animate widgets by means of the CustomP ainter class, which is capable of painting directly to the Canvas.

In order to understand the following code snippets, following the explicit animation paradigm for animating widgets, three class definitions must be introduced.

Every Animation object in Flutter doesn’t know anything about what the screen is showing: indeed, this abstract class’s aim is to take care of the current value of the animation and of its state (e.g. completed, dismissed); over a certain period of time, an Animation object interpolates numbers between two values in a sequential manner.

its output may be either linear, a step function, a curve or any other possible mapping.

Moreover, the Animation object may run in reverse or even swap directions in the middle.

Its value member is an important property used for accessing the current value of the object’s state.

An AnimationController object is a special case of an Animation object because it is capable of generating a new value every time the hardware requires a new frame to be displayed. The interesting features of this object are its methods for controlling the animation: indeed, to start an animation the f orward() method must be called;

of course, the generation of interpolated numbers depends on the screen refresh rate (thus, in general 60 numbers per second); then, every Animation object bound to the AnimationController will call the attached Listener objects in order to react to the change. One of AnimationController’s constructor paramaters is the vsync argument, which avoids excessive resource use by off-screen animations.

AnimationController is just like an Animation but its value ranges from 0.0 to 1.0;

thus, in case the animation needs a different range, the T ween object helps configuring the animation to interpolate that specific range (its constructor has two parameters, begin and end). This object doesn’t have its own state, but its evaluate(Animation animation) method is responsible for mapping the value of the Animation object to the desired value.

Moreover, the animate(Animation animation) method generates the interpolated desired numbers in that moment.

Despite it is difficult to appreciate an animation from a static screenshot, one of the most effective examples of animations implemented within the mobile application is the palpitating halo that shows the current Air Quality Index (AQI) value in the Home page,

which is represented in figure 4.2 on page 57. This animation was realized with the following code snippets, the first one taken from the EllipsisW ithChartSection class which in the build() method instantiates the EllipsisHalo class, reported in the second snippet, by passing haloT ween and animation objects.

AnimationController animationController;

Animation curve;

Animation<double> animation;

Tween haloTween;

@override

void initState() { super.initState();

animationController = AnimationController(

duration: const Duration(milliseconds: 1500), vsync: this);

animation =

CurvedAnimation(parent: animationController, curve: Curves.decelerate) ..addListener(() {

AnimationStatus status = animationController.status;

if (status == AnimationStatus.completed) { animationController.reverse();

} else if (status == AnimationStatus.dismissed) { animationController.forward();

}

setState(() {});

});

haloTween = Tween<double>(begin: 0.49, end: 0.52);

animationController.forward();

}

Notice that the animation object becomes strictly bond over the animationController object, so that when the animation is started by the f orward() method then the inter-polated numbers between 0.0 and 1.0 are generated and then mapped into the range indicated by the haloT ween object; moreover, the code exploits the .. syntax, which al-lows to call the following method on the object preceding the two dots with no side effects on the return value, to add a listener on the animation status: indeed, this listener will be called every time the value of the animation changes (potentially 60 times per second) in order to force the framework to rebuild the widget by using the setState() function.

Container(

width: 204, height: 204,

decoration: BoxDecoration(

shape: BoxShape.circle, gradient: RadialGradient(

radius: haloTween.animate(animation).value, center: Alignment(0, 0),

colors: [

paintAQIDarkCircle(...).withOpacity(0.7), paintAQIDarkCircle(...).withOpacity(0.6), paintAQIDarkCircle(...).withOpacity(0.1), paintAQIDarkCircle(...).withOpacity(0), ],

stops: [0.5, 0.8, 0.9, 1], ))

)

The last code snippet highlights the most important aspects about the UI: the halo behind the circle should create a smooth effect that increases the more it is further from the center of the circle, so a dynamic set of Color objects with different opacity is used

Study and development of a participative system for monitoring air pollution data

to obtain this effect; finally, the animating palpitation effect is realized by dynamically changing the radius of the circular BoxDecoration object.

The whole code developed for this mobile application contains many other examples of animating widgets that have been created following this technique; implicit animations have rarely been used because most of the times the context required to animate the widgets after user’s interaction or until a certain condition was true. An original and quite time-consuming animation effect is the one that animates the needle of the sensor’s card shown in figure 4.4 on page 59.