Instantly share code, notes, and snippets.
Forked from HansMuller/navigator_scroll_fade_demo.dart
Last active
April 15, 2021 17:54
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save evoL/72ca1355d71421e18ae3715175d4cb96 to your computer and use it in GitHub Desktop.
Demo updated to support null safety with Dart 2.12
This file contains 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 'package:flutter/material.dart'; | |
import 'package:flutter/rendering.dart'; | |
class Destination { | |
const Destination(this.index, this.title, this.icon, this.color); | |
final int index; | |
final String title; | |
final IconData icon; | |
final MaterialColor color; | |
} | |
const List<Destination> allDestinations = <Destination>[ | |
Destination(0, 'Home', Icons.home, Colors.teal), | |
Destination(1, 'Business', Icons.business, Colors.cyan), | |
Destination(2, 'School', Icons.school, Colors.orange), | |
Destination(3, 'Flight', Icons.flight, Colors.blue) | |
]; | |
class RootPage extends StatelessWidget { | |
const RootPage({ Key? key, required this.destination }) : super(key: key); | |
final Destination destination; | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: Text(destination.title), | |
backgroundColor: destination.color, | |
), | |
backgroundColor: destination.color[50], | |
body: SizedBox.expand( | |
child: InkWell( | |
onTap: () { | |
Navigator.pushNamed(context, "/list"); | |
}, | |
child: Center( | |
child: Text('tap here'), | |
), | |
), | |
), | |
); | |
} | |
} | |
class ListPage extends StatelessWidget { | |
const ListPage({ Key? key, required this.destination }) : super(key: key); | |
final Destination destination; | |
@override | |
Widget build(BuildContext context) { | |
const List<int> shades = <int>[50, 100, 200, 300, 400, 500, 600, 700, 800, 900]; | |
return Scaffold( | |
appBar: AppBar( | |
title: Text(destination.title), | |
backgroundColor: destination.color, | |
), | |
backgroundColor: destination.color[50], | |
body: SizedBox.expand( | |
child: ListView.builder( | |
itemCount: shades.length, | |
itemBuilder: (BuildContext context, int index) { | |
return SizedBox( | |
height: 128, | |
child: Card( | |
color: destination.color[shades[index]]!.withOpacity(0.25), | |
child: InkWell( | |
onTap: () { | |
Navigator.pushNamed(context, "/text"); | |
}, | |
child: Center( | |
child: Text('Item $index', style: Theme.of(context).primaryTextTheme.headline4), | |
), | |
), | |
), | |
); | |
}, | |
), | |
), | |
); | |
} | |
} | |
class TextPage extends StatefulWidget { | |
const TextPage({ Key? key, required this.destination }) : super(key: key); | |
final Destination destination; | |
@override | |
_TextPageState createState() => _TextPageState(); | |
} | |
class _TextPageState extends State<TextPage> { | |
late final TextEditingController _textController; | |
@override | |
void initState() { | |
super.initState(); | |
_textController = TextEditingController( | |
text: 'sample text: ${widget.destination.title}', | |
); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: Text(widget.destination.title), | |
backgroundColor: widget.destination.color, | |
), | |
backgroundColor: widget.destination.color[50], | |
body: Container( | |
padding: const EdgeInsets.all(32.0), | |
alignment: Alignment.center, | |
child: TextField(controller: _textController), | |
), | |
); | |
} | |
@override | |
void dispose() { | |
_textController.dispose(); | |
super.dispose(); | |
} | |
} | |
class ViewNavigatorObserver extends NavigatorObserver { | |
ViewNavigatorObserver(this.onNavigation); | |
final VoidCallback onNavigation; | |
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) { | |
onNavigation(); | |
} | |
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) { | |
onNavigation(); | |
} | |
} | |
class DestinationView extends StatefulWidget { | |
const DestinationView({ Key? key, required this.destination, this.onNavigation }) : super(key: key); | |
final Destination destination; | |
final VoidCallback? onNavigation; | |
@override | |
_DestinationViewState createState() => _DestinationViewState(); | |
} | |
class _DestinationViewState extends State<DestinationView> { | |
@override | |
Widget build(BuildContext context) { | |
return Navigator( | |
observers: <NavigatorObserver>[ | |
if (widget.onNavigation != null) ViewNavigatorObserver(widget.onNavigation!), | |
], | |
onGenerateRoute: (RouteSettings settings) { | |
return MaterialPageRoute( | |
settings: settings, | |
builder: (BuildContext context) { | |
switch(settings.name) { | |
case '/': | |
return RootPage(destination: widget.destination); | |
case '/list': | |
return ListPage(destination: widget.destination); | |
case '/text': | |
return TextPage(destination: widget.destination); | |
default: | |
throw ArgumentError("Invalid route"); | |
} | |
}, | |
); | |
}, | |
); | |
} | |
} | |
class HomePage extends StatefulWidget { | |
@override | |
_HomePageState createState() => _HomePageState(); | |
} | |
class _HomePageState extends State<HomePage> with TickerProviderStateMixin<HomePage> { | |
late final List<Key> _destinationKeys; | |
late final List<AnimationController> _faders; | |
late final AnimationController _hide; | |
int _currentIndex = 0; | |
@override | |
void initState() { | |
super.initState(); | |
_faders = allDestinations.map<AnimationController>((Destination destination) { | |
return AnimationController(vsync: this, duration: Duration(milliseconds: 200)); | |
}).toList(); | |
_faders[_currentIndex].value = 1.0; | |
_destinationKeys = List<Key>.generate(allDestinations.length, (int index) => GlobalKey()).toList(); | |
_hide = AnimationController(vsync: this, duration: kThemeAnimationDuration); | |
} | |
@override | |
void dispose() { | |
for (AnimationController controller in _faders) | |
controller.dispose(); | |
_hide.dispose(); | |
super.dispose(); | |
} | |
bool _handleScrollNotification(ScrollNotification notification) { | |
if (notification.depth == 0) { | |
if (notification is UserScrollNotification) { | |
final UserScrollNotification userScroll = notification; | |
switch (userScroll.direction) { | |
case ScrollDirection.forward: | |
_hide.forward(); | |
break; | |
case ScrollDirection.reverse: | |
_hide.reverse(); | |
break; | |
case ScrollDirection.idle: | |
break; | |
} | |
} | |
} | |
return false; | |
} | |
@override | |
Widget build(BuildContext context) { | |
return NotificationListener<ScrollNotification>( | |
onNotification: _handleScrollNotification, | |
child: Scaffold( | |
body: SafeArea( | |
top: false, | |
child: Stack( | |
fit: StackFit.expand, | |
children: allDestinations.map((Destination destination) { | |
final Widget view = FadeTransition( | |
opacity: _faders[destination.index].drive(CurveTween(curve: Curves.fastOutSlowIn)), | |
child: KeyedSubtree( | |
key: _destinationKeys[destination.index], | |
child: DestinationView( | |
destination: destination, | |
onNavigation: () { | |
_hide.forward(); | |
}, | |
), | |
), | |
); | |
if (destination.index == _currentIndex) { | |
_faders[destination.index].forward(); | |
return view; | |
} else { | |
_faders[destination.index].reverse(); | |
if (_faders[destination.index].isAnimating) { | |
return IgnorePointer(child: view); | |
} | |
return Offstage(child: view); | |
} | |
}).toList(), | |
), | |
), | |
bottomNavigationBar: ClipRect( | |
child: SizeTransition( | |
sizeFactor: _hide, | |
axisAlignment: -1.0, | |
child: BottomNavigationBar( | |
currentIndex: _currentIndex, | |
onTap: (int index) { | |
setState(() { | |
_currentIndex = index; | |
}); | |
}, | |
items: allDestinations.map((Destination destination) { | |
return BottomNavigationBarItem( | |
icon: Icon(destination.icon), | |
backgroundColor: destination.color, | |
label: destination.title | |
); | |
}).toList(), | |
), | |
), | |
), | |
), | |
); | |
} | |
} | |
void main() { | |
runApp(MaterialApp(home: HomePage())); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment