滚动¶
一屏幕显示不下,需要上下滚动。
滚动条¶
body: Scrollbar(
child: ListView.builder(itemBuilder: itemBuilder),
),
ListView¶
数据很多的时候,ListView可以动态加载,Column是全部加载。
屏幕能显示多少条数据,只加载显示的。滑出去的回收,新的进来,复用之前的组件。
1、数据固定写死的¶
children是所有的都进来显示。
ListView(
children: [
listViewSection('现金'),
DiscoverCell(
imageName: defaultImageName,
title: '现金',
onTapCallBack: () {},
),
listViewSection('网络'),
DiscoverCell(
title: '微信',
imageName: defaultImageName,
onTapCallBack: () {},
),
lineWidget(),
DiscoverCell(
title: '支付宝',
imageName: defaultImageName,
onTapCallBack: () {},
),
],
),
2、数据和UI分离¶
builder是显示多少加载多少。
//构造方法
ListView.builder(
//重要‼️:根据ListView的内容大小来展示,有多少内容,ListView就多大。当item很少占不满一屏,滑动会超出ListView而看不到。
shrinkWrap: true,
//itemBuilder是一个回调函数:Function(BuildContext context, int index);
//方法返回一个widget 返回每一个item,类似iOS中tableView的cellForRow,鼠标放上去根据提示快速创建createMethod方法
itemBuilder: _itemForRow,
//总共有多少个item
itemCount: datas.length,
//cacheExtent: 10, //缓冲区域大小
//itemExtent: 60,//主轴方向item的高度 最大60 最小60
scrollDirection: Axis.horizontal, //横向滚动
);
//定义方法,返回一个widget
//dart中不希望外界访问的话加下划线 下划线的内部指文件内部。整个文件都可以访问,其它文件不能访问。
Widget _itemForRow(BuildContext context, int index) {
return Container(
///省略代码
);
}
shrinkWrap¶
ListView
的 shrinkWrap
属性是一个布尔类型的值,它决定了ListView的主轴(通常是垂直方向)的大小是应该固定还是动态变化。
默认情况下,shrinkWrap
的值是 false
,这意味着 ListView
会尽可能的占用其父容器在主轴方向上的所有可用空间。这种情况下,ListView
的大小不受其内容的大小影响,因此它会创建一个具有无限滚动空间的视图。
当 shrinkWrap
设置为 true
时,ListView
的大小会被内容撑开,也就是说它的大小会根据列表中的项目数量和大小来确定。这样的 ListView
只会占用其内容所需的空间大小,不会占用额外的空间。这在某些布局场景中非常有用,比如当你想要将 ListView
嵌入到一个复杂的布局中,并且你希望 ListView
不要占用比它的内容更多的空间。
需要注意的是,使用 shrinkWrap: true
可能会带来性能上的影响,因为Flutter需要计算ListView中所有子项的大小来确定ListView的大小,这在有很多子项的情况下可能会造成性能问题。因此,只有在确实需要的情况下才设置 shrinkWrap
为 true
,并且要注意其对性能可能产生的影响。
分割线separated¶
ListView.separated(
//分割线
separatorBuilder: (context, index) {
return const Divider(thickness: 4,);
},
itemBuilder: _itemForRow, //itemBuilder是一个回调,方法返回一个widget 相当于cell
itemCount: datas.length,
),
空数据页面¶
Container(
child: _datas.isEmpty
? const Center(
child: Text('loading...'),
)
: ListView.builder(
itemCount: _datas.length,
itemBuilder: _itemBuilderForRow,
),
),
controller¶
listView滚动的时候需要一个controller属性
点击导航栏文字,跳转到顶部。
final _controller = ScrollController();
return Scaffold(
appBar: AppBar(
title: GestureDetector(
onTap: (){
//_controller.jumpTo(0.0);
//动画
_controller.animateTo(
-20.0,
duration: Duration(seconds: 1),
curve: Curves.linear,
);
},
child: Text("Demo"),
),
),
body: Scrollbar(
child: ListView.builder(
itemBuilder: _itemForRow, //itemBuilder是一个回调,方法返回一个widget 相当于cell
itemCount: datas.length,
scrollDirection: Axis.horizontal, //横向滚动
),
),
);
现在的位置_controller.offset;
ListView滚动¶
item的高度 必须要提前知道位置是多少,根据数据内容计算。
ListView滚动的时候会产生一个事件,这个事件可以被监听到。
NotificationListener(
onNotification: (ScrollNotification _event) {
print(_event);
return false;
},
child: RefreshIndicator(
//下拉刷新
onRefresh: () async {
await Future.delayed(Duration(seconds: 2)); //网络加载数据
setState(() {});
},
child: ListView.builder(
itemCount: 200,
controller: _controller,
itemBuilder: (_, index) {
return Container(
color: Colors.blue[index % 9 * 100],
height: 50,
);
},
),
),
)
滚动方向¶
scrollDirection
滑动删除Dismissible¶
ListView.builder(
itemCount: 200,
itemBuilder: (_, index) {
return Dismissible(
key: UniqueKey(),
//滑动方向
direction: DismissDirection.endToStart,
//一定要有key,要知道哪一个被滑走
background: Container(
color: Colors.green,
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(left: 24),
child: const Icon(
Icons.phone,
size: 24,
),
),
secondaryBackground: Container(
color: Colors.black,
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: 24),
child: const Icon(
Icons.sms,
color: Colors.white,
),
),
onDismissed: (direction) {
//左滑右滑
print(direction);
//UI上删除了,业务逻辑的数据上也得删除
if (direction == DismissDirection.startToEnd) {
print("phone");
}
},
//删除之后的动作
onResize: () {
print("onResize");
//删除item,页面变化时会调
},
confirmDismiss: (direction) async {
//是否确认删除
//可以弹出确认弹窗
return true;
},
//默认超过40%就会滑完
dismissThresholds: const {
DismissDirection.startToEnd: 0.1,
DismissDirection.endToStart: 0.99,
},
//滑动方向
direction: DismissDirection.vertical,
//删除时间
resizeDuration: const Duration(seconds: 3),
movementDuration: const Duration(seconds: 3),
child: Container(
height: 72,
color: Colors.blue[index % 9 * 100],
),
);
},
)
自定义左滑显示多个按钮¶
因为Dismissible
会在动作完成后移除小部件,所以使用Stack
小部件来叠加内容和滑动背景。使用ListTile
配合GestureDetector
或者InkWell
来检测滑动手势,并使用AnimatedContainer
或AnimatedPositioned
来控制滑动效果。
如果滑动的偏移量超过一定值,固定按钮的位置,否则重置。
class SlideItem extends StatefulWidget {
final Widget itemWidget;
final VoidCallback? callbackAction;
final VoidCallback deleteAction;
final VoidCallback editAction;
const SlideItem(
{super.key,
required this.itemWidget,
this.callbackAction,
required this.deleteAction,
required this.editAction});
@override
State<SlideItem> createState() => _SlideItemState();
}
class _SlideItemState extends State<SlideItem> {
double _offset = 0;
void _slide(double dx) {
setState(() {
double newOffset = _offset - dx;
_offset = newOffset > 150 ? 150 : newOffset;
});
}
void _setEndState() {
setState(() {
_offset < 75 ? _offset = 0 : _offset = 150;
});
}
void _reset() {
setState(() {
_offset = 0;
});
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: 55,
child: Stack(
children: <Widget>[
//底部的widget
Positioned.fill(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
IconButton(
icon: const Icon(Icons.edit),
color: themeColor(context),
onPressed: () {
_reset();
widget.editAction();
},
),
IconButton(
icon: const Icon(Icons.delete),
color: Colors.red,
onPressed: () {
_reset();
widget.deleteAction();
},
),
],
),
),
//上层的widget
AnimatedPositioned(
duration: const Duration(milliseconds: 200),
right: _offset,
left: -_offset,
top: 0,
bottom: 0,
child: GestureDetector(
onHorizontalDragUpdate: (details) {
_slide(details.primaryDelta!);
},
onHorizontalDragEnd: (details) {
_setEndState();
},
child: GestureDetector(
onTap: () {
_offset > 1 ? _reset() : widget.callbackAction!();
},
child: widget.itemWidget,
),
),
),
],
),
);
}
}
ListView嵌套ListView¶
里面的ListView需要设置
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
RefreshIndicator下拉刷新¶
在Flutter中实现CustomScrollView
的下拉刷新功能,通常会使用RefreshIndicator
控件包裹CustomScrollView
。RefreshIndicator
是一个Material组件,它提供了一个下拉刷新的效果。当用户在列表顶部下拉时,会显示一个圆形进度指示器,并且可以触发一个回调函数来实现数据的刷新。
如果想让列表在加载数据时停留在指示器下方,直到数据加载完成,你需要控制列表的滚动位置。
这通常可以通过以下步骤实现:
- 创建一个
ScrollController
来控制ListView
的滚动。 - 在触发刷新操作时,调整滚动位置使得
ListView
保持在指示器下方。 - 数据加载完成后,再将滚动位置恢复到顶部。
这里有一个简化的例子展示如何实现这个效果:
class MyHomePage extends StatefulWidget {
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<int> items = List.generate(20, (i) => i);
final ScrollController _scrollController = ScrollController();
Future<void> _handleRefresh() async {
// 让ListView停留在一定的位置,显示指示器
_scrollController.jumpTo(50);
// 模拟网络加载
await Future.delayed(Duration(seconds: 2));
// 加载数据
setState(() {
items = List.generate(20, (i) => items.length + i);
});
// 数据加载完成后,恢复ListView的位置
_scrollController.animateTo(
0,
duration: Duration(milliseconds: 200),
curve: Curves.easeIn,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Custom Refresh Behavior'),
),
body: RefreshIndicator(
onRefresh: _handleRefresh,
color: Colors.white, // 指示器的颜色
backgroundColor: Colors.blue, // 指示器背景颜色
displacement: 40.0, // 指示器显示时的偏移量
child: ListView.builder(
controller: _scrollController,
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text('Item ${items[index]}'),
);
},
),
),
);
}
}
- 首先使用
jumpTo
方法将ListView
滚动到一个指定的位置,以便在加载时保持指示器可见。 - 加载完成后,我们使用
animateTo
方法将ListView
平滑滚动回顶部。 onRefresh
是一个返回Future<void>
的回调函数,在用户下拉刷新时被调用。在这个函数中,执行数据加载的异步操作。当操作完成并且Future
解析后,刷新指示器会消失,UI也会根据新的数据进行更新。
GridView网格视图¶
GridView.builder(
// gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
// crossAxisCount: 4,
// childAspectRatio: 16 / 9,
// mainAxisSpacing: 2,
// crossAxisSpacing: 4,
// ), //横着方向4个
//设置交叉轴方向最大的尺寸
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 120, //最大不超过120
childAspectRatio: 16 / 9,
mainAxisSpacing: 2.0, //主轴方向间隙
crossAxisSpacing: 4.0, //交叉轴方向间隙
),
itemBuilder: (_, index) => Container(
color: Colors.blue[index % 8 * 100],
),
//itemCount: _datas.length,
)
ListWheelScrollView¶
ListWheelScrollView
是 Flutter 中的一个滚动容器,用于实现一个3D效果的滚动列表,类似于iOS的UIPickerView
。这个列表中的条目看起来好像是沿着一个圆柱体的表面滚动的,远离中心的条目会变得更小并向后倾斜。
下面是一个基本的ListWheelScrollView
的使用示例:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('ListWheelScrollView Example'),
),
body: Center(
child: ListWheelScrollView(
itemExtent: 100, // 每个条目的高度
diameterRatio: 2, // 圆柱的直径与主轴窗口尺寸的比例
useMagnifier: true, // 是否使用放大镜效果
magnification: 1.5, // 中心条目放大的倍数
children: List<Widget>.generate(
10, // 生成10个条目
(index) => Card(
child: Center(
child: Text('Item $index'),
),
),
),
),
),
),
);
}
}
在这个例子中,ListWheelScrollView
创建了一个包含10个条目的滚动列表。每个条目都是一个Card
,里面包含一个居中的Text
widget。以下是ListWheelScrollView
的一些重要属性:
itemExtent
: 必需的属性,确定每个子widget在主轴上的固定尺寸。diameterRatio
: 确定圆柱的直径与主轴窗口尺寸的比例。较小的值可以产生更强的3D效果。useMagnifier
: 确定是否在中心条目上使用放大镜效果。magnification
: 如果启用了放大镜,这个值确定中心条目放大的倍数。offAxisFraction
: 确定圆柱的偏移量,可以让圆柱向左或向右倾斜。onSelectedItemChanged
: 当选中的条目发生变化时调用的回调函数。
ListWheelScrollView
还有许多其他属性可以帮助你自定义滚动行为和外观,包括perspective
来调整视觉深度效果,以及physics
来定义滚动物理特性,如弹簧效果和滚动动态。
使用ListWheelScrollView
时,你可以创建一个选择器、日期选择器或其他滚动选择工具。它为用户提供了一种直观且有趣的方式来从列表中选择条目。
ReorderableListView¶
拖拽移动List顺序
SingleChildScrollView¶
在Flutter中,如果你使用SingleChildScrollView
并设置其滚动方向为横向(scrollDirection: Axis.horizontal
),你可以通过监听滚动通知来知道滚动了多少。
这里有一个简单的例子来说明如何实现这一点:
import 'package:flutter/material.dart';
class MyHorizontalScrollingWidget extends StatefulWidget {
@override
_MyHorizontalScrollingWidgetState createState() => _MyHorizontalScrollingWidgetState();
}
class _MyHorizontalScrollingWidgetState extends State<MyHorizontalScrollingWidget> {
ScrollController _scrollController = new ScrollController();
@override
void initState() {
super.initState();
_scrollController.addListener(_scrollListener);
}
void _scrollListener() {
print(_scrollController.position.pixels); // 打印滚动的像素值
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
controller: _scrollController,
child: Row(
children: List.generate(20, (index) => Container(
width: 150,
height: 100,
color: index % 2 == 0 ? Colors.blue : Colors.red,
child: Center(child: Text('Item $index')),
)),
),
);
}
@override
void dispose() {
_scrollController.removeListener(_scrollListener);
_scrollController.dispose();
super.dispose();
}
}
在这个例子中,创建了一个SingleChildScrollView
,它的scrollDirection
属性被设置为Axis.horizontal
,使其可以横向滚动。
同时使用了一个ScrollController
来监听滚动事件,并在_scrollListener
方法中打印了当前滚动的位置(以像素为单位)。
记得在dispose
方法中移除监听器并且释放ScrollController
资源,以防内存泄漏。
当用户滚动时,_scrollListener
方法会被触发,并且滚动的像素值会被打印出来。这样就可以知道用户滚动了多少。
SliverPersistentHeader¶
组头,滑动会固定在顶部。
SliverToBoxAdapter¶
组头,跟随页面一起滚动。