【问题标题】: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),
                )

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

【问题讨论】:

  • @JimChiu 正如您在我的代码中看到的,我将列表包装在滚动条小部件中,此滚动仅在我们滚动时出现。我需要的是甚至在滚动之前应该有一个可见的滚动。

标签: flutter dart


【解决方案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...
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-17
        • 2014-07-20
        • 2011-06-05
        • 1970-01-01
        • 2011-11-30
        • 2014-09-21
        • 1970-01-01
        • 2012-11-21
        相关资源
        最近更新 更多