- Flutter和Dart系列文章和代码GitHub地址
- 在
Flutter
中, 当内容超过显示视图时,如果没有特殊处理,Flutter
则会提示Overflow
错误 Flutter
提供了多种可滚动(Scrollable Widget
)用于显示列表和长布局- 可滚动
Widget
都直接或间接包含一个Scrollable
, 下面是常用的几个可滚动的Widget
SingleChildScrollView
ListView
GridView
CustomScrollView
- 滚动监听及控制
ScrollController
Scrollbar
Scrollbar
是一个Material
风格的滚动指示器(滚动条),如果要给可滚动widget
添加滚动条,只需将Scrollbar
作为可滚动widget
的父widget
即可CupertinoScrollbar
是iOS
风格的滚动条,如果你使用的是Scrollbar
,那么在iOS
平台它会自动切换为CupertinoScrollbar
Scrollbar
和CupertinoScrollbar
都是通过ScrollController
来监听滚动事件来确定滚动条位置,关于ScrollController
详细的内容我们将在后面专门一节介绍- 下面是
Scrollbar
和CupertinoScrollbar
的构造函数, 都只有一个child
属性, 用于接受一个可滚动的Widget
const Scrollbar({
Key key,
@required this.child,
})
const CupertinoScrollbar({
Key key,
@required this.child,
})
主轴和纵轴
- 在可滚动
widget
的坐标描述中,通常将滚动方向称为主轴,非滚动方向称为纵轴。 - 由于可滚动
widget
的默认方向一般都是沿垂直方向,所以默认情况下主轴就是指垂直方向,水平方向同理
SingleChildScrollView
SingleChildScrollView
类似于开发中常用的ScrollView
, 不再详细介绍了, 下面看一下具体使用介绍吧
const SingleChildScrollView({
Key key,
// 设置滚动的方向, 默认垂直方向
this.scrollDirection = Axis.vertical,
// 设置显示方式
this.reverse = false,
// 内边距
this.padding,
// 是否使用默认的controller
bool primary,
// 设置可滚动Widget如何响应用户操作
this.physics,
this.controller,
this.child,
})
scrollDirection
设置视图的滚动方向(默认垂直方向), 需要对应的设置其子Widget
是Column
或者Row
, 否则会报Overflow
错误
scrollDirection: Axis.vertical,
// 枚举值
enum Axis {
/// 水平滚动
horizontal,
/// 垂直滚动
vertical,
}
reverse
- 是否按照阅读方向相反的方向滑动
- 设置水平滚动时
- 若
reverse: false
,则滚动内容头部和左侧对其, 那么滑动方向就是从左向右 reverse: true
时,则滚动内容尾部和右侧对其, 那么滑动方向就是从右往左。
- 若
- 其实此属性本质上是决定可滚动
widget
的初始滚动位置是在头还是尾,取false
时,初始滚动位置在头,反之则在尾
physics
- 此属性接受一个
ScrollPhysics
对象,它决定可滚动Widget
如何响应用户操作 - 比如用户滑动完抬起手指后,继续执行动画;或者滑动到边界时,如何显示。
- 默认情况下,
Flutter
会根据具体平台分别使用不同的ScrollPhysics
对象,应用不同的显示效果,如当滑动到边界时,继续拖动的话,在iOS
上会出现弹性效果,而在Android
上会出现微光效果。 - 如果你想在所有平台下使用同一种效果,可以显式指定,
Flutter SDK
中包含了两个ScrollPhysics
的子类可以直接使用:ClampingScrollPhysics
:安卓下微光效果。BouncingScrollPhysics
:iOS
下弹性效果。
controller
- 此属性接受一个
ScrollController
对象 ScrollController
的主要作用是控制滚动位置和监听滚动事件。- 默认情况下,
widget
中会有一个默认的PrimaryScrollController
,如果子widget
中的可滚动widget
没有显式的指定controller
并且primary
属性值为true
时(默认就为true
),可滚动widget
会使用这个默认的PrimaryScrollController
- 这种机制带来的好处是父
widget
可以控制子树中可滚动widget
的滚动,例如,Scaffold
使用这种机制在iOS
中实现了"回到顶部"的手势
代码示例
class ScrollView extends StatelessWidget {
@override
Widget build(BuildContext context) {
String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
return Scrollbar(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
reverse: true,
padding: EdgeInsets.all(0.0),
physics: BouncingScrollPhysics(),
child: Center(
child: Column(
//动态创建一个List<Widget>
children: str.split("")
//每一个字母都用一个Text显示,字体为原来的两倍
.map((c) => Text(c, textScaleFactor: 2.0))
.toList(),
),
),
),
);
}
}
ListView
ListView
是最常用的可滚动widget
,它可以沿一个方向线性排布所有子widget
, 类似于ReactNative
中的ListView
ListView
共有四种构造函数ListView()
默认构造函数ListView.builder()
ListView.separated()
ListView custom()
ListView({
// 公共参数上面都介绍过了
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
EdgeInsetsGeometry padding,
// 是否根据子widget的总长度来设置ListView的长度,默认值为false
bool shrinkWrap = false,
// cell高度
this.itemExtent,
// 子widget是否包裹在AutomaticKeepAlive中
bool addAutomaticKeepAlives = true,
// 子widget是否包裹在RepaintBoundary中
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
// 设置预加载的区域, moren 0.0
double cacheExtent,
//子widget列表
List<Widget> children = const <Widget>[],
// 子widget的个数
int semanticChildCount,
})
属性介绍
shrinkWrap
- 表示是否根据子
widget
的总长度来设置ListView
的长度,默认值为false
。 - 默认情况下,
ListView
的会在滚动方向尽可能多的占用空间 - 当
ListView
在一个无边界(滚动方向上)的容器中时,shrinkWrap
必须为true
itemExtent
- 该参数如果不为
null
,则会强制children
的"长度"为itemExtent
的值 - 这里的"长度"是指滚动方向上子
widget
的长度,即如果滚动方向是垂直方向,则代表子widget
的高度,如果滚动方向为水平方向,则代表子widget
的长度 - 在
ListView
中,指定itemExtent
比让子widget
自己决定自身长度会更高效,这是因为指定itemExtent
后,滚动系统可以提前知道列表的长度,而不是总是动态去计算,尤其是在滚动位置频繁变化时
addAutomaticKeepAlives
- 表示是否将列表项包裹在
AutomaticKeepAlive
中 - 在一个懒加载列表中,如果将列表项包裹在
AutomaticKeepAlive
中,在该列表项滑出视口时该列表项不会被GC,它会使用KeepAliveNotification
来保存其状态 - 如果列表项自己维护其
KeepAlive
状态,那么此参数必须置为false
addRepaintBoundaries
- 性表示是否将列表项包裹在
RepaintBoundary
中 - 当可滚动
widget
滚动时,将列表项包裹在RepaintBoundary
中可以避免列表项重绘,但是当列表项重绘的开销非常小(如一个颜色块,或者一个较短的文本)时,不添加RepaintBoundary
反而会更高效 - 和
addAutomaticKeepAlive
一样,如果列表项自己维护其KeepAlive
状态,那么此参数必须置为false
使用示例
class ScrollView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
itemExtent: 60,
cacheExtent: 100,
addAutomaticKeepAlives: false,
children: r