PageView with pages in CustomScrollView with Flutter - flutter

I am trying to place a PageView which pages are scrollable vertically into a CustomScrollView.
The reason for this setup is that I want to make use of SliverAppBar. If one starts scrolling vertically, the AppBar starts shrinking and ultimately docks at the top of the screen. At the same time the currently active page's content is expanded. Once the AppBar is docked and the page's content reached it's maximum allowed size, it itself starts scrolling.
I am running into all sorts of problem, mostly issues with unbounded sizes.

The answer you've found as you've mentioned in the comments is correct. Instead of a CustomScrollView, NestedScrollView should be used because there are multiple scrollable views that will be managed on the screen. As mentioned in the docs, the most common use case for NestedScrollView is a scrollable view with a flexible SliverAppBar containing a TabBar in the header - similar to your use case.
Here's a working sample based from the snippet provided in the post you've referenced.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
var _scrollController, _tabController;
#override
void initState() {
_scrollController = ScrollController();
_tabController = TabController(vsync: this, length: 2);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
controller: _scrollController,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
title: Text(widget.title),
pinned: true,
floating: true,
snap: false,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
tabs: <Tab>[
Tab(text: "Page 1"),
Tab(text: "Page 2"),
],
controller: _tabController,
),
),
];
},
body: TabBarView(
controller: _tabController,
children: <Widget>[
_pageView(),
_pageView(),
],
),
),
);
}
_pageView() {
return ListView.builder(
itemCount: 20,
itemBuilder: (BuildContext context, int index) {
return Card(
child: Container(
padding: EdgeInsets.all(16.0),
child: Text('List Item $index'),
),
);
},
);
}
}
Demo

Related

Flutter - Slidable on whole page?

I'm trying to implement a sliding option that will basically, depending on if I go left or right, direct me to the previous/next message in my app (aka. trigger an action). It should work on the entire page, similar to how tinder slide left/right works. Is there any way to do this in flutter?
I've looked into the flutter_slidable but I'm not sure if I can make the sliding work on the whole page.
Would appreciate some help, thanks in advance!
You can use TabBarView for this:
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
void main()
{
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Learning',
theme: ThemeData(
primarySwatch: Colors.green,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState()
{
return _MyHomePageState();
}
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: [
Tab(text: "Flights", icon: Icon(Icons.flight),),
Tab(text: "Trains", icon: Icon(Icons.train)),
Tab(text: "Hotels",icon: Icon(Icons.restaurant)),
],
),
title: Text('Flutter TabBar'),
),
body: TabBarView(
children: const <Widget>[
Center(
child: Text("Flights"),
),
Center(
child: Text("Trains"),
),
Center(
child: Text("Hotels"),
),
],
),
),
);
}
}
Link
Flutter PageView is used to slide pages either vertically or horizontally.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('PageView ')),
body: const MyStatelessWidget(),
),
);
}
}
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final PageController controller = PageController();
return PageView(
/// [PageView.scrollDirection] defaults to [Axis.horizontal].
/// Use [Axis.vertical] to scroll vertically.
scrollDirection: Axis.horizontal,
controller: controller,
children: const <Widget>[
Center(
child: Text('First Page'),
),
Center(
child: Text('Second Page'),
),
Center(
child: Text('Third Page'),
)
],
);
}
}

Issue with nested scroll when both scroll are always visible (Flutter Stable Channel)

