Last active
November 26, 2020 00:47
-
-
Save umaqs/cab0114c55b327fb12b305ec40cb4eba to your computer and use it in GitHub Desktop.
PageView with scrollable tabs
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 'package:flutter/material.dart'; | |
import 'dart:math'; | |
void main() => runApp(MyApp()); | |
Color get randomColor => | |
Colors.primaries[Random().nextInt(Colors.primaries.length)]; | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Flutter Demo', | |
debugShowCheckedModeBanner: false, | |
theme: ThemeData.dark(), | |
home: Scaffold( | |
body: PageViewCard( | |
scrollable: true, | |
items: List.generate( | |
20, | |
(i) => PageViewItem( | |
title: 'Page ${i+1}', | |
content: Container( | |
color: randomColor, | |
), | |
), | |
), | |
), | |
), | |
); | |
} | |
} | |
class PageViewItem { | |
String title; | |
Widget content; | |
PageViewItem({this.title, this.content}); | |
} | |
class PageViewCard extends StatefulWidget { | |
final bool scrollable; | |
final List<PageViewItem> items; | |
PageViewCard({ | |
this.scrollable = false, | |
@required this.items, | |
}); | |
@override | |
_PageViewCardState createState() => _PageViewCardState(); | |
} | |
class _PageViewCardState extends State<PageViewCard> { | |
PageController _controller; | |
int _selectedIndex = 0; | |
Duration _duration = Duration(milliseconds: 500); | |
Curve _curve = Curves.easeInOut; | |
bool _isScrolling; | |
List<GlobalKey> keys; | |
@override | |
void initState() { | |
super.initState(); | |
_controller = PageController(initialPage: 0); | |
keys = widget.items.map((i) => GlobalKey()).toList(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return new Container( | |
decoration: BoxDecoration( | |
color: Theme.of(context).canvasColor, | |
borderRadius: BorderRadius.all( | |
Radius.circular(15), | |
), | |
), | |
padding: const EdgeInsets.all(8), | |
margin: const EdgeInsets.all(12), | |
child: Column( | |
mainAxisSize: MainAxisSize.max, | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: <Widget>[ | |
Flexible( | |
child: NotificationListener<ScrollNotification>( | |
onNotification: _onScroll, | |
child: PageView( | |
controller: _controller, | |
physics: widget.scrollable | |
? const BouncingScrollPhysics() | |
: const NeverScrollableScrollPhysics(), | |
children: widget.items.map((p) => p.content).toList(), | |
onPageChanged: _onPageChanged, | |
), | |
), | |
), | |
Container( | |
height: 50, | |
child: ListView.builder( | |
scrollDirection: Axis.horizontal, | |
itemCount: widget.items.length, | |
physics: BouncingScrollPhysics(), | |
itemBuilder: (ctx, index) { | |
var item = widget.items[index]; | |
return BottomTabCard( | |
key: keys[index], | |
label: item.title, | |
color: _selectedIndex == index | |
? Theme.of(context).accentColor | |
: Colors.white, | |
backgroundColor: _selectedIndex == index | |
? Colors.white30 | |
: Colors.transparent, | |
onPressed: () => _onTabSelected(index), | |
); | |
}, | |
), | |
), | |
], | |
), | |
); | |
} | |
void _onTabSelected(int index) async { | |
await _animateScroll(index); | |
} | |
void _onPageChanged(int index) { | |
setState(() { | |
_selectedIndex = index; | |
}); | |
} | |
bool _onScroll(ScrollNotification notification) { | |
final metrics = notification.metrics; | |
if (metrics is PageMetrics) { | |
setState(() => _selectedIndex = metrics.page.round()); | |
try { | |
Scrollable.ensureVisible( | |
keys[_selectedIndex].currentContext, | |
alignment: 0.5, | |
duration: Duration(milliseconds: 200), | |
curve: Curves.easeInOut, | |
); | |
} catch (e) { | |
print(e); | |
} | |
} | |
return false; | |
} | |
Future<void> _animateScroll(int page) async { | |
setState(() => _isScrolling = true); | |
await _controller.animateToPage( | |
page, | |
duration: _duration, | |
curve: _curve, | |
); | |
setState(() => _isScrolling = false); | |
} | |
} | |
class BottomTabCard extends StatelessWidget { | |
final String label; | |
final Color color; | |
final Color backgroundColor; | |
final Function onPressed; | |
const BottomTabCard( | |
{Key key, this.label, this.color, this.backgroundColor, this.onPressed}) | |
: super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return Padding( | |
padding: const EdgeInsets.symmetric(horizontal: 4), | |
child: ActionChip( | |
backgroundColor: backgroundColor, | |
padding: const EdgeInsets.symmetric(horizontal: 8), | |
label: Text( | |
label, | |
style: TextStyle(color: color, fontWeight: FontWeight.w500), | |
), | |
shape: StadiumBorder( | |
side: BorderSide(color: Colors.transparent, width: 0), | |
), | |
onPressed: onPressed, | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment