如何让滚动条一直可见?

Posted

技术标签:

【中文标题】如何让滚动条一直可见?【英文标题】:How to make scrollbar visible all time? 【发布时间】:2020-07-15 13:28:17 【问题描述】:

如何在用户开始颤动滚动之前使滚动条可见。这是我制作列表的代码

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

将列表包装在滚动条内我可以在滚动时看到滚动条。但是是否有可能让滚动条一直可见?提前致谢。

【问题讨论】:

希望有帮助,flutter-how-to-add-scroll-indicator-in-listview @JimChiu 正如您在我的代码中看到的,我将列表包装在滚动条小部件中,此滚动仅在我们滚动时出现。我需要的是甚至在滚动之前应该有一个可见的滚动。 【参考方案1】:

您可以使用可拖动滚动条包https://pub.dev/packages/draggable_scrollbar 来实现。

有很多可能性,你可以随时使用alwaysVisibleScrollThumb: true,来展示它

例如你可以这样做:

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

另一种方式,最初创建自@slightfoot:https://gist.github.com/slightfoot/beb74749bf2e743a6da294b37a7dcf8d

您可以使用自定义滚动绘制达到始终可见的滚动条,如下例代码所示:

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();
  

【讨论】:

不加这个包不行吗? 我的意思是没有默认的方法来获得它吗?我需要添加上面的包吗? @Marcel Dz @Anu 检查我的答案,有一个不使用包的例子。您好,希望对您有所帮助! @Macel DZ 我尝试了你的代码 太棒了,我使用 mediaquery 将容器更改为屏幕高度为 30% 的列表,但滚动条很大并且没有调整列表项?有什么关系吗? Container( height: MediaQuery.of(context).size.height * 0.3, child: ListView.builder(..) 我这样做而不是 1500 作为高度【参考方案2】:

将您的小部件包装在滚动条小部件中

【讨论】:

【参考方案3】:

切换到flutter分支master

在滚动条中添加 isAlwaysShown: true

Ref

【讨论】:

【参考方案4】:

演示:DartPad

您可以使用ScrollbarPainter。然后使用AlwaysStoppedAnimation&lt;double&gt;(1.0) 使其始终可见,ScrollNotification 更新滚动位置。

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

用法: 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"),
          ),
        ),
      ),
    );
  

注意:

如果ListView 小部件中的一个子小部件是Stateful 小部件并动态更改其大小,则MyScrollbar 可能不会更新。

【讨论】:

这符合我的预期。无法用语言来形容。谢谢! 我有一个在从服务器检索一些数据后填充的列表,所以这个滚动条高度占据了容器的整个高度,直到我滚动。有什么关系吗【参考方案5】:

您可以使用Scrollbar 包装您的ListView,如下所示。当 isAlwaysShown 为 true 时,必须传递一个附加到滚动视图的控制器

Scrollbar(
          controller: ScrollController(),
          isAlwaysShown: true,
          child: ListView...

【讨论】:

以上是关于如何让滚动条一直可见?的主要内容,如果未能解决你的问题,请参考以下文章

如何隐藏水平滚动条并保持垂直滚动条可见,同时仍然能够水平滚动?

如何使滚动条不可见[重复]

如何使 div 上的滚动条仅在必要时可见?

iOS如何让滚动条一直显示

C#如何让panel显示滚动条

如何让ScrollView 的滚动条不显示