DEV Community

Cover image for Flutter Animations for Beginners: What You Can Build Without Plugins
Codexlancers
Codexlancers

Posted on

Flutter Animations for Beginners: What You Can Build Without Plugins

Animations are often where apps start to feel alive instead of just functional.
And if you're just getting started with Flutter, it's easy to assume:
"I'll need a bunch of animation libraries to make things look good."

We used to think the same.
But in reality, Flutter already gives you a powerful set of built-in animation tools - no plugins required.

In this blog, we'll walk through real UI animations we've built in production apps, using only Flutter's default capabilities.

Why Avoid Plugins (At First)?

From our experience:

  • Plugins increase dependency risk
  • Updates can break things unexpectedly
  • Debugging becomes harder

Flutter's built-in animations are:

  • Lightweight
  • Performant
  • Fully customizable

And honestly more than enough for most use cases.

Animations You Can Build Without Plugins

1. Button Click Animation (Scale Effect)

On tap, the button slightly scales down and then smoothly returns to its original size.

It's a subtle effect, but it plays a big role - without feedback like this, users can feel unsure if their action was registered. This small touch makes the UI feel responsive and polished.

Where to use?

  • Buttons
  • Cards
  • Any tappable UI
import 'package:flutter/material.dart';

class AnimatedButton extends StatefulWidget {
  @override
  _AnimatedButtonState createState() => _AnimatedButtonState();
}

class _AnimatedButtonState extends State<AnimatedButton> {
  double _scale = 1.0;

  void _onTapDown(_) => setState(() => _scale = 0.95);
  void _onTapUp(_) => setState(() => _scale = 1.0);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTapDown: _onTapDown,
      onTapUp: _onTapUp,
      child: AnimatedScale(
        scale: _scale,
        duration: Duration(milliseconds: 150),
        child: Container(
          padding: EdgeInsets.all(16),
          color: Colors.blue,
          child: Text("Tap Me", style: TextStyle(color: Colors.white)),
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Expand / Collapse Cards

When tapped, the container smoothly expands or collapses to reveal or hide content.

Rather than content appearing abruptly, this creates a more natural flow, making the UI easier to read and noticeably more polished.

Where to use?

  • FAQs
  • Settings
  • Dashboards
class ExpandableCard extends StatefulWidget {
  @override
  _ExpandableCardState createState() => _ExpandableCardState();
}

class _ExpandableCardState extends State<ExpandableCard> {
  bool isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => setState(() => isExpanded = !isExpanded),
      child: AnimatedContainer(
        duration: Duration(milliseconds: 300),
        padding: EdgeInsets.all(16),
        height: isExpanded ? 150 : 70,
        color: Colors.grey[200],
        child: Column(
          children: [
            Text("Tap to Expand"),
            if (isExpanded) Text("More content here...")
          ],
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Image Zoom (Hero Animation)

The image smoothly moves from one screen to another, as if it's the same element transitioning between pages.

This creates a seamless experience and makes the app feel more polished.

Where to use?

  • Profile pictures
  • Product previews
  • Galleries
Hero(
  tag: "imageHero",
  child: Image.network("https://via.placeholder.com/150"),
)
Enter fullscreen mode Exit fullscreen mode

Next screen:

Hero(
  tag: "imageHero",
  child: Image.network("https://via.placeholder.com/600"),
)
Enter fullscreen mode Exit fullscreen mode

4. Fade-In Screens

Instead of instantly appearing, the screen fades in smoothly.

This removes the harshness of sudden transitions and creates a more pleasant, polished user experience.

Where to use?

  • Onboarding
  • Splash to home
  • First load screens
class FadeScreen extends StatefulWidget {
  @override
  _FadeScreenState createState() => _FadeScreenState();
}

class _FadeScreenState extends State<FadeScreen> {
  double opacity = 0;

  @override
  void initState() {
    super.initState();
    Future.delayed(Duration(milliseconds: 200), () {
      setState(() => opacity = 1);
    });
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedOpacity(
      duration: Duration(milliseconds: 500),
      opacity: opacity,
      child: Scaffold(
        body: Center(child: Text("Welcome")),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Smooth List Insert / Remove

When items are added or removed, they animate smoothly instead of just appearing or disappearing.

This makes the changes feel more natural and easier to follow.

Where to use?

  • Chat apps
  • Todo lists
  • Notifications
AnimatedList(
  initialItemCount: items.length,
  itemBuilder: (context, index, animation) {
    return SizeTransition(
      sizeFactor: animation,
      child: ListTile(
        title: Text(items[index]),
      ),
    );
  },
)
Enter fullscreen mode Exit fullscreen mode

6. Page Transitions (Custom Navigation)

This controls how screens transition when navigating between them.

While default transitions work fine, custom ones can make the experience feel smoother and more intentional.

Where to use?

  • Screen navigation
  • Onboarding flows
Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (_, __, ___) => NextScreen(),
    transitionsBuilder: (_, animation, __, child) {
      return FadeTransition(
        opacity: animation,
        child: child,
      );
    },
  ),
);
Enter fullscreen mode Exit fullscreen mode

7. Toggle UI Animations

The switch smoothly slides and updates its color when toggled.

This clear visual feedback helps users instantly understand the current state, making interactions feel more intuitive.

Where to use?

  • Settings
  • Filters
  • Feature toggles
class ToggleAnimation extends StatefulWidget {
  @override
  _ToggleAnimationState createState() => _ToggleAnimationState();
}

class _ToggleAnimationState extends State<ToggleAnimation> {
  bool isOn = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => setState(() => isOn = !isOn),
      child: AnimatedContainer(
        duration: Duration(milliseconds: 300),
        width: 60,
        height: 30,
        decoration: BoxDecoration(
          color: isOn ? Colors.green : Colors.grey,
          borderRadius: BorderRadius.circular(20),
        ),
        alignment: isOn ? Alignment.centerRight : Alignment.centerLeft,
        child: Padding(
          padding: EdgeInsets.all(4),
          child: CircleAvatar(radius: 10),
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

8. Tween Animation (Advanced Control)

This approach lets you animate values such as size, position, or opacity from one state to another.

It offers much finer control, making it ideal for cases where built-in animation widgets don't quite meet your needs.

Where to use?

  • Custom animations
  • Complex UI interactions
class TweenExample extends StatefulWidget {
  @override
  _TweenExampleState createState() => _TweenExampleState();
}

class _TweenExampleState extends State<TweenExample>
    with SingleTickerProviderStateMixin {
  late AnimationController controller;
  late Animation<double> animation;

  @override
  void initState() {
    super.initState();

    controller = AnimationController(
      duration: Duration(seconds: 1),
      vsync: this,
    );

    animation = Tween<double>(begin: 0, end: 200).animate(controller);

    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: animation,
      builder: (_, child) {
        return Container(
          width: animation.value,
          height: 50,
          color: Colors.blue,
        );
      },
    );
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}
Enter fullscreen mode Exit fullscreen mode

Common Mistakes Beginners Make

Overusing animations

Just because you can animate something doesn't mean you should.

If everything is moving, nothing really stands out - and the UI starts to feel noisy instead of smooth.

Making animations too slow

Long animations often feel frustrating instead of smooth.

In most cases, anything above ~500ms starts to feel laggy and hurts the experience.

No consistency in motion

If every screen uses a different animation style, the app starts to feel disconnected.

Consistency in motion is just as important as consistency in colors or fonts.

Ignoring performance

Animations can get expensive if not handled carefully.

Avoid rebuilding heavy widgets unnecessarily while animations are running - it can quickly impact performance.

Final Thoughts

If you're just starting with Flutter animations, don't jump to plugins.

Start with:

  • AnimatedContainer
  • AnimatedOpacity
  • Hero
  • AnimatedList
  • Tween + AnimationController

These alone can help you build smooth, production-level UI.

From our experience, mastering these basics gives you:

  • Better control
  • Cleaner code
  • More confidence

And honestly most apps don't need more than this.

Top comments (0)