可滚动组件介绍
环境介绍以及参考文献
本示例是在 Linux 16.04.1-Ubuntu 搭配 VS Code 使用。
《Flutter实战》电子书 Flutter中文网
可滚动组件简介
当组件内容超过当前显示视口(ViewPort)时,如果不做处理,Flutter 会提示 Overflow 错误。
针对 overflow 问题,flutter 提供了可滚动组件去显示长列表和长布局。
可滚动组件的核心组件 Scrollable
Scrollable({
//...
this.axisDirection = AxisDirection.down, // 滚动方向
this.controller, // 接受 ScrollController 对象,控制滚动位置和监听滚动事件
this.physics, // 接受 ScrollPhysics 对象,响应用户操作。
@required this.viewportBuilder, // 后面介绍
})
基于Sliver的延迟构建
因为可滚动组件的子组件可能会非常多,如果一次性将全部子组件构建出来会非常耗费内存。因此 Flutter 提出一个 Sliver 的概念,一个可滚动组件如果支持 Sliver 模型,那么该滚动可以将子组件分成多个 Sliver, 只有当 Sliver 出现在 ViewPort 时才去构建它。
SingleChildScrollView
类似于 Android 中的 ScrollView。不支持 Sliver 的延迟实例化模型,因此只适合不太多的内容。
class SingleChildScrollViewTestRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
return Scrollbar( // 显示进度条
child: SingleChildScrollView(
padding: EdgeInsets.all(16.0),
child: Center(
child: Column(
//动态创建一个List<Widget>
children: str.split("")
//每一个字母都用一个Text显示,字体为原来的两倍
.map((c) => Text(c, textScaleFactor: 2.0,))
.toList(),
),
),
),
);
}
}
ListView
可以沿一个方向线性排布所有子组件,支持 Sliver 的延迟实例化模型。
实现一个可以自加载数据的 listview,并设置 listview 的标题,在 listview 滚动的时候其标题一直置顶。 
class ListViewRoute extends StatefulWidget {
@override
_ListViewState createState() => new _ListViewState();
}
class _ListViewState extends State<ListViewRoute> {
static const loadingTag = "##loading##";
var _words = <String>[loadingTag];
void _retrieveData() {
print("_retrieveData");
Future.delayed(Duration(seconds: 2)).then((e) {
setState(() {
_words.insertAll(_words.length - 1,
generateWordPairs().take(20).map((e) => e.asPascalCase).toList()
);
});
});
}
@override
Widget build(BuildContext context) {
Widget divider1 = Divider(color: Colors.blue,);
Widget divider2 = Divider(color: Colors.green);
return Scaffold(
appBar: AppBar(
title: Text("ListView"),
),
body: Column(
children: <Widget>[
Container (
decoration: BoxDecoration (
color: Colors.purple,
),
child: ListTile(title:Text("Word_List"),
trailing: Icon(Icons.keyboard_arrow_right),
leading: Icon(Icons.list_alt),
onTap: () => print(_words.length),),
),
Expanded(
child: ListView.separated(
itemCount: _words.length,
itemBuilder: (context, index) {
if(_words[index] == loadingTag) {
if(_words.length - 1 < 100 ) {
_retrieveData();
return Container(
padding: const EdgeInsets.all(16.0),
alignment: Alignment.center,
child: SizedBox(
width: 24.0,
height: 24.0,
child: CircularProgressIndicator(strokeWidth: 2.0,),
),
);
} else {
return Container(
alignment: Alignment.center,
padding: EdgeInsets.all(16.0),
child: Text("没有更多了", style: TextStyle(color: Colors.grey),)
);
}
}
return ListTile(title: Text(_words[index]));
},
separatorBuilder: (context, index) {
return index % 2 == 0 ? divider1 : divider2;
}
),
)
],
)
);
}
}
GridView
利用 GridView 可以构建一个二维网格列表。

class GridViewRoute extends StatefulWidget {
@override
_GridViewState createState() => new _GridViewState();
}
class _GridViewState extends State<GridViewRoute> {
List<IconData> _icons = [];
@override
void initState() {
super.initState();
_retrieveIcons();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("GridView"),
),
body: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 1.0,
),
itemCount: _icons.length,
itemBuilder: (context, index) {
if(index == _icons.length - 1 && _icons.length < 200) {
_retrieveIcons();
}
return Icon(_icons[index]);
},
),
);
}
void _retrieveIcons() {
Future.delayed(Duration(milliseconds: 200)).then((e) {
setState(() {
_icons.addAll([
Icons.ac_unit,
Icons.airport_shuttle,
Icons.all_inclusive,
Icons.beach_access,
Icons.cake,
Icons.free_breakfast,
]);
});
});
}
}
CustomScrollView
CustomScrollView 可以将多个 Sliver 合在一起,这些 Sliver 公用 CustomScrollView 的 Scrollable,实现统一的滑动效果。 
class CustomScrollViewRoute extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Material(
child: CustomScrollView(
slivers: <Widget>[
//AppBar,包含一个导航栏
SliverAppBar(
pinned: true,
expandedHeight: 250.0,
flexibleSpace: FlexibleSpaceBar(
title: const Text('Demo'),
),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
sliver: new SliverGrid( //Grid
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, //Grid按两列显示
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 4.0,
),
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
//创建子widget
return new Container(
alignment: Alignment.center,
color: Colors.cyan[100 * (index % 9)],
child: new Text('grid item $index'),
);
},
childCount: 10,
),
),
),
//List
new SliverFixedExtentList(
itemExtent: 50.0,
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
//创建列表项
return new Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: new Text('list item $index'),
);
},
childCount: 10
),
),
],
),
);
}
}
滚动监听及控制
ScrollController
控制可滚动组件的滚动位置
//新建一个 ScrollController 工具类
class ScrollControllerUtil {
ScrollController _controller = new ScrollController();
//在 init 的时候 register
void init(String tag) {
_controller.addListener(() {
print(tag + " : $_controller.offset");
});
}
//不需要的时候 dispose,避免内存泄漏
void dispose() {
_controller.dispose();
}
ScrollController getController() {
return _controller;
}
}
//使用
//在 gridview 或者 listview 的 control 属性中设置 ScrollController
NotificationListener
- 通过 NotificationListener 可以在从可滚动组件到 widget 树根之间任意位置都能监听。而 ScrollController 只能和具体的可滚动组件关联后才可以。
- 收到滚动事件后获得的信息不同;NotificationListener 在收到滚动事件时,通知中会携带当前滚动位置和 ViewPort 的一些信息,而 ScrollController 只能获取当前滚动位置。
NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
double progress = notification.metrics.pixels /
notification.metrics.maxScrollExtent;
//重新构建
setState(() {
_progress = "${(progress * 100).toInt()}%";
});
print("BottomEdge: ${notification.metrics.extentAfter == 0}");
return true;
},
);
|