Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animate a widget from outside its container to its final position

I'm trying to create an animation where a widget moves from outside its container to its final position.

Something like this:

example 1

Or something like this (In the Choose Exercises screen):

example 2

I don't know the final position inside the container in advance (computed by the container, like a grid, a row, a column, etc.).

I think that I need a widget that knows its position on the screen. An overlay to draw the animation above the containers and to animate the opacity of the two widgets in the containers. Am I on the right track?

like image 555
Romain Rastel Avatar asked Oct 21 '18 19:10

Romain Rastel


People also ask

How do you animate a widget in Flutter?

The animations are considered hard work and take time to learn. Flutter made it easy with its packages. To animate the widgets without much effort, we can wrap them inside different defined animated widgets in the animate_do package.

How do you change the widget position in Flutter?

Here's how you do it: Step 1: Wrap the Stack's child widget inside the Position widget. Step 2: Inside the Position widget, add the top , right , bottom , left property and give it a value. For example, setting top:15 and right:0 will position a widget on the top right of your screen with 15 px space from the top.


1 Answers

I created a package for doing this: https://github.com/letsar/flutter_sidekick.

To accomplish these kinds of animations, you can use the SidekickTeamBuilder widget.

Here a code example that creates the following animation:

Sidekick animation

import 'package:flutter/material.dart';
import 'package:flutter_sidekick/flutter_sidekick.dart';
import '../widgets/utils.dart';

class BubblesExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SidekickTeamBuilder<String>(
      animationDuration: Duration(milliseconds: 500),
      initialSourceList: <String>[
        'Log\nextension',
        'Goblet\nSquats',
        'Squats',
        'Barbell\nLunge',
        'Burpee',
        'Dumbell\nLunge',
        'Front\nSquats',
      ],
      builder: (context, sourceBuilderDelegates, targetBuilderDelegates) {
        return Center(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: ListView(
              children: <Widget>[
                ConstrainedBox(
                  constraints: BoxConstraints(minHeight: 150.0),
                  child: Wrap(
                    spacing: 4.0,
                    runSpacing: 4.0,
                    children: targetBuilderDelegates.map((builderDelegate) {
                      return builderDelegate.build(
                        context,
                        GestureDetector(
                          onTap: () => builderDelegate.state
                              .move(builderDelegate.message),
                          child: Bubble(
                            radius: 30.0,
                            fontSize: 12.0,
                            backgroundColor: Colors.blue,
                            foregroundColor: Colors.white,
                            child: Padding(
                              padding: const EdgeInsets.all(2.0),
                              child: Text(
                                builderDelegate.message,
                                textAlign: TextAlign.center,
                              ),
                            ),
                          ),
                        ),
                        animationBuilder: (animation) => CurvedAnimation(
                              parent: animation,
                              curve: FlippedCurve(Curves.easeOut),
                            ),
                        flightShuttleBuilder: (
                          context,
                          animation,
                          type,
                          from,
                          to,
                        ) =>
                            buildShuttle(
                              animation,
                              builderDelegate.message,
                            ),
                      );
                    }).toList(),
                  ),
                ),
                SizedBox(
                  height: 100.0,
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: <Widget>[
                      CircleButton(
                        text: '>',
                        onPressed: () => SidekickTeamBuilder.of<String>(context)
                            .moveAll(SidekickFlightDirection.toSource),
                      ),
                      SizedBox(width: 60.0, height: 60.0),
                      CircleButton(
                        text: '<',
                        onPressed: () => SidekickTeamBuilder.of<String>(context)
                            .moveAll(SidekickFlightDirection.toTarget),
                      ),
                    ],
                  ),
                ),
                Center(
                  child: Wrap(
                    spacing: 4.0,
                    runSpacing: 4.0,
                    children: sourceBuilderDelegates.map((builderDelegate) {
                      return builderDelegate.build(
                        context,
                        GestureDetector(
                          onTap: () => builderDelegate.state
                              .move(builderDelegate.message),
                          child: Bubble(
                            radius: 50.0,
                            fontSize: 20.0,
                            backgroundColor: Colors.green,
                            foregroundColor: Colors.white,
                            child: Padding(
                              padding: const EdgeInsets.all(2.0),
                              child: Text(
                                builderDelegate.message,
                                textAlign: TextAlign.center,
                              ),
                            ),
                          ),
                        ),
                        animationBuilder: (animation) => CurvedAnimation(
                              parent: animation,
                              curve: Curves.easeOut,
                            ),
                        flightShuttleBuilder: (
                          context,
                          animation,
                          type,
                          from,
                          to,
                        ) =>
                            buildShuttle(
                              animation,
                              builderDelegate.message,
                            ),
                      );
                    }).toList(),
                  ),
                ),
              ],
            ),
          ),
        );
      },
    );
  }

  Widget buildShuttle(
    Animation<double> animation,
    String message,
  ) {
    return AnimatedBuilder(
      animation: animation,
      builder: (_, __) {
        return Bubble(
          radius: Tween<double>(begin: 50.0, end: 30.0).evaluate(animation),
          fontSize: Tween<double>(begin: 20.0, end: 12.0).evaluate(animation),
          backgroundColor: ColorTween(begin: Colors.green, end: Colors.blue)
              .evaluate(animation),
          foregroundColor: Colors.white,
          child: Padding(
            padding: const EdgeInsets.all(2.0),
            child: Text(
              message,
              textAlign: TextAlign.center,
            ),
          ),
        );
      },
    );
  }
}

class Bubble extends StatelessWidget {
  const Bubble({
    Key key,
    this.child,
    this.backgroundColor,
    this.foregroundColor,
    this.radius,
    this.fontSize,
  }) : super(key: key);

  final Widget child;

  final Color backgroundColor;

  final Color foregroundColor;

  final double radius;

  final double fontSize;

  @override
  Widget build(BuildContext context) {
    final ThemeData theme = Theme.of(context);
    TextStyle textStyle =
        theme.primaryTextTheme.subhead.copyWith(color: foregroundColor);
    Color effectiveBackgroundColor = backgroundColor;
    if (effectiveBackgroundColor == null) {
      switch (ThemeData.estimateBrightnessForColor(textStyle.color)) {
        case Brightness.dark:
          effectiveBackgroundColor = theme.primaryColorLight;
          break;
        case Brightness.light:
          effectiveBackgroundColor = theme.primaryColorDark;
          break;
      }
    } else if (foregroundColor == null) {
      switch (ThemeData.estimateBrightnessForColor(backgroundColor)) {
        case Brightness.dark:
          textStyle = textStyle.copyWith(color: theme.primaryColorLight);
          break;
        case Brightness.light:
          textStyle = textStyle.copyWith(color: theme.primaryColorDark);
          break;
      }
    }

    textStyle = textStyle.copyWith(fontSize: fontSize);

    final double diameter = radius * 2;
    return Container(
      width: diameter,
      height: diameter,
      decoration: BoxDecoration(
        color: effectiveBackgroundColor,
        shape: BoxShape.circle,
      ),
      child: Center(
        child: IconTheme(
          data: theme.iconTheme.copyWith(color: textStyle.color),
          child: DefaultTextStyle(
            style: textStyle,
            child: child,
          ),
        ),
      ),
    );
  }
}
like image 73
Romain Rastel Avatar answered Oct 23 '22 22:10

Romain Rastel