Open In App

Flutter - Radial Hero Animation

Last Updated : 04 Mar, 2025
Comments
Improve
Suggest changes
Like Article
Like
Report

A Radial transformation means turning a circular shape into a square shape. In Radial Hero animation the same is done with a Hero. In Flutter, a Hero Widget is used to create a hero animation. A hero in this context refers to a widget that moves in between screens. This is one of the most fundamental types of animation used in the application, especially if the app deals with media like images. Simply put, a hero animation is when a hero flies from one screen to another.

The following process occurs while creating a radial hero Animation:

  • The RadialExpansion Widget is used to wrap the hero.
  • The size of the hero changes as it flies through the routes and the hero adopts the size of the child.
  • Two overlapping clips form the RadialExpansion animation.
  • The PageRouteBuilder is used to build the destination of the hero.
  • All gestures are handled by the InkWell widget.

The following image shows the radial transformation of a hero at time = 0 to time =1.

Afterclipping


The RadialExpansion widget works in the following depicted way:


Now let's build a simple app that has three heroes that make a radial transformation when clicked on them. The heroes will be stores in the following folder of the flutter app:

flutter_app/assets/images/.....

Make sure to add the dependencies in the pubspec.yaml file as shown below:


Now let's build the main.dart file:

main.dart:

Dart
import 'dart:math' as math;

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;

void main() {
  runApp(MaterialApp(home: RadialExpansionDemo()));
}

class Photo extends StatelessWidget {
  Photo({super.key, required this.photo, required this.color, required this.onTap});

  final String photo;
  final Color color;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    return Material(
      color: Theme.of(context).primaryColor.withOpacity(0.25),
      child: InkWell(
        onTap: onTap,
        child: LayoutBuilder(
          builder: (BuildContext context, BoxConstraints size) {
            return Image.asset(
              photo,
              fit: BoxFit.contain,
            );
          },
        ),
      ),
    );
  }
}

class RadialExpansion extends StatelessWidget {
  RadialExpansion({
    super.key,
    required this.maxRadius,
    required this.child,
  }) : clipRectSize = 2.0 * (maxRadius / math.sqrt2);

  final double maxRadius;
  final double clipRectSize;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return ClipOval(
      child: Center(
        child: SizedBox(
          width: clipRectSize,
          height: clipRectSize,
          child: ClipRect(
            child: child,
          ),
        ),
      ),
    );
  }
}

class RadialExpansionDemo extends StatelessWidget {
  static const double kMinRadius = 32.0;
  static const double kMaxRadius = 128.0;
  static const opacityCurve = Interval(0.0, 0.75, curve: Curves.fastOutSlowIn);

  static Tween<Rect?> _createRectTween(Rect? begin, Rect? end) {
    return MaterialRectCenterArcTween(begin: begin, end: end);
  }

  static Widget _buildPage(BuildContext context, String imageName, String description) {
    return Container(
      color: Theme.of(context).canvasColor,
      child: Center(
        child: Card(
          elevation: 8.0,
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              SizedBox(
                width: kMaxRadius * 2.0,
                height: kMaxRadius * 2.0,
                child: Hero(
                  createRectTween: _createRectTween,
                  tag: imageName,
                  child: RadialExpansion(
                    maxRadius: kMaxRadius,
                    child: Photo(
                      photo: imageName,
                      onTap: () {
                        Navigator.of(context).pop();
                      },
                      color: Colors.white,
                    ),
                  ),
                ),
              ),
              Text(
                description,
                style: TextStyle(fontWeight: FontWeight.bold),
                textScaleFactor: 3.0,
              ),
              const SizedBox(height: 20.0),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildHero(BuildContext context, String imageName, String description) {
    return Container(
      width: kMinRadius * 3.0,
      height: kMinRadius * 3.0,
      child: Hero(
        createRectTween: _createRectTween,
        tag: imageName,
        child: RadialExpansion(
          maxRadius: kMaxRadius,
          child: Photo(
            photo: imageName,
            color: Colors.white,
            onTap: () {
              Navigator.of(context).push(
                PageRouteBuilder<void>(
                  pageBuilder: (BuildContext context, Animation<double> animation,
                      Animation<double> secondaryAnimation) {
                    return AnimatedBuilder(
                      animation: animation,
                      builder: (BuildContext context, Widget? child) {
                        return Opacity(
                          opacity: opacityCurve.transform(animation.value),
                          child: _buildPage(context, imageName, description),
                        );
                      },
                    );
                  },
                ),
              );
            },
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    // animation speed.
    timeDilation = 4.0;

    return Scaffold(
      appBar: AppBar(
        title: const Text('GeeksForGeeks'),
        backgroundColor: Colors.green,
      ),
      body: Container(
        padding: const EdgeInsets.all(33.0),
        alignment: FractionalOffset.bottomLeft,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            _buildHero(context, 'assets/images/aquaman.png', 'Aquaman'),
            _buildHero(context, 'assets/images/superman.jpg', 'Superman'),
            _buildHero(context, 'assets/images/wonderwomen.jpg', 'Wonderwomen'),
          ],
        ),
      ),
    );
  }
}

Output:


Next Article
Article Tags :

Similar Reads