I'm trying to achieve the Hero & shake upright animation result attached gif here. This is the result I got so far.
Seems like the Hero widget conflicts with the animation I've applied. It seems to be working for the 200
& 300
card. But when 100
is tapped, it seems to be working differently. Attached below are the code for the demo above.
Tried using WidgetsBinding.instance.addPostFrameCallback
and SchedulerBinding.instance.addPersistentFrameCallback
.
Is there any way to get the expected result instead of using the code I've used?
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,
),
);
}
}
CodePudding user response:
Wrap your Hero
widget with ShakeTransitionWidget
instead of the other way around:
// ... Hero is the child, not the parent
ShakeTransitionWidget(
axis: Axis.vertical,
duration: _shakeDuration,
offset: 30,
child: Hero(
tag: widget.dummy.backText,
// ...
Now inside the initState()
of _ShakeTransitionWidgetState
simply call _controller.forward()
without any SchedulerBinding
or WidgetsBinding
.
As a rule of thumb, usually the Hero and its children should not change from page to page. Instead add modifications to the parent widgets.
See gif.