flutter_animations
Animations play a vital role in creating engaging and interactive user interfaces in Flutter applications. Whether you’re building a game, an e-commerce app, or a social media platform, mastering animations can significantly enhance the user experience. In this guide, we’ll explore how to implement animations in Flutter using practical examples.

Today, we are going to take an example of a game that leverages flutter’s animation support to create a dynamic and engaging user experience for the game. In the code, animations are used to provide feedback to the user, such as indicating successful word completion or updating the currency count. The game screen (HomeScreen.dart) orchestrates these animations and manages the game’s state, while individual components like letter tiles (Tile.dart) and currency display (CurrencyDisplay.dart) handle their respective responsibilities within the game. 

To enhance the explanation, let’s delve deeper into Flutter’s animation capabilities and how they are utilized in the codebase. Flutter provides various classes and methods for creating animations, including explicit animations, implicit animations, and custom animations. Below codebase uses explicit animations, which involve directly controlling the animation’s state and properties.

1. Define an AnimationController to control the animation’s duration, curve, and other properties.
2. Create an Animation object that represents the current state of the animation.
3. Animate the widget’s properties using the Animation object within a StatefulWidget’s build method or a custom animation widget.

In addition to the steps outlined above, Flutter’s animation system also allows for the use of animation listeners, which enable developers to execute custom logic at various points during the animation’s lifecycle. This feature is particularly useful for synchronizing animations with other parts of the application or triggering actions based on the animation’s progress.

Swipe Score Animation: Displays the score earned when swiping a word in the game:

