flutter_sidekick
用于在同一屏幕内的两个小部件之间创建类似 Hero 的动画的小部件。


特点
- 类似Hero的动画。
- 允许您为每个Sidekick指定不同的动画。
- 用于管理两个多子部件之间动画的部件。
入门
在您的 flutter 项目的 pubspec.yaml 文件中,添加以下依赖项
最新版本是
dependencies:
...
flutter_sidekick: ^latest_version
在您的库中添加以下导入
import 'package:flutter_sidekick/flutter_sidekick.dart';
要开始使用 Flutter,请查看在线 文档。
小部件
Sidekick
Sidekick部件大量借鉴了Hero部件API。
要链接两个Sidekick,作为源的Sidekick的targetTag属性必须与作为目标的Sidekick的tag属性相同。
然后,要为Sidekick设置动画,您可以使用SidekickController和其中一个move函数。
下面的动画可以用以下代码创建

import 'package:flutter/material.dart';
import 'package:flutter_sidekick/flutter_sidekick.dart';
class SimpleExample extends StatefulWidget {
@override
_SimpleExampleState createState() => _SimpleExampleState();
}
class _SimpleExampleState extends State<SimpleExample>
with TickerProviderStateMixin {
SidekickController controller;
@override
void initState() {
super.initState();
controller =
SidekickController(vsync: this, duration: Duration(seconds: 1));
}
@override
void dispose() {
controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Positioned(
top: 20.0,
left: 20.0,
width: 100.0,
height: 100.0,
child: GestureDetector(
onTap: () => controller.moveToTarget(context),
child: Sidekick(
tag: 'source',
targetTag: 'target',
child: Container(
color: Colors.blue,
),
),
),
),
Positioned(
bottom: 20.0,
right: 20.0,
width: 150.0,
height: 100.0,
child: GestureDetector(
onTap: () => controller.moveToSource(context),
child: Sidekick(
tag: 'target',
child: Container(
color: Colors.blue,
),
),
),
),
],
);
}
}
SidekickTeamBuilder
SidekickTeamBuilder部件可用于创建复杂布局,其中一个容器中的小部件可以移动到另一个容器,并且您希望过渡动画化。

import 'package:example/widgets/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_sidekick/flutter_sidekick.dart';
class Item {
Item({
this.id,
});
final int id;
}
class WrapExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SidekickTeamBuilder<Item>(
initialSourceList: List.generate(20, (i) => Item(id: i)),
builder: (context, sourceBuilderDelegates, targetBuilderDelegates) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
height: 120.0,
child: Wrap(
children: sourceBuilderDelegates
.map((builderDelegate) => builderDelegate.build(
context,
WrapItem(builderDelegate.message, true),
animationBuilder: (animation) => CurvedAnimation(
parent: animation,
curve: Curves.ease,
),
))
.toList(),
),
),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
CircleButton(
text: '>',
onPressed: () => SidekickTeamBuilder.of<Item>(context)
.moveAll(SidekickFlightDirection.toTarget),
),
SizedBox(width: 60.0, height: 60.0),
CircleButton(
text: '<',
onPressed: () => SidekickTeamBuilder.of<Item>(context)
.moveAll(SidekickFlightDirection.toSource),
),
],
),
),
SizedBox(
height: 250.0,
child: Wrap(
children: targetBuilderDelegates
.map((builderDelegate) => builderDelegate.build(
context,
WrapItem(builderDelegate.message, false),
animationBuilder: (animation) => CurvedAnimation(
parent: animation,
curve: FlippedCurve(Curves.ease),
),
))
.toList(),
),
)
],
),
);
},
);
}
}
class WrapItem extends StatelessWidget {
const WrapItem(
this.item,
this.isSource,
) : size = isSource ? 40.0 : 50.0;
final bool isSource;
final double size;
final Item item;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => SidekickTeamBuilder.of<Item>(context).move(item),
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Container(
height: size - 4,
width: size - 4,
color: _getColor(item.id),
),
),
);
}
Color _getColor(int index) {
switch (index % 4) {
case 0:
return Colors.blue;
case 1:
return Colors.green;
case 2:
return Colors.yellow;
case 3:
return Colors.red;
}
return Colors.indigo;
}
}