-
-
Save mutant0113/453e4c0ca9bda7934394b77bffe8bcae to your computer and use it in GitHub Desktop.
| import 'package:flutter/material.dart'; | |
| void main() => runApp(MyApp()); | |
| class MyApp extends StatefulWidget { | |
| @override | |
| _MyAppState createState() => _MyAppState(); | |
| } | |
| class _MyAppState extends State<MyApp> { | |
| @override | |
| Widget build(BuildContext context) => MaterialApp( | |
| theme: ThemeData(primarySwatch: Colors.blue), | |
| home: PageA(), | |
| debugShowCheckedModeBanner: false, | |
| ); | |
| } | |
| class PageA extends StatefulWidget { | |
| @override | |
| _PageAState createState() => _PageAState(); | |
| } | |
| class _PageAState extends State<PageA> { | |
| final GlobalKey _fabButtonKey = GlobalKey(); | |
| RipplePageTransition _ripplePageTransition; | |
| static const _FabButtonColor = Colors.blueAccent; | |
| @override | |
| void initState() { | |
| _ripplePageTransition = RipplePageTransition(_fabButtonKey); | |
| super.initState(); | |
| } | |
| @override | |
| Widget build(BuildContext context) => Stack(children: [ | |
| Scaffold( | |
| body: Center(child: Text('Page A', style: TextStyle(fontSize: 40))), | |
| floatingActionButton: _floatingActionButton, | |
| ), | |
| _ripplePageTransition, | |
| ]); | |
| Widget get _floatingActionButton => FloatingActionButton( | |
| key: _fabButtonKey, | |
| backgroundColor: _FabButtonColor, | |
| child: Icon(Icons.add, size: 40), | |
| elevation: 2.0, | |
| onPressed: () => _ripplePageTransition.navigateTo(PageB()), | |
| ); | |
| } | |
| class PageB extends StatefulWidget { | |
| @override | |
| _PageBState createState() => _PageBState(); | |
| } | |
| class _PageBState extends State<PageB> { | |
| @override | |
| Widget build(BuildContext context) => Scaffold( | |
| backgroundColor: Colors.blueAccent[100], | |
| body: Center( | |
| child: Text('Page B', style: TextStyle(fontSize: 40)), | |
| ), | |
| ); | |
| } | |
| // A widget for ripple page transition between pages. | |
| class RipplePageTransition extends StatefulWidget { | |
| RipplePageTransition( | |
| this._originalWidgetKey, { | |
| Color color, | |
| }) : color = color ?? Colors.blueAccent; | |
| final GlobalKey _originalWidgetKey; | |
| final Color color; | |
| final _state = _RipplePageTransitionState(); | |
| void navigateTo(Widget page) => _state.startSpreadOutAnimation(page); | |
| @override | |
| _RipplePageTransitionState createState() => _state; | |
| } | |
| class _RipplePageTransitionState extends State<RipplePageTransition> { | |
| Widget _page; | |
| Rect _originalWidgetRect; | |
| Rect _ripplePageTransitionRect; | |
| // Starts ripple effect from the original widget size to the whole screen. | |
| void startSpreadOutAnimation(Widget page) { | |
| if (!mounted) { | |
| return; | |
| } | |
| setState(() { | |
| _page = page; | |
| _originalWidgetRect = _getWidgetRect(widget._originalWidgetKey); | |
| _ripplePageTransitionRect = _originalWidgetRect; | |
| }); | |
| WidgetsBinding.instance.addPostFrameCallback((_) { | |
| if (!mounted) { | |
| return; | |
| } | |
| final fullscreenSize = 1.3 * MediaQuery.of(context).size.longestSide; | |
| // Expands the `_ripplePageTransitionRect` to cover the whole screen. | |
| setState(() { | |
| return _ripplePageTransitionRect = _ripplePageTransitionRect.inflate(fullscreenSize); | |
| }); | |
| }); | |
| } | |
| // Starts ripple effect from the whole screen to the original widget size. | |
| void _startShrinkInAnimation() => setState(() => _ripplePageTransitionRect = _originalWidgetRect); | |
| Rect _getWidgetRect(GlobalKey globalKey) { | |
| var renderObject = globalKey?.currentContext?.findRenderObject(); | |
| var translation = renderObject?.getTransformTo(null)?.getTranslation(); | |
| var size = renderObject?.semanticBounds?.size; | |
| if (translation != null && size != null) { | |
| return new Rect.fromLTWH(translation.x, translation.y, size.width, size.height); | |
| } else { | |
| return null; | |
| } | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| if (_ripplePageTransitionRect == null) { | |
| return Container(); | |
| } | |
| return AnimatedPositioned.fromRect( | |
| rect: _ripplePageTransitionRect, | |
| duration: Duration(milliseconds: 300), | |
| child: Container( | |
| decoration: BoxDecoration( | |
| shape: BoxShape.circle, | |
| color: widget.color, | |
| ), | |
| ), | |
| onEnd: () { | |
| bool shouldNavigatePage = _ripplePageTransitionRect != _originalWidgetRect; | |
| if (shouldNavigatePage) { | |
| Navigator.push( | |
| context, | |
| FadeRouteBuilder(page: _page), | |
| ).then((_) { | |
| _startShrinkInAnimation(); | |
| }); | |
| } else { | |
| if (!mounted) { | |
| return; | |
| } | |
| // Dismiss ripple widget after shrinking finishes. | |
| setState(() => _ripplePageTransitionRect = null); | |
| } | |
| }, | |
| ); | |
| } | |
| } | |
| class FadeRouteBuilder<T> extends PageRouteBuilder<T> { | |
| FadeRouteBuilder({@required Widget page}) | |
| : super( | |
| pageBuilder: (context, animation, secondaryAnimation) => page, | |
| transitionDuration: Duration(milliseconds: 200), | |
| transitionsBuilder: ( | |
| context, | |
| animation, | |
| secondaryAnimation, | |
| child, | |
| ) => | |
| FadeTransition(opacity: animation, child: child), | |
| ); | |
| } |
Hi, @Azzeccagarbugli,
I could not reproduce the issue by using this sample code directly. However, I guess the issue is similar to the post:
https://developpaper.com/some-tips-in-flutter-development-ii/#:~:text=The%20preferred%20solution%20is%20to,is%20still%20in%20the%20tree.
According to the error log, there are 2 solutions:
- Cancel the timer or stop listening to the animation in the
dispose()callback.
However, we could not removeonEndcallback because theAnimatedPositioneddoesn't provide such method. - Another solution is to check the “mounted” property of this object before calling setState() to ensure the object is still in the tree.
So I think we should adopt this one. To check whether the view is still in the tree.
I've modified the code and please try again : )
Unfortunately is still giving me the same issue, even with the updated code. I had already tried that 😓
[VERBOSE-2:ui_dart_state.cc(177)] Unhandled Exception: setState() called after dispose(): _RipplePageTransitionState#d7419(lifecycle state: defunct, not mounted)
Could you provide the sample code and steps to reproduce the issue?
Could you provide the sample code and steps to reproduce the issue?
Man, I just found it. The error was the in the following line:
else {
// Dismiss ripple widget after shrinking finishes.
setState(() => _ripplePageTransitionRect = null);
}I corrected this in:
else {
// Dismiss ripple widget after shrinking finishes.
if (this.mounted) setState(() => _ripplePageTransitionRect = null);
}Thanks a lot anyway for your support, hope that can be useful!
No problem. I'll modify the code as well.
This code produces this error when the animation ends:
I was trying to fix it somehow but I didn't succeed, what do you suggest?