Skip to content

Instantly share code, notes, and snippets.

@mutant0113
Last active November 8, 2020 11:01
Show Gist options
  • Select an option

  • Save mutant0113/453e4c0ca9bda7934394b77bffe8bcae to your computer and use it in GitHub Desktop.

Select an option

Save mutant0113/453e4c0ca9bda7934394b77bffe8bcae to your computer and use it in GitHub Desktop.
FlutterContainerTransformFinalCode
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),
);
}
@Azzeccagarbugli
Copy link

This code produces this error when the animation ends:

The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().

I was trying to fix it somehow but I didn't succeed, what do you suggest?

@mutant0113
Copy link
Author

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:

  1. Cancel the timer or stop listening to the animation in the dispose() callback.
    However, we could not remove onEnd callback because the AnimatedPositioned doesn't provide such method.
  2. 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 : )

@Azzeccagarbugli
Copy link

Azzeccagarbugli commented Nov 6, 2020

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)

@mutant0113
Copy link
Author

Could you provide the sample code and steps to reproduce the issue?

@Azzeccagarbugli
Copy link

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!

@mutant0113
Copy link
Author

No problem. I'll modify the code as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment