Skip to content

Instantly share code, notes, and snippets.

@umaqs
Last active November 26, 2020 00:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save umaqs/cab0114c55b327fb12b305ec40cb4eba to your computer and use it in GitHub Desktop.
Save umaqs/cab0114c55b327fb12b305ec40cb4eba to your computer and use it in GitHub Desktop.
PageView with scrollable tabs
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