Reputation: 630
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.
Upvotes: 5
Views: 10264
Reputation: 4260
You can wrap your ListView
with Scrollbar
like below. When thumbVisibility is true, must pass a controller that is attached to a scroll view
Scrollbar(
controller: ScrollController(),
thumbVisibility: true,
child: ListView...
Upvotes: 9
Reputation: 1926
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,
),
);
},
),
)
Upvotes: 2
Reputation: 15113
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.
Upvotes: 5
Reputation: 592
Switch to flutter branch master
Add isAlwaysShown: true in Scrollbar
Upvotes: 2
Reputation: 2714
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();
}
}
Upvotes: 6