Anu
Anu

Reputation: 630

How to make scrollbar visible all time?

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

Answers (6)

Savas Adar
Savas Adar

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

NirajPhutane
NirajPhutane

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,
        ),
      );
    },
  ),
),

enter image description here

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,
        ),
      );
    },
  ),
)

enter image description here

Upvotes: 2

Crazy Lazy Cat
Crazy Lazy Cat

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

naijab.com
naijab.com

Reputation: 592

Switch to flutter branch master

Add isAlwaysShown: true in Scrollbar

Ref

Upvotes: 2

Javier Gonz&#225;lez
Javier Gonz&#225;lez

Reputation: 2105

wrap your widget in Scrollbar widget

Upvotes: 0

Marcel Dz
Marcel Dz

Reputation: 2714

scrollable

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

Related Questions