我正在尝试创建动画,其中小部件从容器的外部移动到其最终位置。
类似这样的东西:
或类似这样的内容(在选择锻炼屏幕中):
我不知道容器内部的最终位置(由容器计算,例如网格,行,列等)。
我认为我需要一个知道其在屏幕上位置的小部件。一个叠加层,用于在容器上方绘制动画并设置容器中两个小部件的不透明度动画。 我在正确的轨道上吗?
答案 0 :(得分:2)
我为此创建了一个程序包:https://github.com/letsar/flutter_sidekick。
要完成这类动画,可以使用SidekickTeamBuilder
小部件。
下面是创建以下动画的代码示例:
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,
),
),
),
);
}
}