Last active
July 8, 2024 11:24
-
-
Save fgatti675/80b777c090b5687a1a9c9caafc1c732b to your computer and use it in GitHub Desktop.
Flutter transition from FAB to navigator page
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'package:flutter/material.dart'; | |
final routeObserver = RouteObserver<PageRoute>(); | |
final duration = const Duration(milliseconds: 300); | |
void main() => runApp(MaterialApp( | |
home: HomePage(), | |
navigatorObservers: [routeObserver], | |
)); | |
class HomePage extends StatefulWidget { | |
@override | |
_HomePageState createState() => _HomePageState(); | |
} | |
class _HomePageState extends State<HomePage> with RouteAware { | |
GlobalKey _fabKey = GlobalKey(); | |
bool _fabVisible = true; | |
@override | |
didChangeDependencies() { | |
super.didChangeDependencies(); | |
routeObserver.subscribe(this, ModalRoute.of(context)); | |
} | |
@override | |
dispose() { | |
super.dispose(); | |
routeObserver.unsubscribe(this); | |
} | |
@override | |
didPopNext() { | |
// Show back the FAB on transition back ended | |
Timer(duration, () { | |
setState(() => _fabVisible = true); | |
}); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar(elevation: 0, backgroundColor: Colors.yellow.shade600), | |
bottomNavigationBar: Container(height: 92, color: Colors.yellow.shade600), | |
body: Container(color: Colors.yellow), | |
floatingActionButton: Visibility( | |
visible: _fabVisible, | |
child: _buildFAB(context, key: _fabKey), | |
), | |
); | |
} | |
Widget _buildFAB(context, {key}) => FloatingActionButton( | |
elevation: 0, | |
backgroundColor: Colors.pink, | |
key: key, | |
onPressed: () => _onFabTap(context), | |
child: Icon(Icons.search), | |
); | |
_onFabTap(BuildContext context) { | |
// Hide the FAB on transition start | |
setState(() => _fabVisible = false); | |
final RenderBox fabRenderBox = _fabKey.currentContext.findRenderObject(); | |
final fabSize = fabRenderBox.size; | |
final fabOffset = fabRenderBox.localToGlobal(Offset.zero); | |
Navigator.of(context).push(PageRouteBuilder( | |
transitionDuration: duration, | |
pageBuilder: (BuildContext context, Animation<double> animation, | |
Animation<double> secondaryAnimation) => | |
SearchPage(), | |
transitionsBuilder: (BuildContext context, Animation<double> animation, | |
Animation<double> secondaryAnimation, Widget child) => | |
_buildTransition(child, animation, fabSize, fabOffset), | |
)); | |
} | |
Widget _buildTransition( | |
Widget page, | |
Animation<double> animation, | |
Size fabSize, | |
Offset fabOffset, | |
) { | |
if (animation.value == 1) return page; | |
final borderTween = BorderRadiusTween( | |
begin: BorderRadius.circular(fabSize.width / 2), | |
end: BorderRadius.circular(0.0), | |
); | |
final sizeTween = SizeTween( | |
begin: fabSize, | |
end: MediaQuery.of(context).size, | |
); | |
final offsetTween = Tween<Offset>( | |
begin: fabOffset, | |
end: Offset.zero, | |
); | |
final easeInAnimation = CurvedAnimation( | |
parent: animation, | |
curve: Curves.easeIn, | |
); | |
final easeAnimation = CurvedAnimation( | |
parent: animation, | |
curve: Curves.easeOut, | |
); | |
final radius = borderTween.evaluate(easeInAnimation); | |
final offset = offsetTween.evaluate(animation); | |
final size = sizeTween.evaluate(easeInAnimation); | |
final transitionFab = Opacity( | |
opacity: 1 - easeAnimation.value, | |
child: _buildFAB(context), | |
); | |
Widget positionedClippedChild(Widget child) => Positioned( | |
width: size.width, | |
height: size.height, | |
left: offset.dx, | |
top: offset.dy, | |
child: ClipRRect( | |
borderRadius: radius, | |
child: child, | |
)); | |
return Stack( | |
children: [ | |
positionedClippedChild(page), | |
positionedClippedChild(transitionFab), | |
], | |
); | |
} | |
} | |
class SearchPage extends StatelessWidget { | |
@override | |
Widget build(context) { | |
return Scaffold( | |
backgroundColor: Colors.pinkAccent, | |
appBar: AppBar( | |
elevation: 0, | |
backgroundColor: Colors.pink, | |
title: TextField( | |
decoration: InputDecoration.collapsed(hintText: "Search"), | |
), | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment