DEV Community

kanta13jp1
kanta13jp1

Posted on

Flutter Advanced Animation — Hero, Staggered, and CustomPainter for Rich UIs

Flutter Advanced Animation — Hero, Staggered, and CustomPainter for Rich UIs

Three advanced animation patterns with full implementation code.

Hero Animation (Screen Transitions)

// List screen
GridView.builder(
  itemBuilder: (context, index) {
    final item = items[index];
    return GestureDetector(
      onTap: () => Navigator.push(context,
        MaterialPageRoute(builder: (_) => DetailPage(item: item))),
      child: Hero(
        tag: 'item-${item.id}', // unique tag
        child: Image.network(item.imageUrl, fit: BoxFit.cover),
      ),
    );
  },
)

// Detail screen
Hero(
  tag: 'item-${widget.item.id}', // same tag
  child: Image.network(widget.item.imageUrl),
)
Enter fullscreen mode Exit fullscreen mode

Staggered Animation (Sequential Reveal)

class StaggeredList extends StatefulWidget { ... }

class _StaggeredListState extends State<StaggeredList>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 1200),
      vsync: this,
    )..forward();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: List.generate(items.length, (i) {
        final start = i * 0.1;
        final end = (start + 0.4).clamp(0.0, 1.0);
        final animation = CurvedAnimation(
          parent: _controller,
          curve: Interval(start, end, curve: Curves.easeOut),
        );
        return FadeTransition(
          opacity: animation,
          child: SlideTransition(
            position: Tween<Offset>(
              begin: const Offset(0, 0.3),
              end: Offset.zero,
            ).animate(animation),
            child: ItemCard(item: items[i]),
          ),
        );
      }),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

CustomPainter (Custom Drawing Animation)

class CircleProgressPainter extends CustomPainter {
  final double progress; // 0.0 to 1.0
  final Color color;

  const CircleProgressPainter({required this.progress, required this.color});

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = color
      ..strokeWidth = 8
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round;

    canvas.drawArc(
      Rect.fromCircle(center: size.center(Offset.zero), radius: size.width / 2 - 4),
      -pi / 2,            // start at 12 o'clock
      2 * pi * progress,  // draw progress amount
      false,
      paint,
    );
  }

  @override
  bool shouldRepaint(CircleProgressPainter old) => old.progress != progress;
}

// Usage
AnimatedBuilder(
  animation: _controller,
  builder: (_, __) => CustomPaint(
    painter: CircleProgressPainter(
      progress: _controller.value,
      color: Colors.blue,
    ),
    size: const Size(100, 100),
  ),
)
Enter fullscreen mode Exit fullscreen mode

Summary

Hero            → shared element transition between screens (linked by tag)
Staggered       → Interval staggers each element's entrance
CustomPainter   → shouldRepaint prevents unnecessary redraws
Performance     → always dispose AnimationController
Enter fullscreen mode Exit fullscreen mode

Animation exists to help the eye follow elements — meaning over spectacle.

Top comments (0)