- 常见的滚动组件有:ListView、GridView、SingleChildScrollView、CustomScrollView、NestedScrollView、Scrollable、Scrollbar、NotificationListener、ScrollConfiguration、RefreshIndicator、PageView
ListView
- ListView创建的方式通常有三种,分别为
ListView()
,ListView.builder()
,ListView.separated()
;
ListView创建方式之ListView()
- 构造方法如下:
ListView({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
this.itemExtent,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
List children = const [],
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
})
-
scrollDirection
:滚动方向,属于Axis
类型; -
itemExtent
:item单元格的高度; -
children
:单元格数组,这里的单元格指的是ListTile; -
shrinkWrap
:其值为true时,会将item子组件的高度相加,然后赋值给ListView的高度,容易引发ListView高度溢出异常的问题; - 案例代码如下:
class SFHomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
scrollDirection: Axis.vertical,
itemExtent: 100,//设置Item的高度
children: List.generate(100, (index) {
return ListTile(
leading: Icon(Icons.people),
trailing: Icon(Icons.delete),
title: Text("联系人${index+1}"),
subtitle: Text("联系人电话号码:19991604555"),
);
}),
);
}
}
- 效果图如下:
- 通过
ListView()
创建,会一次性创建100个Item,这样性能比较差,其适用于Item个数确定,且数量较少的情况下才会采用;
ListView创建方式之ListView.builder()
- 其
不会一次性创建所有Item,而是需要展示的Item才会去创建
,性能较好,构造方法如下:
ListView.builder({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
this.itemExtent,
@required IndexedWidgetBuilder itemBuilder,
int itemCount,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
})
-
scrollDirection
:滚动方向,属于Axis
类型; -
itemExtent
:单元格高度; -
itemCount
:单元格数量; -
itemBuilder
:获取item单元格,其类型为Widget Function(BuildContext context, int index)
,调用函数创建item; - 案例代码如下:
class SFHomeBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return ListView.builder(
itemExtent: 80,
itemCount: 100,
itemBuilder: (BuildContext context,int index) {
return Container(
color: Colors.red,
child: Text("Hello World!!! ${index}",style: TextStyle(fontSize: 20)),
padding: EdgeInsets.all(10),
);
}
);
}
}
- 效果图如下:
ListView创建方式之ListView.separated()
- 创建
带有分割线的ListView
,构造函数如下:
ListView.separated({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
@required IndexedWidgetBuilder itemBuilder,
@required IndexedWidgetBuilder separatorBuilder,
@required int itemCount,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
})
-
scrollDirection
:滚动方向,属于Axis
类型; -
itemExtent
:单元格高度; -
itemCount
:单元格数量; -
itemBuilder
:获取item单元格组件,其类型为Widget Function(BuildContext context, int index)
,调用函数创建item; -
separatorBuilder
:获取分割线组件,类型同上; -
不能设置单元格item的高度
; - 案例代码如下:
class SFHomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView.separated(
itemBuilder: (BuildContext ctx, int index){
return Text("Hello World!!! ${index}",style: TextStyle(fontSize: 20),);
},
//分割线
separatorBuilder: (BuildContext ctx,int index){
return Divider(color: Colors.red,indent: 20,endIndent: 20,thickness: 5);
},
itemCount: 100
);
}
}
- 效果图如下:
GridView
- GridView的创建方式有:
GridView()
,GridView.builder()
创建方式之GridView()
- 构造方法如下所示:
GridView({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
@required this.gridDelegate,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
List children = const [],
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
})
-
children
:子组件数组,item单元格数组; -
scrollDirection
:表示滚动方向,默认为垂直方向,属于Axis
类型; -
gridDelegate
:GridView的代理,属于SliverGridDelegate
类型,其本质是控制GridView如何排列内部子元素的一个委托;-
SliverGridDelegate
是一个抽象类,其有两个实现子类,如下所示: -
SliverGridDelegateWithFixedCrossAxisCount
:用于固定列数的场景; -
SliverGridDelegateWithMaxCrossAxisExtent
:用于子元素有最大宽度限制的场景;
-
-
controller
:GridView的滚动监听者; -
primary
:当内容不足以滚动时,是否支持滚动; - 使用
gridDelegate
为SliverGridDelegateWithFixedCrossAxisCount
来创建GridView,其中SliverGridDelegateWithFixedCrossAxisCount
的构造方法如下:
const SliverGridDelegateWithFixedCrossAxisCount({
@required this.crossAxisCount,
this.mainAxisSpacing = 0.0,
this.crossAxisSpacing = 0.0,
this.childAspectRatio = 1.0,
})
- 交叉轴方向上item数量固定,其宽度根据屏幕的宽度与item的数量进行计算;
-
crossAxisCount
:交叉轴方向(水平方向)item的个数; -
childAspectRatio
:item的宽高比; -
crossAxisSpacing
:交叉轴方向上 item之间的间距; -
mainAxisSpacing
:主轴方向上 item之间的间距; - 案例代码如下:
class SFHomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 1.5,
crossAxisSpacing: 8,
mainAxisSpacing: 8
),
children:
List.generate(100, (index) {
return Container(
color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)),
);
}),
);
}
}
- 效果图如下所示:
- 使用
gridDelegate
为SliverGridDelegateWithMaxCrossAxisExtent
来创建GridView,其中SliverGridDelegateWithMaxCrossAxisExtent
的构造方法如下:
const SliverGridDelegateWithMaxCrossAxisExtent({
@required this.maxCrossAxisExtent,
this.mainAxisSpacing = 0.0,
this.crossAxisSpacing = 0.0,
this.childAspectRatio = 1.0,
})
- 交叉轴方向上的设置item的最大宽度,item的个数不固定;
-
maxCrossAxisExtent
:交叉轴方向上item的最大宽度; -
childAspectRatio
:item的宽高比; -
crossAxisSpacing
:交叉轴方向上 item之间的间距; -
mainAxisSpacing
:主轴方向上 item之间的间距; - 案例代码如下:
class SFHomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GridView(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 220,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: 1.5
),
children: List.generate(100, (index) {
return Container(
color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256),Random().nextInt(256)),
);
})
);
}
}
- 效果图如下:
创建方式之GridView.builder()
- 构造方法如下:
GridView.builder({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
@required this.gridDelegate,
@required IndexedWidgetBuilder itemBuilder,
int itemCount,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
})
- 属性大部分相同;
-
gridDelegate
:GridView的代理,属于SliverGridDelegate类型,其本质是控制GridView如何排列内部子元素的一个委托; -
itemBuilder
:创建item的构造函数; -
itemCount
:item的数量; - 案例代码如下:
class SFHomeBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 1.5
),
itemBuilder: (BuildContext ctx,int index){
return Container(color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256),Random().nextInt(256)));
},
itemCount: 100,
);
}
}
- 效果图如下:
CustomScrollView
-
CustomScrollView
:自定义滚动组件,需要传入Slivers
即Sliver数组,我们知道ListView
与GridView
都是继承自BoxScrollView
,而BoxScrollView
是一个抽象类,从源码来看ListView
与GridView
在创建的过程中都需要执行buildSlivers
方法,其内部调用buildChildLayout
方法,这是一个抽象方法,分别由ListView
与GridView
来实现,最终提供一个Sliver数组
,其中ListView
提供的Sliver为SliverFixedExtentList
,GridView
提供的Sliver为SliverGrid
; -
CustomScrollView
的构造函数如下:
const CustomScrollView({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
Key center,
double anchor = 0.0,
double cacheExtent,
this.slivers = const [],
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
})
-
slivers
:Sliver的数组,最重要的一个参数; - 常见的Sliver组件如下:
- SliverSafeArea
- SliverAppBar
- SliverList
- SliverGrid
- SliverPadding
- SliverFixedExtentList
- SliverPrototypeExtentList
- SliverToBoxAdapter
- SliverPersistentHeader
单个Sliver -- SliverSafeArea
- SliverSafeArea的构造函数如下:
const SliverSafeArea({
Key key,
this.left = true,
this.top = true,
this.right = true,
this.bottom = true,
this.minimum = EdgeInsets.zero,
@required this.sliver,
})
- 案例代码如下:
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(SFMyApp());
class SFMyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: SFHomePage());
}
}
class SFHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("基础widget")),
body: SFHomeContent());
}
}
class SFHomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SliverDemo1();
}
}
class SliverDemo1 extends StatelessWidget {
const SliverDemo1({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: [
SliverSafeArea(
sliver: SliverPadding(
padding: EdgeInsets.all(8),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: 1.5
),
delegate: SliverChildBuilderDelegate(
(BuildContext ctx,int index){
return Container(
color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256),Random().nextInt(256)),
);
},
childCount: 100
),
),
),
)
],
);
}
}
- 自定义CustomScrollView,需传入Slivers数组,这里传入的Sliver为
SliverSafeArea
; -
SliverPadding
:属于Sliver组件,可设置内边距; -
SliverGrid
:属于Sliver组件,是实现网格视图的核心组件,其构造函数如下:
const SliverGrid({
Key key,
@required SliverChildDelegate delegate,
@required this.gridDelegate,
})
-
gridDelegate
:GridView的代理,属于SliverGridDelegate类型,其本质是控制GridView如何排列内部子元素的一个委托; -
delegate
:是提供item组件,类型为SliverChildDelegate
, - SliverChildDelegate是抽象类,其作用是用来创建滚动组件的item,其有两个子类分别为
SliverChildListDelegate
与SliverChildBuilderDelegate
-
SliverChildListDelegate
:性能较差,item一次性创建所有; -
SliverChildBuilderDelegate
:性能较好,创建需要展示的item,其构造函数如下:
-
const SliverChildBuilderDelegate(
this.builder, {
this.findChildIndexCallback,
this.childCount,
this.addAutomaticKeepAlives = true,
this.addRepaintBoundaries = true,
this.addSemanticIndexes = true,
this.semanticIndexCallback = _kDefaultSemanticIndexCallback,
this.semanticIndexOffset = 0,
})
builder
:是一个item的构造函数;childCount
:item的数量;-
SafeArea
与SliverSafeArea
的区别:- SafeArea:安全区域,让目标组件在安全区域内显示;
- SliverSafeArea:给Sliver设置安全区域,且在滚动时可以在非安全区域内滚动,而SafeArea不可以;
效果图如下:
多个Sliver (SliverAppBar + SliverGrid + SliverList)
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(SFMyApp());
class SFMyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: SFHomePage());
}
}
class SFHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(title: Text("基础widget")),
body: SFHomeContent());
}
}
class SFHomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
expandedHeight: 200,
flexibleSpace: FlexibleSpaceBar(
title: Text("Hello World!!",style: TextStyle(fontSize: 25),),
),
),
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: 2.5
),
delegate: SliverChildBuilderDelegate(
(BuildContext ctx,int index){
return Container(
color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256),Random().nextInt(256)),
);
},
childCount: 10
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext ctx,int index){
return ListTile(
leading: Icon(Icons.people),
title: Text("联系人$index"),
);
},
childCount: 20
),
)
],
);
}
}
-
slivers
数组中传入了SliverAppBar
,SliverGrid
与SliverList
三种类型的Sliver,效果如下:
滚动组件的监听
- 滚动组件的监听通常有两种方式,分别为
controller
与NotificationListener
controller监听
- 可以设置默认值offset;
- 监听滚动,也可以监听滚动的位置;
- 案例代码:
import 'package:flutter/material.dart';
void main() => runApp(SFMyApp());
class SFMyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: SFHomePage());
}
}
class SFHomePage extends StatefulWidget {
@override
_SFHomePageState createState() => _SFHomePageState();
}
class _SFHomePageState extends State {
ScrollController controller = ScrollController(initialScrollOffset: 300);
bool isShowFloatButton = false;
@override
void initState() {
super.initState();
controller.addListener(() {
print("监听到滚动: ${controller.offset}");
setState(() {
isShowFloatButton = controller.offset >= 1000;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("基础widget")),
body: SFHomeContent(controller),
floatingActionButton: isShowFloatButton ? FloatingActionButton(
child: Icon(Icons.arrow_upward),
onPressed: (){
controller.animateTo(0, duration: Duration(seconds: 1), curve: Curves.easeIn);
},
) : null,
);
}
}
class SFHomeContent extends StatelessWidget {
final ScrollController controller;
SFHomeContent(this.controller);
@override
Widget build(BuildContext context) {
return ListView.builder(
controller: controller,
itemBuilder: (BuildContext ctx, int index) {
return ListTile(
leading: Icon(Icons.people),
title: Text("联系人$index"),
);
},
itemCount: 100,
);
}
}
- 右下角悬浮按钮,当前滚动偏移量>=1000时显示;
NotificationListener监听
- 案例代码:
import 'package:flutter/material.dart';
void main() => runApp(SFMyApp());
class SFMyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: SFHomePage());
}
}
class SFHomePage extends StatefulWidget {
@override
_SFHomePageState createState() => _SFHomePageState();
}
class _SFHomePageState extends State {
bool isShowFloatButton = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("基础widget")),
body: SFHomeContent(),
floatingActionButton: isShowFloatButton
? FloatingActionButton(
child: Icon(Icons.arrow_upward),
onPressed: () {
},
)
: null,
);
}
}
class SFHomeContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return NotificationListener(
onNotification: (ScrollNotification notification){
if(notification is ScrollStartNotification){
print("开始滚动");
}else if (notification is ScrollUpdateNotification){
print("正在滚动 -- 总区域:${notification.metrics.maxScrollExtent} 当前位置: ${notification.metrics.pixels}");
}else if (notification is ScrollEndNotification){
print("结束滚动");
}
return true;
},
child: ListView.builder(
itemBuilder: (BuildContext ctx, int index) {
return ListTile(
leading: Icon(Icons.people),
title: Text("联系人$index"),
);
},
itemCount: 100,
),
);
}
}