从容器的外部动画到其最终位置的小部件

时间:2018-10-21 19:00:57

标签: flutter flutter-layout flutter-animation

我正在尝试创建动画,其中小部件从容器的外部移动到其最终位置。

类似这样的东西:

example 1

或类似这样的内容(在选择锻炼屏幕中):

example 2

我不知道容器内部的最终位置(由容器计算,例如网格,行,列等)。

我认为我需要一个知道其在屏幕上位置的小部件。一个叠加层,用于在容器上方绘制动画并设置容器中两个小部件的不透明度动画。 我在正确的轨道上吗?

1 个答案:

答案 0 :(得分:2)

我为此创建了一个程序包:https://github.com/letsar/flutter_sidekick

要完成这类动画,可以使用SidekickTeamBuilder小部件。

下面是创建以下动画的代码示例:

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,
          ),
        ),
      ),
    );
  }
}