The issue is on web when we try to scroll the vertically scrollable widget which is a child of horizontally scrollable widget where scroll bar is always visible.
When we scroll the horizontally scrollable widget the scroll bar for horizontal scroll is correctly displayed as shown in picture
horizontal scroll
But when we scroll the vertically scrollable widget the horizontal scrollbar disappears and new vertical scroll bar is seen at right as shown in screenshot
vertical scroll
Code to reproduce the issue.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Scroll Issue Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Scroll Issue Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final ScrollController _horizontalController = ScrollController();
final List<ScrollController> _verticalController = [];
#override
void initState() {
super.initState();
for (int i = 0; i < 6; i++) {
_verticalController.add(ScrollController());
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Row(
children: <Widget>[
Expanded(
child: _buildHorizontalScroll(),
)
],
),
);
}
Widget _buildHorizontalScroll() {
return Scrollbar(
isAlwaysShown: true,
controller: _horizontalController,
notificationPredicate: (predicate) {
return predicate.depth == 0;
},
child: ListView.separated(
controller: _horizontalController,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) => Container(
margin: const EdgeInsets.all(30),
width: 300,
color: Colors.black12,
child: Column(
children: [
Expanded(
child: _buildVerticalScroll(index),
)
],
),
),
separatorBuilder: (context, index) => const Divider(
height: 30,
),
itemCount: 6),
);
}
Widget _buildVerticalScroll(int index) {
return Scrollbar(
scrollbarOrientation: ScrollbarOrientation.right,
isAlwaysShown: true,
controller: _verticalController[index],
child: ListView.separated(
controller: _verticalController[index],
itemBuilder: (context, index) => Container(
margin: const EdgeInsets.all(30),
height: 100,
color: Colors.green,
),
separatorBuilder: (context, index) => const Divider(
height: 30,
),
itemCount: 200),
);
}
}
Checking the notification predicate in the horizontal scroll bar, it detects the depth of scroll as both 0 and 1 when scrolling the vertically scrollable list. Is there a workaround for this problem? The problem started to appear since the upgrade to 2.5.0 for stable channel of flutter.
Maybe this package will help you easily customize your thumb and scrolling track both horizontally and vertically
https://pub.dev/packages/cross_scroll

PageView widget in vertical wrapped in Scrollbar Widget

I'm trying to implement a list of image arranged in a column vertically. I used PageView Widget to make use of the PageChange listener to indicate what current page I currently am. However I am implementing this in WebView as well so I need to wrap it in ScrollBar. Although the Scrollbar is showing and moving accordingly, it is not draggable or not automatically scrolling if I used the ScrollBar. Is theres a work around this or a solution?
You have to pass the controller for the Scrollbar widget with the same controller of PageView widget.
Define a PageController
final PageController pageController = PageController(initialPage: 0);
Pass the pageController to the controller property of Scrollbar widget.
return Scrollbar(
controller: pageController,
isAlwaysShown: true, // This forces the scrollbar to be visible always
child: PageView(
scrollDirection: Axis.vertical,
controller: pageController,
You can set other properties like thickness and hoverthickness to addjust the thickness of the scroll bar. Refer this youtube video and documentation for more details.
A working demo. Also available as a dartpad here:
/// Flutter code sample for PageView
// Here is an example of [PageView]. It creates a centered [Text] in each of the three pages
// which scroll horizontally.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
/// This is the main application widget.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatelessWidget(),
),
);
}
}
/// This is the stateless widget that the main application instantiates.
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final PageController pageController = PageController(initialPage: 0);
return Scrollbar(
controller: pageController,
isAlwaysShown: true,
thickness: 10,
showTrackOnHover: true,
hoverThickness: 15,
radius: Radius.circular(0),
child: PageView(
scrollDirection: Axis.vertical,
controller: pageController,
children: const <Widget>[
Center(
child: Text('First Page'),
),
Center(
child: Text('Second Page'),
),
Center(
child: Text('Third Page'),
)
],
),
);
}
}

Scroll to end with a ListView inside TabView inside NestedScrollView with Flutter

I have a simple question: how to initialize the scroll position of a ListView widget, child of a TabView inside a NestedScrollView?
My application has different parts:
a NestedScrollView with a SliverAppBar containing a TabBar
the body contains the TabView with the both tabs
the 1st tab is a ListView of 100 items
the 2nd tab is a Column with a ListView of 100 items + 1 TextField (like a Chat application)
I'm trying to initialize the scroll position of the ListView of the 2nd tab to display the last items. But:
I can't use the ListView controller variable (to keep the sync of the NestedScrollView widget)
If I access the PrimaryScrollController in the initState of Tab2, this error occurs : "ScrollController attached to multiple scroll views."
I can't use the "reverse:true" parameter of the ListView widget because it doesn't work inside a NestedScrollView
Help Me !! Please !
Here is my code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyPage(),
);
}
}
class MyPage extends StatefulWidget {
#override
_MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: DefaultTabController(
length: 2,
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) => [
SliverAppBar(
pinned: true,
title: Text("Test Application"),
expandedHeight: 200,
bottom: TabBar(
tabs: [
Tab(icon: Icon(Icons.info), text: 'Tab 1'),
Tab(icon: Icon(Icons.chat), text: 'Tab 2'),
],
),
),
],
body: TabBarView(
children: [
ListView.builder(
itemCount: 100,
itemBuilder: (context, index) => Text("ITEM $index FROM TAB 1"),
),
Tab2(),
],
),
),
)
);
}
}
class Tab2 extends StatefulWidget {
#override
_Tab2State createState() => _Tab2State();
}
class _Tab2State extends State<Tab2> {
#override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: ListView.builder(
itemCount: 100,
itemBuilder: (context, index) => Text("ITEM $index FROM TAB 2"),
)
),
TextField()
],
);
}
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
ScrollController controller = PrimaryScrollController.of(context);
controller.jumpTo(controller.position.maxScrollExtent);
});
}
}
I think I have found the solution: I just have to replace this string:
controller.jumpTo(controller.position.maxScrollExtent)
by this string:
controller.jumpTo(controller.positions.last.maxScrollExtent)