In the following code, animations are triggered using the setState method, which rebuilds the widget tree with updated animation values. This approach allows for smooth and responsive animations that integrate seamlessly with the game’s logic. HomeScreen.dart  This is main game screen where animations and game logic are implemented. The updateIndex() function, updates the game’s state based on user input. The wordBloc variable is an instance of the WordBloc class, triggers animation updates based on game events.
void updateIndex() {
  for (int i = 0i < wordBloc.addedWords.lengthi++) {
    if (wordBloc.addedWords[i== userSelection) {
      setState(() {
        if (WordScapeState.indexList.last -
                WordScapeState
                    .indexList[WordScapeState.indexList.length - 2<
            0) {
          HomeScreen.isReversed = true;
        }
        HomeScreen.gameIndex = i;
      });
    }
  }
}
Tile.dart This file represents individual letter tiles used in the game. This logic ensures that the CurrencyDisplay widget is displayed next to the selected tile, with its position and size adjusted dynamically based on the game state.

Condition Check: It first checks if the showExtraInfo variable in the HomeScreen class is true. This determines whether extra information should be displayed. Then, it checks if the tileId of the current Tile widget matches the lastIndex stored in the HomeScreen class. This ensures that the CurrencyDisplay is only shown for the selected tile.

Alignment Determination: If both conditions are met, it sets the alignment of the CurrencyDisplay widget based on the direction of the word associated with the current tile. If the word direction is vertical (WordDirection.Vertical), it checks if the game is reversed (isReversed). Depending on this, it aligns the CurrencyDisplay either above or below the tile vertically.

If the word direction is horizontal, it aligns the CurrencyDisplay either to the left or right of the tile horizontally, again depending on whether the game is reversed.

Size Configuration: Additionally, it configures the width and height of the CurrencyDisplay widget based on whether the word direction is vertical or horizontal, ensuring it fits appropriately with the layout.

This logic ensures that the CurrencyDisplay widget is displayed next to the selected tile, with its position and size adjusted dynamically based on the game state.

import 'package:my_game/blocs/word_bloc.dart';
import 'package:my_game/utils/base/word_direction.dart';

class Tile extends StatefulWidget {
  final int tileId;
  final bool isSelected;
  final String letter;
  final Color selectedColor;
  final int level;
  final WordBloc wordBloc;
  Tile(
      {Key? key,
      required this.letter,
      required this.isSelected,
      required this.tileId,
      required this.selectedColor,
      required this.level,
      required this.wordBloc})
      : super(keykey);

if (HomeScreen.showExtraInfo)
  if (widget.tileId == HomeScreen.lastIndex)
    Align(
      alignmentwidget.wordBloc.wordDirections[HomeScreen.gameIndex==
              WordDirection.Vertical
          ? HomeScreen.isReversed
              ? Alignment(0-2.3)
              : Alignment(02.3)
          : HomeScreen.isReversed
              ? Alignment(-2.30)
              : Alignment(2.30),
      childCurrencyDisplay(
          widthwidget.wordBloc.wordDirections[HomeScreen.gameIndex==
                  WordDirection.Vertical
              ? FULL_WIDTH
              : 25,
          heightwidget.wordBloc.wordDirections[HomeScreen.gameIndex==
                  WordDirection.Vertical
              ? 25
              : FULL_HEIGHT),
    }						

CurrencyDisplay.dart

This file defines the widget responsible for displaying the currency count in the game. It utilizes Flutter’s StatefulWidget to manage its state, particularly the visibility of the currency count. The build() method renders the widget based on its properties (height and width) and the current game state.

Visibility: The widget from material.dart controls the visibility of its child based on the value of the visible parameter. In this case, it determines whether the CurrencyDisplay widget should be visible.

Container: The Container widget serves as the parent widget for the CurrencyDisplay . It defines its width, height, decoration, and child. 

Conditional Layout: Depending on whether the width of the Container is greater than its height (widget.width > widget.height), the layout changes: If the width is greater, a Row() widget is used to display the image and the score text horizontally. If the height is greater or equal, a Column widget is used to display them vertically.

Content: The text widget from the auto_size_text package and an image widget is used to display the current score dynamically.

The image is loaded from the assets.
The score text is dynamically updated from GamePoints.score.

Styling: The container has a grey background with a yellow border. The score text is styled with yellow color and bold font weight.

This logic efficiently handles the display of the currency information, adjusting its layout based on available space.

import 'package:flutter/material.dart';
import 'package:my_game/utils/word_points.dart';
import 'package:auto_size_text/auto_size_text.dart';

class CurrencyDisplay extends StatefulWidget {
  final double height;
  final double width;
  CurrencyDisplay({required this.heightrequired this.width});
  static bool isVisible = false;
  @override
  State<CurrencyDisplay> createState() => _CurrencyDisplayState();
}

class _CurrencyDisplayState extends State<CurrencyDisplay> {
  @override
  Widget build(BuildContext context) {
    return Visibility(
      visibleCurrencyDisplay.isVisible,
      childContainer(
        widthwidget.width,
        heightwidget.height,
        decorationBoxDecoration(
          colorColors.grey[800],
          borderBorder.all(
            colorColors.yellowAccent,
            width2,
          ),
          borderRadiusBorderRadius.circular(15),
        ),
        childwidget.width > widget.height
            ? Row(
                mainAxisAlignmentMainAxisAlignment.spaceAround,
                children: [
                  Expanded(
                    childImage.asset(
                      'assets/images/currency_image.png',
                      fitBoxFit.contain,
                    ),
                  ),
                  Expanded(
                    flex2,
                    childAutoSizeText(
                      "${GamePoints.score}",
                      styleTextStyle(
                        colorColors.yellowAccent,
                        fontWeightFontWeight.bold,
                      ),
                    ),
                  ),
                ],
              )
            : Column(
                mainAxisAlignmentMainAxisAlignment.center,
                children: [
                  Expanded(
                    childImage.asset(
                      'assets/images/currency_image.png',
                      fitBoxFit.contain,
                    ),
                  ),
                  Expanded(
                    flex2,
                    childAutoSizeText(
                      "${GamePoints.score}",
                      styleTextStyle(
                        colorColors.yellowAccent,
                        fontWeightFontWeight.bold,
                      ),
                    ),
                  ),
                ],
              ),
      ),
    );
  }
}						

Header Score Update Animation: It is responsible for updating the score displayed in the header menu of the game

Following code is another example of an explicit animation. It uses predefined animation widgets called FadeIn.
import'package:animate_do/animate_do.dart';
import'package:flutter/material.dart';
import'package:google_fonts/google_fonts.dart';   

class TotalScore extends StatefulWidget{
 static int total = 0;
 final int pointsEarned;
 static bool isScoreChanged = false;

 TotalScore({Key? key, this.pointsEarned = 0}) : super(key: key);

  @override
 State<TotalScore> createState() => _TotalScoreState();
}

class _TotalScoreState extends State <TotalScore> {
  @override
 Widget build(BuildContext context) {
    return Container(
      height: 85,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          if (TotalScore.isScoreChanged)
            Stack(
              children: [
                FadeInUp(
                  duration: Duration(milliseconds: 500),
                  animate: true,
                  curve: Curves.easeOutQuint,
                  child: Text(
                    "${widget.pointsEarned > 0 ? '+' : ''}${widget.pointsEarned}",
                    style: GoogleFonts.gluten(
                      textStyle: TextStyle(
                        color: widget.pointsEarned > 0 ? Colors.green : Colors.red,
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ),
                ),
              ],
            ),
          Container(
            height: 60,
            width:  MediaQuery.of(context).size.width * 0.25,
            decoration: BoxDecoration(
              border: Border.all(
                color: Colors.green,
                width: 4,
                style: BorderStyle.solid,
              ),
              borderRadius: BorderRadius.circular(15.0),
              color: Colors.white,
            ),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Container(
                  child: Text(
                    "Score",
                    style: GoogleFonts.gluten(
                      textStyle: TextStyle(
                        height: 1,
                        fontSize: 20,
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                  ),
                ),
                Container(
                  child: Text(
                    "${TotalScore.total}",
                    style: GoogleFonts.gluten(
                      textStyle: TextStyle(
                        height: 0.8,
                        color: Colors.green,
                        fontWeight: FontWeight.bold,
                        fontSize: 30,
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}						

Introduction to FadeIn Animation

The FadeIn animation is a visual effect used to smoothly transition an element from invisible to visible by gradually increasing its opacity. This animation technique is commonly employed in web and mobile app development to enhance user experience and draw attention to specific elements on the screen. 

FadeIn animation modifies the opacity of the target element over a specified duration to create a smooth transition effect. Here’s a detailed explanation of how it works in below code,

1. Widget Composition: The widget to be animated, such as a Text or Image, is wrapped inside a FadeIn widget provided by the animate_do library.

2. Configuration: The FadeIn widget is configured with parameters such as duration, delay, curve, and whether to loop the animation.

3. Opacity Transition: During the animation, the FadeIn widget gradually increases the opacity of its child widget from 0 to 1 over the specified duration. This transition creates the effect of the widget fading in.

4. Curve: The curve parameter determines the rate of change of opacity over time, allowing for customization of the animation’s acceleration and deceleration.

5. Completion: Once the animation is complete, the opacity of the child widget reaches its maximum value of 1, making the widget fully visible.

FadeIn(
  durationDuration(milliseconds500),
  childText(
    'Hello, World!',
    styleTextStyle(fontSize20),
  ),
)

Summary

Through detailed explanations and practical examples, this blog post delved into the practical application of explicit animations, where the starting and ending points are defined explicitly. Through the use of animation controllers and other Flutter tools, readers learned how to create smooth and responsive user interactions tailored to their specific needs. Overall, the post offered a practical approach to leverage Flutter’s animation support effectively to create engaging and visually appealing user interfaces. Whether beginners or experienced developers, the blog provides valuable insights and techniques to enhance animation implementation in Flutter apps. We used Google Fonts on our interface to showcase the animations. its worthy to note that developers can further enhance the visual appeal of their applications, ensuring a polished and professional look while leveraging Flutter’s animation capabilities to deliver dynamic user experiences.

About Author

Vaishakhi Panchmatia

As Tech Co-Founder at Yugensys, I’m passionate about fostering innovation and propelling technological progress. By harnessing the power of cutting-edge solutions, I lead our team in delivering transformative IT services and Outsourced Product Development. My expertise lies in leveraging technology to empower businesses and ensure their success within the dynamic digital landscape.

Looking to augment your software engineering team with a team dedicated to impactful solutions and continuous advancement, feel free to connect with me. Yugensys can be your trusted partner in navigating the ever-evolving technological landscape.

Subscribe to our newsletter.
Loading

Related Articles