跳到主要内容

Flutter 滚动组件

滚动组件

信息

如果需要手动用代码控制滚动组件的话,那么滚动组件必须要在有状态组件中,有状态为了记录滚动的各种数据。如果不手动用代码命令控制滚动组件的话,那么在无状态组件和有状态组件中都行。

组件特点使用场景
SingleChildScrollView让单个子组件可以用滚动,所有内容一次性渲染长表单、设置页、内容不固定但是总量不多的页面
ListView线性列表,通过builder可以实现懒加载,性能优异聊天记录、新闻、常见的单列滚动的数据列表
GridView网格布局列表,支持懒加载,可以固定列数图片墙、商品网格、应用图标列表
CustomScrollView复杂布局方案,通过组合多个Sliver组件实现滚动电商首页、社交App个人主页多个滚动紧密联动
PageView整页滚动效果,支持横向和纵向应用引导页、图片轮播图、书籍翻页

SingleChildScrollView举例

这些滚动组件用法都差不多,这里代码以SingleChildScrollView举例如下:

import 'package:flutter/material.dart';

class MainPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return _MainPageState();
}
}

class _MainPageState extends State<MainPage> {
ScrollController _sc = ScrollController();
List<Widget> productList() {
return List.generate(20, (index) {
return Container(
height: 50,
width: 50,
decoration: BoxDecoration(color: Colors.amber),
child: Text(index.toString()),
);
});
}

@override
Widget build(BuildContext context) {
return MaterialApp(
title: "滚动列表",
home: Scaffold(
appBar: AppBar(title: Text("滚动列表")),
body: Stack(
children: [
SingleChildScrollView(
controller: _sc,
child: Column(children: productList()),
),
Positioned(
left: 10,
top: 60,
child: GestureDetector(
onTap: () {
_sc.animateTo(
0,
duration: Duration(seconds: 3),
curve: Curves.easeIn,
);
},
child: Container(
width: 30,
height: 30,
child: Text("顶部"),
decoration: BoxDecoration(color: Colors.blue),
),
),
),
Positioned(
right: 10,
bottom: 300,
child: GestureDetector(
onTap: () {
// _sc.jumpTo(_sc.position.maxScrollExtent);
_sc.animateTo(
_sc.position.maxScrollExtent,
duration: Duration(seconds: 3),
curve: Curves.easeIn,
);
},
child: Container(
width: 30,
height: 30,
child: Text("底部"),
decoration: BoxDecoration(color: Colors.blue),
),
),
),
],
),
),
);
}
}

void main() {
runApp(MainPage());
}

子组件:只能包含一个子组件,如果滚动多个组件,通常将其嵌套在Column或Row组件中

滚动方向:通过 scrollDirection属性控制,默认为垂直方向 (Axis.vertical),也可设置为水平方向 (Axis.horizontal)

特点:一次性构建所有子组件,如果嵌套的 Column或 Row中包含大量子项,可能会导致性能问题,建议使用 ListView

控制滚动: 绑定一个ScrollController对象给controller对象,使用animateTo/jumpTo方法控制滚动

滚动到顶部: controller.jumpTo(0)

滚动到底部: controller.jumpTo(controller.position.maxScrollExtent)

ListView举例

用于构建可滚动列表的核心部件,并提供流畅滚动体验,默认构造函数、ListView.builder、ListView.separated,采用按需渲染(懒加载),只构建当前可见区域的列表项,极大提升长列表性能。

  • 默认构造函数适用于静态数量有限数据一次性构建所有表项。

  • builder模式 处理长列表或动态数据的首选和推荐方式 接受一个 itemBuilder回调函数来按需构建列表项,通过itemCount控制列表长度 按需构建,不会在初始化时将所有列表项都创建,而是根据用户的滚动行为,动态地创建和销毁列表项

  • separated 在 ListView.builder的基础上,额外提供了构建分割线的能力

如代码所示:

import 'package:flutter/material.dart';

class MainPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return _MainPageState();
}
}

