Related
I have SingleChildScrollView as a parent and in that, I have two listviews each list view is wrapped with SizedBox with a specific height (like 700), what I want is, when I scroll up all the views that are in the first list, the first Listview should scroll up and then I'll be able to scroll next Listview, Please have a look into the code below.
Your help means a lot to me.
Thank you in advance.
Note: I'm getting this required behavior in chrome but not on a mobile device
SingleChildScrollView( child: Column(children: [
SizedBox(
height: 700,
child:ListView.builder(
itemCount:
20, itemBuilder: (context, index) {
return const ListTile(leading: Icon(Icons.icecream,
color: Colors.amber,), title: Text("Ice Cream"),);
},),
),
SizedBox(
height: 300,
child: ListView.builder(
itemCount: 20, itemBuilder: (context, index) {
return const ListTile(
leading: Icon(Icons.cake, color: Colors.red,),
title: Text("Cake"),);
},),
),
],),)
You Can Do something like this on the Controllers:
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ScrollingBehaviourInDart extends StatefulWidget {
const ScrollingBehaviourInDart({Key? key}) : super(key: key);
#override
State<ScrollingBehaviourInDart> createState() =>
_ScrollingBehaviourInDartState();
}
class _ScrollingBehaviourInDartState extends State<ScrollingBehaviourInDart> {
late ScrollController _sc1;
late ScrollController _sc2;
late ScrollController _sc3;
#override
void initState() {
_sc1 = ScrollController();
_sc2 = ScrollController();
_sc3 = ScrollController();
var _pr = Provider.of<MyScrollProvider>(context, listen: false);
_sc1.addListener(() {
log("SC1::::::::::: " + _sc1.position.pixels.toString());
if (_sc1.position.pixels == _sc1.position.minScrollExtent) {
print("OK");
_pr.changePhysics(enableScrolling: true);
}
});
_sc2.addListener(() {
if (_sc2.offset == _sc2.position.maxScrollExtent) {
_pr.changePhysics(enableScrolling: false);
log("YAAA");
}
});
_sc3.addListener(() {
log("SC3::::::::::: " + _sc3.position.pixels.toString());
});
super.initState();
}
#override
void dispose() {
_sc1.dispose();
_sc2.dispose();
_sc3.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
var _size = MediaQuery.of(context).size;
return SafeArea(
child: Scaffold(
backgroundColor: Colors.white.withOpacity(0.8),
body: SizedBox(
height: _size.height,
child: Consumer<MyScrollProvider>(
builder: (context, myScrollProvider, _) => SingleChildScrollView(
controller: _sc1,
child: Column(
children: [
SizedBox(
height: _size.height * 0.5,
child: ListView.builder(
controller: _sc2,
physics: myScrollProvider.enablePrimaryScroll
? const AlwaysScrollableScrollPhysics()
: const NeverScrollableScrollPhysics(),
itemCount: 20,
shrinkWrap: true,
itemBuilder: (context, index) {
return const ListTile(
leading: Icon(
Icons.icecream,
color: Colors.amber,
),
title: Text("Ice Cream"),
);
},
),
),
ListView.builder(
itemCount: 20,
controller: _sc3,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return const ListTile(
leading: Icon(
Icons.cake,
color: Colors.red,
),
title: Text("Cake"),
);
},
),
],
),
),
),
),
),
);
}
}
class MyScrollProvider extends ChangeNotifier {
var enablePrimaryScroll = true;
changePhysics({required bool enableScrolling}) {
enablePrimaryScroll = enableScrolling;
notifyListeners();
}
}
maybe you can use Stickyheader.
import 'package:sticky_headers/sticky_headers.dart';
ListView(
shrinkwarp:true,
children:[
StickyHeader(
head: Text('List 1 '),
content : ListView.builder(
physics: const ClampingScrollPhysics(), // use this for clamping scroll
itemBuilder: (context, idx) => Container(),
itemCount:5,
)
StickyHeader(
head: Text('List 2 '),
content : ListView.builder(
physics: const ClampingScrollPhysics(), // use this for clamping scroll
itemBuilder: (context, idx) => Container(),
itemCount:5,
)
]
}
There are many easy ways to handle this situation as stated by many other developers. I have created an Example class with ScrollController and AbsordPointer classes to achieve the required behavior.
Sample
class Example extends StatefulWidget {
const Example({super.key});
#override
State<Example> createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
late ScrollController scrollController;
var reachedAtEnd = false;
#override
void initState() {
super.initState();
scrollController =ScrollController()..addListener(() {
if (scrollController.position.pixels == scrollController.position.maxScrollExtent) {
reachedAtEnd = true;
setState(() {
});
}
},);
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
physics: NeverScrollableScrollPhysics(),
child: Column(
children: [
SizedBox(
height: 700,
child: ListView.builder(
controller: scrollController,
itemCount: 20,
itemBuilder: (context, index) {
return const ListTile(
leading: Icon(
Icons.icecream,
color: Colors.amber,
),
title: Text("Ice Cream"),
);
},
),
),
SizedBox(
height: 300,
child: AbsorbPointer(
absorbing: !reachedAtEnd,
child: ListView.builder(
itemCount: 20,
itemBuilder: (context, index) {
return const ListTile(
leading: Icon(
Icons.cake,
color: Colors.red,
),
title: Text("Cake"),
);
},
),
),
),
],
),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
// this variable determnines whether the back-to-top button is shown or not
bool _showBackToTopButton = false;
// scroll controller
late ScrollController _scrollController;
#override
void initState() {
_scrollController = ScrollController()
..addListener(() {
setState(() {
print(_scrollController.offset);
if (_scrollController.offset >= 400) {
_showBackToTopButton = true;
// _scrollToTop();
// show the back-to-top button
} else {
_showBackToTopButton = false; // hide the back-to-top button
}
});
});
super.initState();
}
#override
void dispose() {
_scrollController.dispose(); // dispose the controller
super.dispose();
}
// This function is triggered when the user presses the back-to-top button
void _scrollToTop() {
_scrollController.animateTo(0,
duration: const Duration(seconds: 3), curve: Curves.linear);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('com'),
),
body: ListView.builder(
controller: _scrollController,
itemCount: 40,
itemBuilder: (context, index) {
print(index);
if (index == 39) {
_scrollToTop();
}
return const ListTile(
leading: Icon(
Icons.icecream,
color: Colors.amber,
),
title: Text(' Ice Cream'),
);
},
),
);
}
}
I've a initialScrollOffset in my ScrollController set to 100. This allows the top of my ListView in my Scaffold body to appear under my appBar. (Desired behaviour, my appbar is transparent). This works fine when the page first loads but I'd like my ScrollController to prevent my listView from ever scrolling between 0 and 100 and always have an initialScrollOffset of 100. ScrollController has a boolean property keepScrollOffset` but that doesn't prevent the scroll going from 0-100. Here is my code so far:
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
ScrollController _scrollController;
#override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0.0,
),
body: Container(
color: Colors.blue.withOpacity(0.25),
child: ListView.builder(
itemCount: 30,
itemBuilder: (context, index) {
return Container(
color: Colors.primaries[index % Colors.primaries.length].withOpacity(0.5),
child: ListTile(
title: Text('Item $index'),
),
);
},
controller: _scrollController,
),
),
);
}
#override
void initState() {
_scrollController = ScrollController(initialScrollOffset: 100, keepScrollOffset: true)
..addListener(() {
print("offset = ${_scrollController.offset}");
});
super.initState();
}
}
void main() {
runApp(MaterialApp(home: HomePage()));
}
If you want to add padding to the inner content that scrolls up and down with the items, you can just add padding to the ListView.
padding: EdgeInsets.only(top: 100)
Not necessary to use ScrollController.
How do I make a scrollbar visible before user starts scrolling in flutter. Here is the code how I made my list
Scrollbar(
child: ListView.separated(
itemBuilder:
(ctx,index){
return ListTile(
isThreeLine: false,
dense: true,
leading: IconButton(
icon: Icon(Icons.location_on),
onPressed: null,
),
title: Text(bList[index].bName),
onTap: ()=>_homescreen(bList[index].bId,bList[index].bName,index),
);
},
separatorBuilder: (context,index) => Divider(color: Colors.black,),
itemCount: bList.length == null ? 0 : bList.length),
)
Wrapping the List inside Scrollbar I am able to see a scrollbar on scrolling. But is it possible to make scrollbar visible all time? Thanks in advance.
You can wrap your ListView with Scrollbar like below. When isAlwaysShown is true, must pass a controller that is attached to a scroll view
Scrollbar(
controller: ScrollController(),
isAlwaysShown: true,
child: ListView...
Demo: DartPad
You can use ScrollbarPainter. Then use AlwaysStoppedAnimation<double>(1.0) to make it always visible, ScrollNotification to update the scroll position.
MyScrollbar.dart
import 'package:flutter/material.dart';
const double _kScrollbarThickness = 6.0;
class MyScrollbar extends StatefulWidget {
final ScrollableWidgetBuilder builder;
final ScrollController scrollController;
const MyScrollbar({
Key key,
this.scrollController,
#required this.builder,
}) : assert(builder != null),
super(key: key);
#override
_MyScrollbarState createState() => _MyScrollbarState();
}
class _MyScrollbarState extends State<MyScrollbar> {
ScrollbarPainter _scrollbarPainter;
ScrollController _scrollController;
Orientation _orientation;
#override
void initState() {
super.initState();
_scrollController = widget.scrollController ?? ScrollController();
WidgetsBinding.instance.addPostFrameCallback((_) {
_updateScrollPainter(_scrollController.position);
});
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
_scrollbarPainter = _buildMaterialScrollbarPainter();
}
#override
void dispose() {
_scrollbarPainter.dispose();
super.dispose();
}
ScrollbarPainter _buildMaterialScrollbarPainter() {
return ScrollbarPainter(
color: Theme.of(context).highlightColor.withOpacity(1.0),
textDirection: Directionality.of(context),
thickness: _kScrollbarThickness,
fadeoutOpacityAnimation: const AlwaysStoppedAnimation<double>(1.0),
padding: MediaQuery.of(context).padding,
);
}
bool _updateScrollPainter(ScrollMetrics position) {
_scrollbarPainter.update(
position,
position.axisDirection,
);
return false;
}
#override
void didUpdateWidget(MyScrollbar oldWidget) {
super.didUpdateWidget(oldWidget);
_updateScrollPainter(_scrollController.position);
}
#override
Widget build(BuildContext context) {
return OrientationBuilder(
builder: (context, orientation) {
_orientation ??= orientation;
if (orientation != _orientation) {
_orientation = orientation;
_updateScrollPainter(_scrollController.position);
}
return NotificationListener<ScrollNotification>(
onNotification: (notification) =>
_updateScrollPainter(notification.metrics),
child: CustomPaint(
painter: _scrollbarPainter,
child: widget.builder(context, _scrollController),
),
);
},
);
}
}
Usage: main.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'MyScrollbar.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Home(),
);
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: MyScrollbar(
//scrollController: ctrl, //You can assign your scroll controller here or ignore
builder: (context, scrollController) => ListView.builder(
controller: scrollController, //should scrollController from callback
itemCount: 30,
itemBuilder: (context, index) => ListTile(
title: Text("Index $index"),
),
),
),
);
}
}
Note:
If one the child in ListView widget is Stateful widget and dynamically changes its size, then the MyScrollbar may not get updated.
you can use draggable scrollbar package https://pub.dev/packages/draggable_scrollbar to achieve this.
There are many possabilitys, you can show it always by using alwaysVisibleScrollThumb: true,
Fore example you can do:
DraggableScrollbar.rrect(
controller: myScrollController,
child: ListView.builder(
controller: myScrollController,
itemCount: 1000,
itemExtent: 100.0,
itemBuilder: (context, index) {
return Container(
padding: EdgeInsets.all(8.0),
child: Material(
elevation: 4.0,
borderRadius: BorderRadius.circular(4.0),
color: Colors.green[index % 9 * 100],
child: Center(
child: Text(index.toString()),
),
),
);
},
),
);
Another way of doing it, originally created from #slightfoot: https://gist.github.com/slightfoot/beb74749bf2e743a6da294b37a7dcf8d
You can reach always visible scrollbar with custom scroll paint like in the following example code:
import 'package:flutter/gestures.dart' show DragStartBehavior;
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.indigo,
accentColor: Colors.pinkAccent,
),
home: ExampleScreen(),
),
);
}
class ExampleScreen extends StatefulWidget {
#override
_ExampleScreenState createState() => _ExampleScreenState();
}
class _ExampleScreenState extends State<ExampleScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SingleChildScrollView With Scrollbar'),
),
body:
Container(
height: MediaQuery.of(context).size.height * 0.3,
child:
SingleChildScrollViewWithScrollbar(
scrollbarColor: Theme.of(context).accentColor.withOpacity(0.75),
scrollbarThickness: 8.0,
child: Container(
//height: 1500,
child: ListView(
shrinkWrap: true,
children: <Widget>[
ListTile(title: Text('Item 1')),
ListTile(title: Text('Item 2')),
ListTile(title: Text('Item 3')),
ListTile(title: Text('Item 4')),
ListTile(title: Text('Item 5')),
ListTile(title: Text('Item 6')),
ListTile(title: Text('Item 1')),
ListTile(title: Text('Item 2')),
ListTile(title: Text('Item 3')),
ListTile(title: Text('Item 4')),
ListTile(title: Text('Item 5')),
ListTile(title: Text('Item 6')),
],
),
),
),),
);
}
}
class SingleChildScrollViewWithScrollbar extends StatefulWidget {
const SingleChildScrollViewWithScrollbar({
Key key,
this.scrollDirection = Axis.vertical,
this.reverse = false,
this.padding,
this.primary,
this.physics,
this.controller,
this.child,
this.dragStartBehavior = DragStartBehavior.down,
this.scrollbarColor,
this.scrollbarThickness = 6.0,
}) : super(key: key);
final Axis scrollDirection;
final bool reverse;
final EdgeInsets padding;
final bool primary;
final ScrollPhysics physics;
final ScrollController controller;
final Widget child;
final DragStartBehavior dragStartBehavior;
final Color scrollbarColor;
final double scrollbarThickness;
#override
_SingleChildScrollViewWithScrollbarState createState() => _SingleChildScrollViewWithScrollbarState();
}
class _SingleChildScrollViewWithScrollbarState extends State<SingleChildScrollViewWithScrollbar> {
AlwaysVisibleScrollbarPainter _scrollbarPainter;
#override
void didChangeDependencies() {
super.didChangeDependencies();
rebuildPainter();
}
#override
void didUpdateWidget(SingleChildScrollViewWithScrollbar oldWidget) {
super.didUpdateWidget(oldWidget);
rebuildPainter();
}
void rebuildPainter() {
final theme = Theme.of(context);
_scrollbarPainter = AlwaysVisibleScrollbarPainter(
color: widget.scrollbarColor ?? theme.highlightColor.withOpacity(1.0),
textDirection: Directionality.of(context),
thickness: widget.scrollbarThickness,
);
}
#override
void dispose() {
_scrollbarPainter?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return RepaintBoundary(
child: CustomPaint(
foregroundPainter: _scrollbarPainter,
child: RepaintBoundary(
child: SingleChildScrollView(
scrollDirection: widget.scrollDirection,
reverse: widget.reverse,
padding: widget.padding,
primary: widget.primary,
physics: widget.physics,
controller: widget.controller,
dragStartBehavior: widget.dragStartBehavior,
child: Builder(
builder: (BuildContext context) {
_scrollbarPainter.scrollable = Scrollable.of(context);
return widget.child;
},
),
),
),
),
);
}
}
class AlwaysVisibleScrollbarPainter extends ScrollbarPainter {
AlwaysVisibleScrollbarPainter({
#required Color color,
#required TextDirection textDirection,
#required double thickness,
}) : super(
color: color,
textDirection: textDirection,
thickness: thickness,
fadeoutOpacityAnimation: const AlwaysStoppedAnimation(1.0),
);
ScrollableState _scrollable;
ScrollableState get scrollable => _scrollable;
set scrollable(ScrollableState value) {
_scrollable?.position?.removeListener(_onScrollChanged);
_scrollable = value;
_scrollable?.position?.addListener(_onScrollChanged);
_onScrollChanged();
}
void _onScrollChanged() {
update(_scrollable.position, _scrollable.axisDirection);
}
#override
void dispose() {
_scrollable?.position?.removeListener(notifyListeners);
super.dispose();
}
}
Switch to flutter branch master
Add isAlwaysShown: true in Scrollbar
Ref
wrap your widget in Scrollbar widget
For Scrollbar or RawScrollbar visible all time, use thumbVisibility: true (isAlwaysShown: true is deprecated)
By using Scrollbar For Custom Scroll Bar,
**ScrollController controller = ScrollController(); // Mandatory: ScrollController**
Scrollbar(
controller: controller, // Mandatory: ScrollController
// isAlwaysShown: true, // deprecated
thumbVisibility: true, // For always showing Scroll Bar: Use this
thickness: 10, // Optional: Thickness
radius: Radius.circular(5), // Optional: Radius
child: ListView.builder(
itemCount: 100,
controller: controller, // Mandatory: ScrollController
itemBuilder: (context, index){
return ListTile(
leading: CircleAvatar(
backgroundColor: Colors.deepOrangeAccent,
child: FlutterLogo(),
),
title: Text('Title-$index'),
subtitle: Text('Subtitle-$index'),
trailing: CircleAvatar(
child: Icon(Icons.navigate_next),
backgroundColor: Colors.deepOrangeAccent,
),
);
},
),
),
By using RawScrollbar For Custom Scroll Bar,
ScrollController controller = ScrollController(); // Mandatory: ScrollController
RawScrollbar(
controller: controller, // Mandatory: ScrollController
// isAlwaysShown: true, // deprecated
thumbVisibility: true, // For always showing Scroll Bar: Use this
thickness: 10, // Optional: Thickness
thumbColor: Colors.greenAccent, // Optional: Color
radius: Radius.circular(5), // Optional: Radius
child: ListView.builder(
itemCount: 100,
controller: controller, // Mandatory: ScrollController
itemBuilder: (context, index){
return ListTile(
leading: CircleAvatar(
backgroundColor: Colors.deepOrangeAccent,
child: FlutterLogo(),
),
title: Text('Title-$index'),
subtitle: Text('Subtitle-$index'),
trailing: CircleAvatar(
child: Icon(Icons.navigate_next),
backgroundColor: Colors.deepOrangeAccent,
),
);
},
),
)
Is there any built in widget in flutter to implement a widget like(red border in picture below) ?
which we can drag it change the value.
That's simply a horizontal ListView with predefined height.
SizedBox(
height: 56, // height of ListView
child: ListView.builder(
scrollDirection: Axis.horizontal, // makes it scroll in horizontal direction
itemBuilder: (_, index) {
// build your years
},
),
)
The desired output can be brought using the ListView.
Flutter just uploaded a video on ListView in which you can find your answer:
https://www.youtube.com/watch?v=KJpkjHGiI5A
Example code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
bool change ;
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int i=0;
void fxn(){
setState(() {
i++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('List View'),),
body: ListView.builder(
itemCount: 30,
itemBuilder: (_, index)=>Padding(
padding: const EdgeInsets.fromLTRB(0, 270, 0, 270),
child: MaterialButton(
child:
Text('200$index',style: TextStyle(fontSize: 25),),
onPressed: (){
},
color: Colors.white70,
),
),
scrollDirection: Axis.horizontal,
)
);
}
}
Output: Output
I'm making an app divided to two sections: one(upper section) is PageView widget area, another(lower section) is Container widget area. I want the lower section to show 'we are in X page' when I change pages in the upper section.
I tried to use index of PageView widget in Container widget, but console said "undefined name 'index'".
So I declared like int index; as a global variable, and tried again, but it doesn't work. I think this index is different from index of PageView widget.
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final controller = PageController(initialPage: 0);
var scrollDirection = Axis.horizontal;
var actionIcon = Icons.swap_vert;
int index;
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
centerTitle: true,
title: Text('it\'s a drill for page view'),
),
body: _buildBody(),
);
}
Widget _buildBody() {
return SafeArea(
child: Column(
children: <Widget>[
Expanded(
child: PageView.builder(
controller: controller,
itemCount: 5,
itemBuilder: (context, index) {
return Text('it is ${index} page');
},
)
),
Expanded(
child: FittedBox(
fit: BoxFit.fitWidth,
child: Container(
color: Colors.blue,
child: Text('we are in ${index} page!'),
),
),
)
],
),
);
}
}
I'm a beginner of programming, and doing this as a hobby.
But I really like it. Actually I gave up my own study and career and stick to programming now. I hope you help me solve this problem.
Thank you. I love you.
yes. like controller.page for the current page.
class Sample extends StatelessWidget{
final int value;
Sample(this.value);
build(context) => Text("you are in $value");
}
and use Sample(controller.page)
EDIT: your code should be
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final controller = PageController(initialPage: 0);
var scrollDirection = Axis.horizontal;
var actionIcon = Icons.swap_vert;
int currentPage=0;
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
centerTitle: true,
title: Text('it\'s a drill for page view'),
),
body: _buildBody(),
);
}
Widget _buildBody() {
return SafeArea(
child: Column(
children: <Widget>[
Expanded(
child: PageView.builder(
controller: controller,
itemCount: 5,
itemBuilder: (context, index) {
return Text('it is ${index} page');
},
onPageChanged: (page){
setState(() {
currentPage=page;
});
},
)
),
Expanded(
child: FittedBox(
fit: BoxFit.fitWidth,
child: Container(
color: Colors.blue,
child: Text('we are in ${currentPage} page!'),
),
),
)
],
),
);
}
}
Just add listener to PageController like that:
#override
void initState() {
super.initState();
index = 0;
controller.addListener(() {
setState(() {
index = controller.page.toInt();
});
});
}