Flutter for OpenHarmony 实战:ListView与GridView滚动列表完全指南
文章目录
- [Flutter for OpenHarmony 实战:ListView与GridView滚动列表完全指南](#Flutter for OpenHarmony 实战:ListView与GridView滚动列表完全指南)
-
- 摘要
- 前言
- 一、ListView滚动列表
-
- [1.1 基础ListView](#1.1 基础ListView)
- [1.2 ListView.builder](#1.2 ListView.builder)
- [1.3 ListView.separated](#1.3 ListView.separated)
- [1.4 水平ListView](#1.4 水平ListView)
- [1.5 ListView自定义](#1.5 ListView自定义)
- 二、GridView网格布局
-
- [2.1 GridView.count](#2.1 GridView.count)
- [2.2 GridView.extent](#2.2 GridView.extent)
- [2.3 GridView.builder](#2.3 GridView.builder)
- [2.4 自定义网格](#2.4 自定义网格)
- 三、列表性能优化
-
- [3.1 使用const](#3.1 使用const)
- [3.2 提取Widget](#3.2 提取Widget)
- [3.3 AutomaticKeepAliveClientMixin](#3.3 AutomaticKeepAliveClientMixin)
- [3.4 ListView性能对比](#3.4 ListView性能对比)
- 四、自定义滚动效果
-
- [4.1 滚动监听](#4.1 滚动监听)
- [4.2 下拉刷新](#4.2 下拉刷新)
- [4.3 上拉加载](#4.3 上拉加载)
- [4.4 粘性标题](#4.4 粘性标题)
- 总结
摘要
ListView和GridView是Flutter中最常用的滚动Widget。ListView用于线性列表,GridView用于网格布局。理解它们的构建方式和性能优化对开发流畅的应用至关重要。这篇文章我想深入讲解ListView和GridView的使用方法。
前言
滚动列表是应用中最常见的UI形式。一开始我用ListView很随意,数据多了就卡。
后来学会了ListView.builder,性能提升了很多。GridView也有很多技巧。这篇文章我想分享滚动列表的使用经验。
一、ListView滚动列表
1.1 基础ListView

dart
class BasicListView extends StatelessWidget {
const BasicListView({super.key});
@override
Widget build(BuildContext context) {
return ListView(
children: [
_tile('项目1', '副标题1'),
_tile('项目2', '副标题2'),
_tile('项目3', '副标题3'),
_tile('项目4', '副标题4'),
_tile('项目5', '副标题5'),
],
);
}
Widget _tile(String title, String subtitle) {
return ListTile(
title: Text(title),
subtitle: Text(subtitle),
leading: Icon(Icons.label),
trailing: Icon(Icons.arrow_forward),
);
}
}
1.2 ListView.builder

dart
class ListViewBuilder extends StatelessWidget {
const ListViewBuilder({super.key});
final List<String> items = const [
'项目1', '项目2', '项目3', '项目4', '项目5',
'项目6', '项目7', '项目8', '项目9', '项目10',
];
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index]),
subtitle: Text('索引:$index'),
leading: CircleAvatar(child: Text('${index + 1}')),
);
},
);
}
}
1.3 ListView.separated

dart
class ListViewSeparated extends StatelessWidget {
const ListViewSeparated({super.key});
final List<String> items = const [
'项目1', '项目2', '项目3', '项目4', '项目5',
];
@override
Widget build(BuildContext context) {
return ListView.separated(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index]),
);
},
separatorBuilder: (context, index) {
return Divider(height: 1, color: Colors.grey);
},
);
}
}
1.4 水平ListView

dart
class HorizontalListView extends StatelessWidget {
const HorizontalListView({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
height: 150,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: 20,
itemBuilder: (context, index) {
return Container(
width: 150,
margin: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.primaries[index % Colors.primaries.length],
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text(
'Item $index',
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
);
},
),
);
}
}
1.5 ListView自定义
dart
class CustomListView extends StatelessWidget {
const CustomListView({super.key});
@override
Widget build(BuildContext context) {
return ListView.custom(
childrenDelegate: SliverChildBuilderDelegate(
(context, index) {
return ListTile(
title: Text('项目 $index'),
);
},
childCount: 20,
addAutomaticKeepAlives: true,
addRepaintBoundaries: true,
),
);
}
}
二、GridView网格布局
2.1 GridView.count

dart
class GridViewCount extends StatelessWidget {
const GridViewCount({super.key});
@override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 2,
children: List.generate(20, (index) {
return Container(
decoration: BoxDecoration(
color: Colors.primaries[index % Colors.primaries.length],
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text(
'$index',
style: TextStyle(color: Colors.white, fontSize: 30),
),
),
);
}),
);
}
}
2.2 GridView.extent

dart
class GridViewExtent extends StatelessWidget {
const GridViewExtent({super.key});
@override
Widget build(BuildContext context) {
return GridView.extent(
maxCrossAxisExtent: 150,
children: List.generate(20, (index) {
return Container(
decoration: BoxDecoration(
color: Colors.teal,
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Text(
'Item $index',
style: TextStyle(color: Colors.white),
),
),
);
}),
);
}
}
2.3 GridView.builder

dart
class GridViewBuilder extends StatelessWidget {
const GridViewBuilder({super.key});
@override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 1,
),
itemCount: 50,
itemBuilder: (context, index) {
return Container(
decoration: BoxDecoration(
color: Colors.primaries[index % Colors.primaries.length],
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text(
'$index',
style: TextStyle(color: Colors.white, fontSize: 24),
),
),
);
},
);
}
}
2.4 自定义网格
dart
class CustomGridView extends StatelessWidget {
const CustomGridView({super.key});
@override
Widget build(BuildContext context) {
return GridView.custom(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 150,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
),
childrenDelegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text(
'Item $index',
style: TextStyle(color: Colors.white),
),
),
);
},
childCount: 30,
),
);
}
}
三、列表性能优化
3.1 使用const
dart
class OptimizedListView extends StatelessWidget {
const OptimizedListView({super.key});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 1000,
itemBuilder: (context, index) {
return const ListTile(
title: Text('标题'),
subtitle: Text('副标题'),
leading: Icon(Icons.star),
);
},
);
}
}
3.2 提取Widget
dart
class ExtractedListItem extends StatelessWidget {
final int index;
const ExtractedListItem({super.key, required this.index});
@override
Widget build(BuildContext context) {
return ListTile(
title: Text('项目 $index'),
subtitle: Text('这是项目 $index 的描述'),
leading: CircleAvatar(child: Text('$index')),
);
}
}
class OptimizedListExtracted extends StatelessWidget {
const OptimizedListExtracted({super.key});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 1000,
itemBuilder: (context, index) {
return ExtractedListItem(index: index);
},
);
}
}
3.3 AutomaticKeepAliveClientMixin
dart
class KeepAliveListItem extends StatefulWidget {
final int index;
const KeepAliveListItem({super.key, required this.index});
@override
State<KeepAliveListItem> createState() => _KeepAliveListItemState();
}
class _KeepAliveListItemState extends State<KeepAliveListItem>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return ListTile(
title: Text('项目 ${widget.index}'),
subtitle: Text('滚动后保持状态'),
);
}
}
3.4 ListView性能对比
dart
class PerformanceComparison extends StatelessWidget {
const PerformanceComparison({super.key});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 10000,
itemExtent: 60, // 固定高度提高性能
itemBuilder: (context, index) {
return _buildItem(index);
},
);
}
Widget _buildItem(int index) {
return ListTile(
title: Text('项目 $index'),
leading: Icon(Icons.list),
);
}
}
四、自定义滚动效果
4.1 滚动监听

