如何让滚动条一直可见?
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<double>(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...
【讨论】:
以上是关于如何让滚动条一直可见?的主要内容,如果未能解决你的问题,请参考以下文章