英雄小部件过渡与其他动画冲突



我正在努力实现英雄&抖动直立动画结果附在这里的GIF。这是我目前得到的结果。

似乎英雄小部件与我应用的动画冲突。它似乎对200&300卡。但当100被选中时,它的工作方式似乎有所不同。下面是上面演示的代码。

尝试使用WidgetsBinding.instance.addPostFrameCallbackSchedulerBinding.instance.addPersistentFrameCallback

是否有任何方法来获得预期的结果,而不是使用我所使用的代码?

dummy_data.dart

class _DummyData {
final IconData icons;
final Color colors;
final String backText;
const _DummyData(this.icons, this.colors, this.backText);
}
const List<_DummyData> _datas = [
_DummyData(Icons.abc, Colors.blue, '100'),
_DummyData(Icons.alarm, Colors.red, '200'),
_DummyData(Icons.shop, Colors.green, '300'),
];

playground_list.dart


class PlayGroundList extends StatelessWidget {
const PlayGroundList({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: CustomScrollView(
slivers: [
const SliverToBoxAdapter(child: SizedBox(height: 50)),
SliverList(
delegate: SliverChildBuilderDelegate(
childCount: _datas.length,
(context, index) => PlayGroundCardWidget(dummy: _datas[index]),
),
),
],
),
);
}
}

playground_card_widget.dart

class PlayGroundCardWidget extends StatelessWidget {
final _DummyData dummy;
const PlayGroundCardWidget({super.key, required this.dummy});
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 5.w, vertical: 1.5.h),
height: 350,
child: GestureDetector(
onTap: () => Navigator.push(
context,
PageRouteBuilder(
transitionsBuilder: (context, animation, _, child) =>
FadeTransition(
opacity: Tween(
begin: 0.0,
end: 1.0,
).chain(CurveTween(curve: Curves.ease)).animate(animation),
child: child,
),
pageBuilder: (context, _, __) => PlayGroundDetail(dummy: dummy),
),
),
child: Stack(
alignment: Alignment.center,
children: [
Positioned.fill(
child: Hero(
tag: dummy.colors.value,
child: Material(color: dummy.colors),
),
),
Align(
alignment: Alignment.topCenter,
child: Hero(
tag: dummy.backText,
child: Material(
color: Colors.transparent,
child: Text(
dummy.backText,
textAlign: TextAlign.center,
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 140.0,
),
),
),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: 3.h),
Expanded(
flex: 12,
child: Hero(
tag: dummy.icons,
child: Icon(dummy.icons, size: 60.0),
),
),
],
),
],
),
),
);
}
}

playground_detail.dart

const _shakeDuration = Duration(milliseconds: 900);
class PlayGroundDetail extends StatefulWidget {
final _DummyData dummy;
const PlayGroundDetail({super.key, required this.dummy});
@override
State<PlayGroundDetail> createState() => _PlayGroundDetailState();
}
class _PlayGroundDetailState extends State<PlayGroundDetail>
with TickerProviderStateMixin {
late final PageController _pageController;
@override
void initState() {
super.initState();
_pageController = PageController();
}
@override
void dispose() {
super.dispose();
_pageController.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
CustomScrollView(
slivers: [
const SliverToBoxAdapter(child: SizedBox(height: 50)),
SliverToBoxAdapter(
child: SizedBox(
height: 350,
child: Stack(
children: [
Positioned.fill(
child: Hero(
tag: widget.dummy.colors.value,
child: Material(color: widget.dummy.colors),
),
),
Align(
alignment: Alignment.topCenter,
child: Hero(
tag: widget.dummy.backText,
child: Material(
color: Colors.transparent,
child: ShakeTransitionWidget(
axis: Axis.vertical,
duration: _shakeDuration,
offset: 30,
child: Text(
widget.dummy.backText,
textAlign: TextAlign.center,
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 140.0,
),
),
),
),
),
),
Center(
child: Hero(
tag: widget.dummy.icons,
child: ShakeTransitionWidget(
axis: Axis.vertical,
offset: 5,
duration: _shakeDuration,
child: Icon(widget.dummy.icons, size: 60.0),
),
),
),
],
),
),
),
],
),
],
),
);
}
}

shake_transition_widget.dart

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
class ShakeTransitionWidget extends StatefulWidget {
final Widget child;
final Duration duration;
final double offset;
final Axis axis;
const ShakeTransitionWidget({
super.key,
required this.child,
required this.duration,
required this.offset,
required this.axis,
});
@override
State<ShakeTransitionWidget> createState() => _ShakeTransitionWidgetState();
}
class _ShakeTransitionWidgetState extends State<ShakeTransitionWidget>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: widget.duration,
);
_animation = Tween(
begin: 1.0,
end: 0.0,
).chain(CurveTween(curve: Curves.elasticOut)).animate(_controller);
// Tried this.
// WidgetsBinding.instance.addPostFrameCallback((_) => _controller.forward());
if (SchedulerBinding.instance.schedulerPhase ==
SchedulerPhase.persistentCallbacks) {
_controller.forward();
}
SchedulerBinding.instance.addPersistentFrameCallback((timeStamp) {});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (ctx, _) => Transform.translate(
offset: widget.axis == Axis.horizontal
? Offset(_animation.value * widget.offset, 0.0)
: Offset(0.0, _animation.value * widget.offset),
child: widget.child,
),
);
}
}

ShakeTransitionWidget包裹Hero小部件,而不是其他方式:

// ... Hero is the child, not the parent
ShakeTransitionWidget(
axis: Axis.vertical,
duration: _shakeDuration,
offset: 30,
child: Hero(
tag: widget.dummy.backText,
// ...

现在,在_ShakeTransitionWidgetStateinitState()内部,只需调用_controller.forward(),而不需要任何SchedulerBindingWidgetsBinding

根据经验,通常Hero和它的子元素不应该在不同的页面之间改变。而是向父部件添加修改。

看到gif。

最新更新