dart
class ScrollListener extends StatefulWidget {
const ScrollListener({super.key});
@override
State<ScrollListener> createState() => _ScrollListenerState();
}
class _ScrollListenerState extends State<ScrollListener> {
final ScrollController _controller = ScrollController();
bool _showButton = false;
@override
void initState() {
super.initState();
_controller.addListener(() {
setState(() {
_showButton = _controller.offset > 500;
});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
ListView.builder(
controller: _controller,
itemCount: 100,
itemBuilder: (context, index) {
return ListTile(title: Text('项目 $index'));
},
),
if (_showButton)
Positioned(
right: 16,
bottom: 16,
child: FloatingActionButton(
onPressed: () {
_controller.animateTo(
0,
duration: Duration(seconds: 1),
curve: Curves.easeInOut,
);
},
child: Icon(Icons.arrow_upward),
),
),
],
);
}
}
4.2 下拉刷新

dart
class PullToRefresh extends StatefulWidget {
const PullToRefresh({super.key});
@override
State<PullToRefresh> createState() => _PullToRefreshState();
}
class _PullToRefreshState extends State<PullToRefresh> {
List<String> items = List.generate(20, (index) => '项目 $index');
Future<void> _refresh() async {
await Future.delayed(Duration(seconds: 2));
setState(() {
items = List.generate(20, (index) => '刷新项目 $index');
});
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: _refresh,
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(title: Text(items[index]));
},
),
);
}
}
4.3 上拉加载
dart
class LoadMore extends StatefulWidget {
const LoadMore({super.key});
@override
State<LoadMore> createState() => _LoadMoreState();
}
class _LoadMoreState extends State<LoadMore> {
final ScrollController _controller = ScrollController();
List<String> items = List.generate(20, (index) => '项目 $index');
bool _isLoading = false;
@override
void initState() {
super.initState();
_controller.addListener(() {
if (_controller.position.pixels >= _controller.position.maxScrollExtent - 200) {
_loadMore();
}
});
}
Future<void> _loadMore() async {
if (_isLoading) return;
setState(() {
_isLoading = true;
});
await Future.delayed(Duration(seconds: 2));
setState(() {
items.addAll(List.generate(20, (index) => '新项目 ${items.length + index}'));
_isLoading = false;
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ListView.builder(
controller: _controller,
itemCount: items.length + (_isLoading ? 1 : 0),
itemBuilder: (context, index) {
if (index == items.length) {
return Center(
child: Padding(
padding: EdgeInsets.all(16),
child: CircularProgressIndicator(),
),
);
}
return ListTile(title: Text(items[index]));
},
);
}
}
4.4 粘性标题
dart
class StickyHeaders extends StatelessWidget {
const StickyHeaders({super.key});
final Map<String, List<String>> _data = const {
'A': ['Apple', 'Apricot', 'Avocado'],
'B': ['Banana', 'Blueberry', 'Blackberry'],
'C': ['Cherry', 'Coconut', 'Cranberry'],
};
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _data.length * 2,
itemBuilder: (context, index) {
if (index.isEven) {
final key = _data.keys.elementAt(index ~/ 2);
return Container(
color: Colors.grey.shade200,
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Text(
key,
style: TextStyle(fontWeight: FontWeight.bold),
),
);
} else {
final key = _data.keys.elementAt(index ~/ 2);
final items = _data[key]!;
final itemIndex = (index - 1) % 3;
if (itemIndex < items.length) {
return ListTile(title: Text(items[itemIndex]));
}
return SizedBox.shrink();
}
},
);
}
}
总结
ListView和GridView是滚动布局的基础。
核心要点:
- ListView用于线性列表
- GridView用于网格布局
- builder方式性能更好
- 使用const优化性能
- 合理使用itemExtent
最佳实践:
- 数据多用builder方式
- 固定高度设置itemExtent
- 提取Widget减少重建
- 及时释放Controller
- 使用keepAlive保持状态
滚动列表是Flutter开发中最常用的组件,需要熟练掌握。
欢迎加入开源鸿蒙跨平台社区:开源鸿蒙跨平台开发者社区