class _MainPageState extends State<MainPage> {
ScrollController _sc = ScrollController();
List<Widget> productList() {
return List.generate(20, (index) {
return Container(
height: 50,
width: 50,
decoration: BoxDecoration(color: Colors.amber),
child: Text(index.toString()),
);
});
}

Widget productListWithBuilder(BuildContext ctx, int index) {
return Container(
height: 50,
width: 50,
decoration: BoxDecoration(color: Colors.amber),
child: Text(index.toString()),
);
}

@override
Widget build(BuildContext context) {
return MaterialApp(
title: "滚动列表",
home: Scaffold(
appBar: AppBar(title: Text("滚动列表")),
body: Stack(
children: [
// ListView普通模式
// ListView(
// controller: _sc,
// children: productList(),
// // child: Column(children: productList()),
// ),
// // ListView.builder动态处理模式
// ListView.builder(
// itemCount: 100,
// itemBuilder: (BuildContext ctx, int index) {
// return productListWithBuilder(ctx, index);
// },
// ),
// ListView.separated 分割线模式
ListView.separated(
separatorBuilder: (context, index) =>
Container(height: 20, color: Colors.red),
itemCount: 100,
itemBuilder: (BuildContext ctx, int index) {
return productListWithBuilder(ctx, index);
},
),
Positioned(
left: 10,
top: 60,
child: GestureDetector(
onTap: () {
_sc.animateTo(
0,
duration: Duration(seconds: 3),
curve: Curves.easeIn,
);
},
child: Container(
width: 30,
height: 30,
child: Text("顶部"),
decoration: BoxDecoration(color: Colors.blue),
),
),
),
Positioned(
right: 10,
bottom: 300,
child: GestureDetector(
onTap: () {
// _sc.jumpTo(_sc.position.maxScrollExtent);
_sc.animateTo(
_sc.position.maxScrollExtent,
duration: Duration(seconds: 3),
curve: Curves.easeIn,
);
},
child: Container(
width: 30,
height: 30,
child: Text("底部"),
decoration: BoxDecoration(color: Colors.blue),
),
),
),
],
),
),
);
}
}

void main() {
runApp(MainPage());
}

GridView举例

用于创建二维可滚动网格布局的核心组件 提供多种构建方式,GridView.count、GridView.extent、GridView.builder等

  • GridView.count: GridView.count以列数为优先。指定网格多少列,Flutter 自动计算列的宽度,在空间内均匀排列

  • GridView.extent 使用GridView.extent指定子项最大宽度或者高度

通过maxCrossAxisExtent设置子项最大宽度/高度来计算横向或者纵向有多少列

  • GridView.builder GridView.builder实现动态长网格-(懒加载,只渲染可见区域)

接收gridDelegate布局委托、itemBuilder构建函数、itemCount构建数量 gridDelegates属性: SliverGridDelegateWithFixedCrossAxisCount / SliverGridDelegateWithMaxCrossAxisExtent

三种模式的代码如下:

import 'package:flutter/material.dart';

class MainPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return _MainPageState();
}
}

class _MainPageState extends State<MainPage> {
ScrollController _sc = ScrollController();
List<Widget> productList() {
return List.generate(20, (index) {
return Container(
height: 50,
width: 50,
decoration: BoxDecoration(color: Colors.amber),
child: Text(index.toString()),
);
});
}

Widget productListWithBuilder(BuildContext ctx, int index) {
return Container(
height: 50,
width: 50,
decoration: BoxDecoration(color: Colors.amber),
child: Text(index.toString()),
);
}

@override
Widget build(BuildContext context) {
return MaterialApp(
title: "滚动列表",
home: Scaffold(
appBar: AppBar(title: Text("滚动列表")),
body: Stack(
children: [
// 使用GridView.count创建固定列数网格
// GridView.count以列数为优先。指定网格多少列,Flutter 自动计算列的宽度,在空间内均匀排列
// GridView.count(
// padding: EdgeInsets.all(10),
// crossAxisCount: 3, // 3列
// mainAxisSpacing: 10, // 主轴间距
// crossAxisSpacing: 10, // 交叉轴间距
// children: productList(),
// ),
// 使用GridView.extent指定子项最大宽度或者高度
// GridView.extent通过maxCrossAxisExtent设置子项最大宽度/高度来计算横向或者纵向有多少列
// GridView.extent(
// padding: EdgeInsets.all(10),
// maxCrossAxisExtent: 100, // 设置子项最大宽度/高度来计算横向或者纵向有多少列
// mainAxisSpacing: 10,
// crossAxisSpacing: 10, // 交叉轴间距
// children: productList(),
// ),
// 使用GridView.builder实现动态长网格-(懒加载,只渲染可见区域)
// 接收gridDelegate布局委托、itemBuilder构建函数、itemCount构建数量
// gridDelegates属性: SliverGridDelegateWithFixedCrossAxisCount / SliverGridDelegateWithMaxCrossAxisExtent
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 1,
),
itemCount: 100,
itemBuilder: productListWithBuilder,
),
],
),
),
);
}
}

