Skip to content

Instantly share code, notes, and snippets.

@fgatti675
Last active May 23, 2019 14:08
Show Gist options
  • Save fgatti675/80b777c090b5687a1a9c9caafc1c732b to your computer and use it in GitHub Desktop.
Save fgatti675/80b777c090b5687a1a9c9caafc1c732b to your computer and use it in GitHub Desktop.
Flutter transition from FAB to navigator page
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