Flutter 滚动组件
文章目录
Flutter 滚动控件
SingleChildScrollView
滚动组件,类似于Android中的ScrollView,只能接收一个子元素。
scrollDirection:滚动方向。
BouncingScrollPhysics:滚动到边界效果。
- ClampingScrollPhysics:类似安卓效果,会有微光显示。
- BouncingScrollPhysics:类似ios效果,会有回弹。
Scrollbar(
child: Container(
width: double.infinity,
child: SingleChildScrollView(
physics: BouncingScrollPhysics(),
padding: EdgeInsets.all(10),
child: Column(
children:
str.split("").map((e) => Text(e, textScaleFactor: 2)).toList(),
),
),
),
)
ListView
itemExtent:子组件长度,指定itemExtent后有利于提高性能,避免每次构建子组件时再次计算。
prototypeItem:列表项原型,指定子元素的Widget。有利于提供ListView性能,与itemExtent互斥。
shrinkWrap:是否根据子组件的总长度设置ListView的长度,默认为false。如果为false时,ListView会在主轴方向尽可能多占空间。
默认构造函数
适合数据量比较小。
ListView(
shrinkWrap: true,
padding: EdgeInsets.all(20),
children: [
Text("A"),
Text("B"),
Text("C"),
Text("D"),
],
)
ListView.builder
适合数据量比较多或不确定的情况。
itemCount:子元素数量。如果为null,表示无限列表。
itemBuilder:设置子元素的Widget。
ListView.builder(
itemCount: null,
itemExtent: 50,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"));
}
)
ListView.separated
可以给ListView添加一个分割线。
Widget divider1 = Divider(color: Colors.red);
Widget divider2 = Divider(color: Colors.blue);
ListView.separated(
itemCount: 100,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"));
},
separatorBuilder: (BuildContext context, int index) {
return index % 2 == 0 ? divider1 : divider2;
},
)
ScrollController
监听滚动组件。
offset:滚动距离。
animateTo & jumpTo:滚动到指定位置,前者滚动时会执行动画,后者则不会。
addListener:监听滚动组件。
class ListViewPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _ListViewPageState();
}
}
class _ListViewPageState extends State<ListViewPage> {
final ScrollController _controller = ScrollController();
bool showTopBtn = false;
@override
void initState() {
super.initState();
_controller.addListener(() {
print("滚动距离:${_controller.offset}");
if (_controller.offset < 1000) {
if (showTopBtn) {
showTopBtn = false;
setState(() {});
}
} else {
if (!showTopBtn) {
showTopBtn = true;
setState(() {});
}
}
});
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ListView"),
actions: [],
),
body: ListView.builder(
itemCount: null,
itemExtent: 50,
physics: BouncingScrollPhysics(),
controller: _controller,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"));
}),
floatingActionButton: !showTopBtn
? null
: FloatingActionButton(
onPressed: () {
_controller.animateTo(
0,
duration: Duration(microseconds: 800),
curve: Curves.ease,
);
},
child: Icon(Icons.arrow_upward),
),
);
}
}
NotificationListener
可以监听ListView、NestedScrollView、GridView的滚动监听。
NotificationListener与ScrollController对比
- NotificationListener可以在任意位置监听,并且携带的信息更多。
- ScrollController只能和具体的滚动组件关联后才能监听,只能获取当前滚动位置。
ScrollNotification类里包含一个metrics属性,其类型是ScrollMetrics,包含一些信息:
var extentBefore = notification.metrics.extentBefore; //已滑出ViewPort顶部的长度,已滚动距离
var extentInside = notification.metrics.extentInside; //ViewPort内部长度,表示屏幕显示的列表部分的长度
var extentAfter = notification.metrics.extentAfter; //未划入ViewPort部分的长度
var pixels = notification.metrics.pixels; //当前滚动距离
var maxScrollExtent = notification.metrics.maxScrollExtent; //最大可滚动距离
class NotificationListenerPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _NotificationListenerPageState();
}
}
class _NotificationListenerPageState extends State<NotificationListenerPage> {
String _progress = "0%";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("滚动监听")),
body: Scrollbar(
child: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
double progress = notification.metrics.pixels /
notification.metrics.maxScrollExtent;
setState(() => _progress = "${(progress * 100).toInt()}%");
return false;
},
child: Stack(
alignment: Alignment.center,
children: [
ListView.builder(
itemCount: 100,
itemExtent: 50,
itemBuilder: (context, index) => ListTile(
title: Text("$index"),
),
),
CircleAvatar(
radius: 30,
child: Text(_progress),
backgroundColor: Colors.transparent.withAlpha(200),
),
],
),
),
),
);
}
}
AnimatedList
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class AnimatedListPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _AnimatedListPageState();
}
}
class _AnimatedListPageState extends State<AnimatedListPage> {
final _listKey = GlobalKey<AnimatedListState>();
var data = <String>[];
int counter = 5;
@override
void initState() {
super.initState();
for (int i = 0; i < counter; i++) {
data.add("${i + 1}");
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("AnimatedList"),
actions: [
IconButton(
icon: const Icon(Icons.add),
onPressed: () {
addItem();
},
),
],
),
body: AnimatedList(
key: _listKey,
initialItemCount: data.length,
itemBuilder: (context, index, animation) {
return buildItem(context, index);
},
),
);
}
Widget buildItem(BuildContext context, int index) {
String c = data[index];
return ListTile(
key: ValueKey(c),
title: Text(c),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
deleteItem(context, index);
},
),
);
}
void addItem() {
data.add("${++counter}");
_listKey.currentState!.insertItem(data.length - 1);
}
void deleteItem(BuildContext context, int index) {
_listKey.currentState!.removeItem(
index,
(BuildContext context, Animation<double> animation) {
var item = buildItem(context, index);
data.removeAt(index);
return item;
},
);
}
}
GridView
二维网格列表。
SliverGridDelegateWithFixedCrossAxisCount
横轴为固定数量的子元素布局。
crossAxisCount:横轴的子元素的数量。
mainAxisSpacing:主轴方向的间距。
crossAxisSpacing:横轴方向的间距。
childAspectRatio:子元素的宽高比。
GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 1,
),
children: const [
Icon(Icons.home),
Icon(Icons.add),
Icon(Icons.delete),
Icon(Icons.update),
Icon(Icons.close),
Icon(Icons.lock),
Icon(Icons.home),
Icon(Icons.add),
Icon(Icons.delete),
Icon(Icons.update),
Icon(Icons.close),
Icon(Icons.lock),
Icon(Icons.home),
Icon(Icons.add),
Icon(Icons.delete),
Icon(Icons.update),
Icon(Icons.close),
Icon(Icons.lock),
],
)
GridView.count
用于快速创建GridView,内部仍然使用SliverGridDelegateWithFixedCrossAxisCount
等价于上面的代码。
GridView.count(
crossAxisCount: 3,
childAspectRatio: 1,
children: [
Icon(Icons.home),
Icon(Icons.add),
Icon(Icons.delete),
Icon(Icons.update),
Icon(Icons.close),
Icon(Icons.lock),
Icon(Icons.home),
],
)
SliverGridDelegateWithMaxCrossAxisExtent
横轴的子元素最大长度的布局。
maxCrossAxisExtent:横轴子元素最大长度。
GridView(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 120,
childAspectRatio: 1,
),
children: const [
Icon(Icons.home),
Icon(Icons.add),
Icon(Icons.delete),
Icon(Icons.update),
Icon(Icons.close),
Icon(Icons.lock),
Icon(Icons.home),
Icon(Icons.add),
Icon(Icons.delete),
Icon(Icons.update),
Icon(Icons.close),
Icon(Icons.lock),
Icon(Icons.home),
Icon(Icons.add),
Icon(Icons.delete),
Icon(Icons.update),
Icon(Icons.close),
Icon(Icons.lock),
],
)
GridView.extent
快速创建GridView,本质是调用SliverGridDelegateWithMaxCrossAxisExtent。
GridView.extent(
maxCrossAxisExtent: 120,
children: const [
Icon(Icons.delete),
Icon(Icons.update),
Icon(Icons.close),
Icon(Icons.lock),
Icon(Icons.home),
Icon(Icons.add),
],
)
GridView.builder
当子元素数量较多时,推荐使用GridView.builder
方式创建。
class MyGridView extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _MyGridViewState();
}
}
class _MyGridViewState extends State<MyGridView> {
List<Icon> icons = [];
@override
void initState() {
super.initState();
getData();
}
@override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 1,
),
itemCount: icons.length,
itemBuilder: (BuildContext context, int index) {
if (index == icons.length - 1 && icons.length < 200) {
getData();
}
return icons[index];
},
);
}
void getData() {
Future.delayed(Duration(seconds: 2)).then((value) {
setState(() {
icons.addAll([
Icon(Icons.delete),
Icon(Icons.update),
Icon(Icons.close),
Icon(Icons.lock),
Icon(Icons.home),
Icon(Icons.add),
]);
});
});
}
}
PageView
类似于Android中的ViewPager控件。PageView组件可以实现页面切换和Tab布局。
pageSnapping:每次滑动时,是否强制切换页面。
class PageViewPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _PageViewPageState();
}
}
class _PageViewPageState extends State<PageViewPage> {
@override
Widget build(BuildContext context) {
List<Page> list = [];
for (int i = 0; i < 5; i++) {
list.add(Page(title: "$i"));
}
return Scaffold(
appBar: AppBar(
title: Text("PageView"),
),
body: PageView(
children: list,
),
);
}
}
class Page extends StatelessWidget {
final String title;
const Page({Key? key, required this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Text(
"$title",
textScaleFactor: 2,
),
);
}
}
页面缓存
allowImplicitScrolling:设置为true,会预渲染下一个页面。
TabBarView
TabBarView是Material组件库提供的Tab布局组件,通过配合TabBar使用。
class TabBarViewPage extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _TabBarViewPageState();
}
}
class _TabBarViewPageState extends State<TabBarViewPage>
with SingleTickerProviderStateMixin {
final List tabs = ["One", "Two", "Three", "Four", "Five", "Six", "Seven"];
late TabController _controller;
@override
void initState() {
super.initState();
_controller = TabController(length: tabs.length, vsync: this);
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("TabBarView"),
bottom: TabBar(
controller: _controller,
isScrollable: true,
tabs: tabs.map((e) {
return Tab(text: e);
}).toList(),
),
),
body: TabBarView(
controller: _controller,
children: tabs.map((e) {
return Container(
alignment: Alignment.center,
child: Text(
e,
textScaleFactor: 3,
),
);
}).toList(),
),
);
}
}
CustomScrollView
CustomScrollView可以组合多个滚动组件,使他们滑动效果能统一起来。
var listView = SliverFixedExtentList(
itemExtent: 50,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ListTile(title: Text("$index"));
},
childCount: 10,
),
);
CustomScrollView(
slivers: [
listView,
listView,
],
);
//AppBar
final appBar = SliverAppBar(
pinned: true, //滑动是标题是否固定
expandedHeight: 250, //展开高度
flexibleSpace: FlexibleSpaceBar(
title: const Text("hello world"),
background: Image.asset(
"images/logo.png",
fit: BoxFit.cover,
),
),
);
//GridView
final gridView = SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 1,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.grey,
child: Text("GridView $index"),
);
},
childCount: 20,
),
);
//Padding
final padding = SliverPadding(
padding: EdgeInsets.all(8),
sliver: gridView,
);
//ListView
final listView = SliverFixedExtentList(
delegate: SliverChildBuilderDelegate((context, index) {
return Container(
alignment: Alignment.center,
color: Colors.lightBlue,
child: Text("ListView $index"),
);
}),
itemExtent: 50,
);
CustomScrollView(
slivers: [
appBar,
padding,
listView,
],
)