void main() {
runApp(MainPage());
}
GridViewGridView

自定义滚动CustomScrollView

用于组合多个可滚动组件(如列表、网格),实现统一协调的滚动效果,Flutter 中描述可滚动视图内部一部分内容的组件,它是滚动视图的"切片" 通过 slivers属性接收一个 Sliver 组件列表

Siiver组件对应关系

slivers其他widget组件
SliverListListView
SliverGridGridView
SliverAppBarAppBar
SliverPaddingPadding
SliverToBoxAdapterToBoxAdapter (用于包裹普通 Widget)
SliverPersistentHeader(粘性吸顶)

看代码即可

自定义列表
import 'package:flutter/material.dart';

class MainPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _MainPageState();
}
}

class _MainPageState extends State<MainPage> {
ScrollController _sc = ScrollController();
List<Widget> productList() {
return List.generate(20, (index) {
return Container(
height: 50,
width: 50,
decoration: BoxDecoration(color: Colors.amber),
child: Text(index.toString()),
);
});
}

Widget productListWithBuilder(BuildContext ctx, int index) {
return Container(
height: 50,
width: 50,
decoration: BoxDecoration(color: Colors.amber),
child: Text(index.toString()),
);
}

@override
Widget build(BuildContext context) {
return MaterialApp(
title: "滚动列表",
home: Scaffold(
appBar: AppBar(title: Text("滚动列表")),
body: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Container(
height: 50,
width: 50,
decoration: BoxDecoration(color: Colors.red),
child: Text("轮播图"),
),
),
SliverPersistentHeader(
pinned: true,
delegate: _StickyCategoryDelegate(),
),
SliverList.separated(
itemBuilder: productListWithBuilder,
separatorBuilder: (ctx, index) {
return Container(
height: 20,
decoration: BoxDecoration(color: Colors.blue),
);
},
),
],
),
),
);
}
}

// 悬浮分类
class _StickyCategoryDelegate extends SliverPersistentHeaderDelegate {
@override
Widget build(
BuildContext context,
double shrinkOffset,
bool overlapsContent,
) {
return Container(
color: Colors.white,
child: ListView.builder(
itemBuilder: (ctx, index) {
return Container(
width: 100,
alignment: Alignment.center,
decoration: BoxDecoration(color: Colors.green),
child: Text(index.toString()),
);
},
),
);
}

@override
// TODO: implement maxExtent
double get maxExtent => 80;

@override
// TODO: implement minExtent
double get minExtent => 50;

@override
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
return true;
}
}

void main() {
runApp(MainPage());
}

PageView

PageView整页滚动容器,用于实现分页滚动视图的核心组件,如轮播图, 支持懒加载(按需渲染) 详细参考代码注释:

import 'package:flutter/material.dart';

class MainPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return _MainPageState();
}
}

class _MainPageState extends State<MainPage> {
PageController _pc = PageController();
// PageView绑定controller属性,对象类型为PageController
// controller.jumpPage/animateToPage
List<Widget> productList() {
return List.generate(20, (index) {
return Container(
height: 50,
width: 50,
decoration: BoxDecoration(color: Colors.amber),
child: Text(index.toString()),
);
});
}

Widget productListWithBuilder(BuildContext ctx, int index) {
return Container(
height: 50,
width: 50,
decoration: BoxDecoration(color: Colors.amber),
child: Text(index.toString()),
);
}

@override
Widget build(BuildContext context) {
return MaterialApp(
title: "滚动列表",
home: Scaffold(
appBar: AppBar(title: Text("滚动列表")),
body: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Container(
height: 200,
child: PageView.builder(
// 创建了轮播图,如果手动跳转的话,那么需要拿着_pc进行跳转即可
controller: _pc,
itemCount: 10,
itemBuilder: (ctx, index) => Container(
height: 50,
width: 50,
decoration: BoxDecoration(color: Colors.amber),
child: Text(index.toString()),
),
),
),
),
],
),
),
);
}
}

void main() {
runApp(MainPage());
}