Swipe to navigate back and admire beautifully morphing widgets.
SwipeablePageRoute is a specialized CupertinoPageRoute that allows your users to go back by swiping anywhere on the current page. Use it instead of MaterialPageRoute or CupertinoPageRoute:
Navigator.of(context).push(SwipeablePageRoute(
builder: (BuildContext context) => MyPageContent(),
));
If your page contains horizontally scrollable content, you can limit SwipeablePageRoute to only react on drags from the start (left in LTR, right in RTL) screen edgeโโโjust like CupertinoPageRoute:
Navigator.of(context).push(SwipeablePageRoute(
onlySwipeFromEdge: true,
builder: (BuildContext context) => MyHorizontallyScrollablePageContent(),
));
You can get the SwipeablePageRoute wrapping your current page using context.getSwipeablePageRoute<T>().
As you can see in the demo above, there's a beautiful animation happening to the AppBar. That's a MorphingAppBar!
You can construct MorphingAppBar (corresponds to AppBar) and MorphingSliverAppBar (corresponds to SliverAppBar) just like the originals:
MorphingAppBar(
backgroundColor: Colors.green,
title: Text('My Page'),
actions: <Widget>[
IconButton(
key: ValueKey('play'),
icon: Icon(Icons.play_arrow),
onPressed: () {},
),
IconButton(
key: ValueKey('favorite'),
icon: Icon(Icons.favorite),
onPressed: () {},
),
PopupMenuButton<void>(
key: ValueKey('overflow'),
itemBuilder: (context) {
return [
PopupMenuItem<void>(child: Text('Overflow action 1')),
PopupMenuItem<void>(child: Text('Overflow action 2')),
];
},
),
],
bottom: TabBar(
tabs: <Widget>[
Tab(text: 'Tab 1'),
Tab(text: 'Tab 2'),
Tab(text: 'Tab 3'),
],
),
)
Both MorphingAppBars internally use Heros, so if you're not navigating directly inside a MaterialApp, you have to add a HeroController to your Navigator:
Navigator(
observers: [HeroController()],
onGenerateRoute: // ...
)
To animate additions, removals, and constants in your AppBars actions, we compare them using Widget.canUpdate(Widget old, Widget new). It compares Widgets based on their type and key, so it's recommended to give every action Widget a key (that you reuse across pages) for correct animations.
Run this command:
With Flutter:
$ flutter pub add swipeable_page_route
This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):
dependencies:
swipeable_page_route: ^0.2.3
Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:swipeable_page_route/swipeable_page_route.dart';
import 'package:black_hole_flutter/black_hole_flutter.dart';
import 'package:flutter/material.dart';
import 'package:swipeable_page_route/swipeable_page_route.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '๐ swipeable_page_route example',
home: FirstPage(),
);
}
}
class FirstPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: MorphingAppBar(
title: Text('๐ swipeable_page_route example'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
context.navigator
.push<void>(SwipeablePageRoute(builder: (_) => SecondPage()));
},
child: Text('Open page 2'),
),
),
);
}
}
class SecondPage extends StatefulWidget {
@override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
@override
Widget build(BuildContext context) {
// Gets the `SwipeablePageRoute` wrapping the current page.
final pageRoute = context.getSwipeablePageRoute<void>()!;
return Scaffold(
appBar: MorphingAppBar(
title: Text('Page 2'),
actions: <Widget>[
IconButton(
key: ValueKey('check'),
icon: Icon(Icons.check),
onPressed: () {},
),
IconButton(
key: ValueKey('star'),
icon: Icon(Icons.star),
onPressed: () {},
),
IconButton(
key: ValueKey('play_arrow'),
icon: Icon(Icons.play_arrow),
onPressed: () {},
),
PopupMenuButton<void>(
itemBuilder: (context) => [
PopupMenuItem(child: Text('One')),
PopupMenuItem(child: Text('Two')),
],
),
],
),
body: SizedBox.expand(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Can swipe: ${pageRoute.canSwipe}'),
TextButton(
onPressed: () {
// You can disable swiping completely using `canSwipe`:
setState(() => pageRoute.canSwipe = !pageRoute.canSwipe);
},
child: Text('Toggle'),
),
SizedBox(height: 32),
ElevatedButton(
onPressed: () {
context.navigator.push<void>(SwipeablePageRoute(
// This option has to be enabled for pages with horizontally
// scrollable content, as otherwise, `SwipeablePageRoute`'s
// swipe-gesture intercepts those gestures in the page. This way,
// only swipes starting from the left (LTR) or right (RTL) screen
// edge can be used to navigate back.
canOnlySwipeFromEdge: true,
// You can customize the width of the detection area with
// `backGestureDetectionWidth`.
builder: (_) => ThirdPage(),
));
},
child: Text('Open page 3'),
),
],
),
),
);
}
}
class ThirdPage extends StatefulWidget {
@override
_ThirdPageState createState() => _ThirdPageState();
}
class _ThirdPageState extends State<ThirdPage>
with SingleTickerProviderStateMixin {
static const _tabCount = 3;
late final TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: _tabCount, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: MorphingAppBar(
backgroundColor: Colors.green,
title: Text('Page 3'),
actions: <Widget>[
IconButton(
key: ValueKey('star'),
icon: Icon(Icons.star),
onPressed: () {},
),
IconButton(
key: ValueKey('play_arrow'),
icon: Icon(Icons.play_arrow),
onPressed: () {},
),
IconButton(
key: ValueKey('favorite'),
icon: Icon(Icons.favorite),
onPressed: () {},
),
PopupMenuButton<void>(
itemBuilder: (context) => [
PopupMenuItem(child: Text('One')),
PopupMenuItem(child: Text('Two')),
],
),
],
bottom: TabBar(
controller: _tabController,
indicatorColor: Colors.white,
isScrollable: true,
tabs: <Widget>[
for (var i = 0; i < _tabCount; i++) Tab(text: 'Tab ${i + 1}'),
],
),
),
body: TabBarView(
controller: _tabController,
children: [
for (var i = 0; i < _tabCount; i++)
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('This is tab ${i + 1}'),
ElevatedButton(
onPressed: () {
context.navigator.push<void>(
SwipeablePageRoute(builder: (_) => SecondPage()),
);
},
child: Text('Open page 2'),
),
],
),
],
),
);
}
}
Author: JonasWanke
Official Website: https://github.com/JonasWanke/swipeable_page_route