How to implement a right navbar in flutter?

The flutter scaffold shows a right navbar, but I suppose there is no right nav widget. How do I implement a right navbar with scaffold in flutter?
Flutter Scaffold Image
The Scaffold now has a endDrawer property which swipes from right-to-left.
Hope this might help someone.
If you are trying to show a right bar/menu or Drawer in your app, whether it is has a permanent view or a temporary one. I was able to achieve this by building my own custom widget from Allign, Container and Column widgets, and by using setState to show or hide the menu bar based on user interaction, see this simple example.
My custom menu widget looks like the following:
class RightNavigationBar extends StatefulWidget {
#override
_RightNavigationBarState createState() => new _RightNavigationBarState();
}
class _RightNavigationBarState extends State<RightNavigationBar> {
#override
Widget build(BuildContext context) {
return new Align(
alignment: FractionalOffset.centerRight,
child: new Container(
child: new Column(
children: <Widget>[
new Icon(Icons.navigate_next),
new Icon(Icons.close),
new Text ("More items..")
],
),
color: Colors.blueGrey,
height: 700.0,
width: 200.0,
),
);
}
}
Then when the user presses the menu icon, an object of my custom RightNavigationBar widget is created inside setState :
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
var _myRightBar = null;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
actions: [new IconButton(
icon: new Icon (Icons.menu), onPressed: _showRightBar)
],
title: new Text("Right Navigation Bar Example"),
),
body: _myRightBar
);
}
_showRightBar() {
setState(() {
_myRightBar == null
? _myRightBar = new RightNavigationBar()
: _myRightBar = null;
});
}
}
vertical_navigation_bar
How to use it? #
Install
dependencies:
vertical_navigation_bar: ^0.0.1
Run flutter command
flutter pub get
import 'package:flutter/material.dart';
import 'package:vertical_navigation_bar/vertical_navigation_bar.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Abubakr Elghazawy (Software Developer)',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final pageController = PageController(
initialPage: 0,
keepPage: true
);
final navItems = [
SideNavigationItem(icon: FontAwesomeIcons.calendarCheck, title: "New task"),
SideNavigationItem(icon: FontAwesomeIcons.calendarAlt, title: "Personal task"),
SideNavigationItem(icon: FontAwesomeIcons.fileAlt, title: "Personal document"),
SideNavigationItem(icon: FontAwesomeIcons.calendar, title: "Company task"),
SideNavigationItem(icon: FontAwesomeIcons.arrowCircleRight, title: "Options")
];
final initialTab = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: <Widget>[
SideNavigation(
navItems: this.navItems,
itemSelected: (index){
pageController.animateToPage(
index,
duration: Duration(milliseconds: 300),
curve: Curves.linear
);
},
initialIndex: 0,
actions: <Widget>[
],
),
Expanded(
child: PageView.builder(
itemCount: 5,
controller: pageController,
scrollDirection: Axis.vertical,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index){
return Container(
color: Colors.blueGrey.withOpacity(0.1),
child: Center(
child: Text("Page " + index.toString()),
)
);
},
),
)
],
),
);
}
}
More Dtailsenter link description here
Use the endDrawer property of the Scaffold like this:
Scaffold(
resizeToAvoidBottomInset: false,
key: _scaffoldKey,
endDrawer: const SideBar(),
body: CustomScrollView(