
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
一、列表虚拟化系统架构深度解析
在现代移动应用中,列表是最常见的 UI 组件之一。当数据量达到成千上万条时,如何保证列表的流畅滚动成为关键问题。Flutter 通过虚拟化技术,只渲染可见区域的元素,实现了大数据量列表的高性能渲染。理解这套架构的底层原理,是构建高性能列表系统的基础。
📱 1.1 Flutter 列表虚拟化架构
Flutter 的列表虚拟化系统由多个核心层次组成,每一层都有其特定的职责:
┌─────────────────────────────────────────────────────────────────┐
│ 应用层 (Application Layer) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ListView, GridView, CustomScrollView... │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Sliver层 (Sliver Layer) │ │
│ │ SliverList, SliverGrid, SliverChildBuilderDelegate... │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 视口层 (Viewport Layer) │ │
│ │ Viewport, RenderViewport, Scrollable... │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 渲染层 (Render Layer) │ │
│ │ RenderSliver, RenderBox, RenderViewport... │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
🔬 1.2 列表虚拟化核心组件详解
Flutter 列表虚拟化系统的核心组件包括以下几个部分:
Viewport(视口)
Viewport 是虚拟化的核心,只渲染可见区域的子组件。
dart
Viewport(
offset: ScrollController(),
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text('Item $index')),
childCount: 10000,
),
),
],
)
Sliver(切片)
Sliver 是可滚动区域的基本单位,可以灵活组合各种滚动效果。
dart
CustomScrollView(
slivers: [
SliverAppBar(),
SliverList(),
SliverGrid(),
SliverPersistentHeader(),
],
)
Scrollable(可滚动)
Scrollable 处理滚动手势和物理效果。
dart
Scrollable(
viewportBuilder: (context, offset) {
return Viewport(offset: offset, slivers: [...]);
},
)
🎯 1.3 虚拟化工作原理
列表虚拟化的核心原理是只创建和渲染可见区域的 Widget:
┌─────────────────────────────────────────────────────────────┐
│ 列表虚拟化工作原理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 缓存区域 (CacheExtent) │ │
│ │ 预加载即将可见的元素 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 可见区域 (Visible Area) │ │
│ │ ████████████████████████████████████████████████ │ │
│ │ │ Item 3 │ Item 4 │ Item 5 │ Item 6 │ Item 7 │ │ │
│ │ ████████████████████████████████████████████████ │ │
│ │ 实际渲染的 Widget │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 虚拟区域 (Virtual Area) │ │
│ │ Item 0, 1, 2... (未渲染) │ │
│ │ Item 8, 9, 10... (未渲染) │ │
│ │ 只保留数据引用,不创建 Widget │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
列表类型对比:
| 类型 | 特点 | 适用场景 |
|---|---|---|
| ListView | 简单线性列表 | 基础列表 |
| ListView.builder | 懒加载虚拟化 | 大数据量列表 |
| ListView.separated | 带分隔符的列表 | 分组列表 |
| GridView | 网格布局 | 图片画廊 |
| CustomScrollView | 自定义 Sliver 组合 | 复杂滚动效果 |
| SliverList | 灵活的列表 Sliver | 组合滚动 |
| SliverGrid | 灵活的网格 Sliver | 组合网格 |
二、基础虚拟化列表实现
基础虚拟化列表包括 ListView.builder、GridView.builder 和带缓存策略的列表。这些是构建高性能列表系统的基础。
👆 2.1 ListView.builder 基础实现
ListView.builder 是最常用的虚拟化列表,通过 builder 模式按需创建 Widget。
dart
import 'package:flutter/material.dart';
/// ListView.builder 基础示例
class BasicListViewDemo extends StatelessWidget {
const BasicListViewDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('ListView.builder 基础')),
body: ListView.builder(
itemCount: 10000,
cacheExtent: 500,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(
child: Text('${index + 1}'),
),
title: Text('列表项 ${index + 1}'),
subtitle: Text('这是第 ${index + 1} 个列表项的描述'),
trailing: const Icon(Icons.chevron_right),
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('点击了第 ${index + 1} 项')),
);
},
);
},
),
);
}
}
🔄 2.2 高级 ListView 配置
高级 ListView 配置包括控制器、物理效果、缓存区域等参数优化。
dart
/// 高级 ListView 配置示例
class AdvancedListViewDemo extends StatefulWidget {
const AdvancedListViewDemo({super.key});
@override
State<AdvancedListViewDemo> createState() => _AdvancedListViewDemoState();
}
class _AdvancedListViewDemoState extends State<AdvancedListViewDemo> {
final ScrollController _scrollController = ScrollController();
final List<String> _items = List.generate(100, (index) => 'Item $index');
bool _isLoading = false;
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
@override
void dispose() {
_scrollController.removeListener(_onScroll);
_scrollController.dispose();
super.dispose();
}
void _onScroll() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) {
_loadMore();
}
}
Future<void> _loadMore() async {
if (_isLoading) return;
setState(() => _isLoading = true);
await Future.delayed(const Duration(seconds: 1));
setState(() {
final startIndex = _items.length;
for (int i = 0; i < 20; i++) {
_items.add('Item ${startIndex + i}');
}
_isLoading = false;
});
}
Future<void> _onRefresh() async {
await Future.delayed(const Duration(seconds: 1));
setState(() {
_items.clear();
_items.addAll(List.generate(100, (index) => 'Item $index (刷新)'));
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('高级 ListView 配置')),
body: RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
controller: _scrollController,
physics: const BouncingScrollPhysics(),
cacheExtent: 1000,
itemCount: _items.length + 1,
itemBuilder: (context, index) {
if (index == _items.length) {
return _buildLoadingIndicator();
}
return _buildListItem(index);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_scrollController.animateTo(
0,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
},
child: const Icon(Icons.arrow_upward),
),
);
}
Widget _buildListItem(int index) {
return Dismissible(
key: Key(_items[index]),
background: Container(
color: Colors.red,
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: 20),
child: const Icon(Icons.delete, color: Colors.white),
),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
setState(() {
_items.removeAt(index);
});
},
child: Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
leading: Hero(
tag: 'item-$index',
child: CircleAvatar(
backgroundColor: Colors.primaries[index % Colors.primaries.length],
child: Text('${index + 1}'),
),
),
title: Text(_items[index]),
subtitle: Text('索引: $index'),
trailing: const Icon(Icons.chevron_right),
),
),
);
}
Widget _buildLoadingIndicator() {
return Container(
padding: const EdgeInsets.all(16),
alignment: Alignment.center,
child: _isLoading
? const CircularProgressIndicator()
: const Text('上拉加载更多'),
);
}
}
🌊 2.3 GridView 虚拟化实现
GridView 通过网格布局实现高效的图片画廊和数据展示。
dart
/// GridView 虚拟化示例
class GridViewDemo extends StatelessWidget {
const GridViewDemo({super.key});
List<Color> get _colors => List.generate(
100,
(index) => Color.fromRGBO(
(index * 25) % 256,
(index * 50) % 256,
(index * 75) % 256,
1.0,
),
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('GridView 虚拟化')),
body: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 4,
crossAxisSpacing: 4,
childAspectRatio: 1.0,
),
itemCount: 100,
cacheExtent: 500,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => GridDetailPage(
index: index,
color: _colors[index],
),
),
);
},
child: Hero(
tag: 'grid-item-$index',
child: Container(
color: _colors[index],
child: Center(
child: Text(
'${index + 1}',
style: const TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
),
),
),
);
},
),
);
}
}
class GridDetailPage extends StatelessWidget {
final int index;
final Color color;
const GridDetailPage({
super.key,
required this.index,
required this.color,
});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: color,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
title: Text('详情 ${index + 1}'),
),
body: Center(
child: Hero(
tag: 'grid-item-$index',
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Center(
child: Text(
'${index + 1}',
style: const TextStyle(
color: Colors.white,
fontSize: 48,
fontWeight: FontWeight.bold,
),
),
),
),
),
),
);
}
}
三、Sliver 家族组件深度实现
Sliver 家族是 Flutter 滚动系统的核心,提供了灵活的组合滚动能力。
📊 3.1 CustomScrollView 组合滚动
CustomScrollView 允许组合多个 Sliver 实现复杂的滚动效果。
dart
/// CustomScrollView 组合滚动示例
class CustomScrollViewDemo extends StatelessWidget {
const CustomScrollViewDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
_buildSliverAppBar(),
_buildSliverPersistentHeader(),
_buildSliverGrid(),
_buildSliverList(),
_buildSliverFillRemaining(),
],
),
);
}
Widget _buildSliverAppBar() {
return SliverAppBar(
expandedHeight: 200,
floating: false,
pinned: true,
snap: false,
flexibleSpace: FlexibleSpaceBar(
title: const Text('CustomScrollView'),
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Colors.blue, Colors.purple],
),
),
child: const Center(
child: Icon(
Icons.list_alt,
size: 80,
color: Colors.white54,
),
),
),
),
);
}
Widget _buildSliverPersistentHeader() {
return SliverPersistentHeader(
pinned: true,
delegate: _SectionHeaderDelegate(
title: '网格区域',
color: Colors.blue.shade100,
),
);
}
Widget _buildSliverGrid() {
return SliverGrid(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 150,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 1.0,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
margin: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.primaries[index % Colors.primaries.length].withOpacity(0.3),
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text(
'Grid $index',
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
);
},
childCount: 12,
),
);
}
Widget _buildSliverList() {
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
leading: CircleAvatar(
child: Text('$index'),
),
title: Text('列表项 $index'),
subtitle: Text('这是第 $index 个列表项'),
trailing: const Icon(Icons.chevron_right),
),
);
},
childCount: 20,
),
);
}
Widget _buildSliverFillRemaining() {
return SliverFillRemaining(
hasScrollBody: false,
child: Container(
color: Colors.grey.shade200,
padding: const EdgeInsets.all(32),
child: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.info_outline, size: 48, color: Colors.grey),
SizedBox(height: 16),
Text(
'列表底部区域',
style: TextStyle(fontSize: 18, color: Colors.grey),
),
],
),
),
);
}
}
class _SectionHeaderDelegate extends SliverPersistentHeaderDelegate {
final String title;
final Color color;
_SectionHeaderDelegate({required this.title, required this.color});
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
color: color,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
);
}
@override
double get maxExtent => 50;
@override
double get minExtent => 50;
@override
bool shouldRebuild(covariant _SectionHeaderDelegate oldDelegate) {
return title != oldDelegate.title || color != oldDelegate.color;
}
}
🎨 3.2 SliverAnimatedList 动画列表
SliverAnimatedList 支持列表项的增删动画效果。
dart
/// SliverAnimatedList 动画列表示例
class SliverAnimatedListDemo extends StatefulWidget {
const SliverAnimatedListDemo({super.key});
@override
State<SliverAnimatedListDemo> createState() => _SliverAnimatedListDemoState();
}
class _SliverAnimatedListDemoState extends State<SliverAnimatedListDemo> {
final GlobalKey<SliverAnimatedListState> _listKey = GlobalKey();
final List<String> _items = List.generate(10, (index) => 'Item $index');
int _counter = 10;
void _addItem() {
final index = _items.length;
_items.insert(index, 'Item $_counter');
_listKey.currentState?.insertItem(index);
_counter++;
}
void _removeItem(int index) {
_listKey.currentState?.removeItem(
index,
(context, animation) => _buildRemovedItem(_items[index], animation),
);
_items.removeAt(index);
}
Widget _buildRemovedItem(String item, Animation<double> animation) {
return SizeTransition(
sizeFactor: animation,
child: Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
color: Colors.red.shade100,
child: ListTile(
title: Text(item),
trailing: const Icon(Icons.delete, color: Colors.red),
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('SliverAnimatedList')),
body: CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
title: const Text('动画列表'),
actions: [
IconButton(
icon: const Icon(Icons.add),
onPressed: _addItem,
),
],
),
SliverAnimatedList(
key: _listKey,
initialItemCount: _items.length,
itemBuilder: (context, index, animation) {
return _buildItem(_items[index], index, animation);
},
),
],
),
);
}
Widget _buildItem(String item, int index, Animation<double> animation) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1, 0),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeOut,
)),
child: SizeTransition(
sizeFactor: animation,
child: Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
leading: CircleAvatar(
child: Text('$index'),
),
title: Text(item),
subtitle: Text('索引: $index'),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => _removeItem(index),
),
),
),
),
);
}
}
🔄 3.3 SliverToBoxAdapter 与 SliverPadding
SliverToBoxAdapter 将普通 Widget 转换为 Sliver,SliverPadding 为 Sliver 添加内边距。
dart
/// SliverToBoxAdapter 与 SliverPadding 示例
class SliverAdapterDemo extends StatelessWidget {
const SliverAdapterDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 150,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: const Text('Sliver Adapter'),
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Colors.teal, Colors.cyan],
),
),
),
),
),
SliverPadding(
padding: const EdgeInsets.all(16),
sliver: SliverToBoxAdapter(
child: Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'SliverToBoxAdapter',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
'将普通 Widget 转换为 Sliver,可以在 CustomScrollView 中使用任何 Widget。',
style: TextStyle(color: Colors.grey[600]),
),
],
),
),
),
),
),
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 16),
sliver: SliverToBoxAdapter(
child: Container(
height: 100,
decoration: BoxDecoration(
color: Colors.blue.shade100,
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Text('横幅广告区域'),
),
),
),
),
SliverPadding(
padding: const EdgeInsets.all(16),
sliver: SliverGrid(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 1.5,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
decoration: BoxDecoration(
color: Colors.primaries[index % Colors.primaries.length].withOpacity(0.2),
borderRadius: BorderRadius.circular(8),
),
child: Center(child: Text('卡片 $index')),
);
},
childCount: 6,
),
),
),
SliverPadding(
padding: const EdgeInsets.all(16),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Card(
child: ListTile(
leading: CircleAvatar(child: Text('$index')),
title: Text('列表项 $index'),
),
);
},
childCount: 10,
),
),
),
],
),
);
}
}
四、自定义 Sliver 组件实现
自定义 Sliver 组件可以实现独特的滚动效果和布局。
🎯 4.1 自定义 SliverPersistentHeader
自定义 SliverPersistentHeader 实现可折叠的头部效果。
dart
/// 自定义 SliverPersistentHeader 示例
class CustomSliverHeaderDemo extends StatelessWidget {
const CustomSliverHeaderDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverPersistentHeader(
pinned: true,
delegate: _CustomHeaderDelegate(
minHeight: 80,
maxHeight: 250,
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return ListTile(
title: Text('列表项 $index'),
subtitle: Text('这是第 $index 个列表项'),
);
},
childCount: 50,
),
),
],
),
);
}
}
class _CustomHeaderDelegate extends SliverPersistentHeaderDelegate {
final double minHeight;
final double maxHeight;
_CustomHeaderDelegate({
required this.minHeight,
required this.maxHeight,
});
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
final progress = shrinkOffset / (maxHeight - minHeight);
final opacity = 1.0 - progress;
final scale = 1.0 - progress * 0.3;
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.indigo,
Colors.purple.withOpacity(opacity),
],
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2 * progress),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Stack(
children: [
Positioned(
left: 16,
bottom: 16,
child: Opacity(
opacity: opacity,
child: Transform.scale(
scale: scale,
alignment: Alignment.bottomLeft,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'自定义头部',
style: TextStyle(
color: Colors.white,
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
'折叠进度: ${(progress * 100).toStringAsFixed(0)}%',
style: TextStyle(
color: Colors.white.withOpacity(opacity),
fontSize: 14,
),
),
],
),
),
),
),
Positioned(
right: 16,
bottom: 16,
child: Opacity(
opacity: progress,
child: const Text(
'已折叠',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
);
}
@override
double get maxExtent => maxHeight;
@override
double get minExtent => minHeight;
@override
bool shouldRebuild(covariant _CustomHeaderDelegate oldDelegate) {
return minHeight != oldDelegate.minHeight ||
maxHeight != oldDelegate.maxHeight;
}
}
📐 4.2 自定义 SliverChildDelegate
自定义 SliverChildDelegate 实现更灵活的列表项构建策略。
dart
/// 自定义 SliverChildDelegate 示例
class CustomSliverDelegateDemo extends StatelessWidget {
const CustomSliverDelegateDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('自定义 SliverChildDelegate')),
body: CustomScrollView(
slivers: [
SliverList(
delegate: _PagedChildDelegate(
itemCount: 1000,
pageSize: 20,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(child: Text('$index')),
title: Text('分页加载项 $index'),
subtitle: Text('页码: ${(index / 20).floor() + 1}'),
);
},
),
),
],
),
);
}
}
class _PagedChildDelegate extends SliverChildDelegate {
final int itemCount;
final int pageSize;
final Widget Function(BuildContext, int) itemBuilder;
_PagedChildDelegate({
required this.itemCount,
required this.pageSize,
required this.itemBuilder,
});
@override
Widget? build(BuildContext context, int index) {
if (index < 0 || index >= itemCount) return null;
return itemBuilder(context, index);
}
@override
int get estimatedChildCount => itemCount;
@override
double estimateMaxScrollOffset(
int firstIndex,
int lastIndex,
double leadingScrollOffset,
double trailingScrollOffset,
) {
return itemCount * (trailingScrollOffset - leadingScrollOffset) / (lastIndex - firstIndex + 1);
}
@override
int findIndexForKey(Key key) {
if (key is ValueKey<int>) {
return key.value;
}
return null!;
}
}
🔄 4.3 自定义 RenderSliver
通过自定义 RenderSliver 实现完全自定义的滚动布局效果。
dart
import 'dart:math' as math;
import 'package:flutter/rendering.dart';
/// 自定义 RenderSliver 示例
class CustomRenderSliverDemo extends StatelessWidget {
const CustomRenderSliverDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('自定义 RenderSliver')),
body: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Container(
height: 100,
color: Colors.blue.shade100,
child: const Center(child: Text('顶部区域')),
),
),
const SpiralSliver(
itemCount: 30,
itemSize: 60,
),
SliverToBoxAdapter(
child: Container(
height: 100,
color: Colors.green.shade100,
child: const Center(child: Text('底部区域')),
),
),
],
),
);
}
}
/// 螺旋布局 Sliver
class SpiralSliver extends LeafRenderObjectWidget {
final int itemCount;
final double itemSize;
const SpiralSliver({
super.key,
required this.itemCount,
this.itemSize = 50,
});
@override
RenderObject createRenderObject(BuildContext context) {
return RenderSpiralSliver(
itemCount: itemCount,
itemSize: itemSize,
);
}
@override
void updateRenderObject(BuildContext context, covariant RenderSpiralSliver renderObject) {
renderObject
..itemCount = itemCount
..itemSize = itemSize;
}
}
class RenderSpiralSliver extends RenderSliver {
int _itemCount;
double _itemSize;
RenderSpiralSliver({
required int itemCount,
required double itemSize,
}) : _itemCount = itemCount,
_itemSize = itemSize;
int get itemCount => _itemCount;
set itemCount(int value) {
if (_itemCount != value) {
_itemCount = value;
markNeedsLayout();
}
}
double get itemSize => _itemSize;
set itemSize(double value) {
if (_itemSize != value) {
_itemSize = value;
markNeedsLayout();
}
}
@override
void performLayout() {
final double totalHeight = _itemCount * _itemSize * 0.5;
geometry = SliverGeometry(
scrollExtent: totalHeight,
paintExtent: math.min(totalHeight, constraints.remainingPaintExtent),
maxPaintExtent: totalHeight,
hasVisualOverflow: totalHeight > constraints.remainingPaintExtent,
);
}
@override
void paint(PaintingContext context, Offset offset) {
final canvas = context.canvas;
canvas.save();
canvas.translate(offset.dx, offset.dy);
final visibleHeight = geometry!.paintExtent;
for (int i = 0; i < _itemCount; i++) {
final y = i * _itemSize * 0.5;
if (y + _itemSize < 0 || y > visibleHeight) continue;
final angle = i * 0.3;
final x = 150 + 50 * math.sin(angle);
final paint = Paint()
..color = Colors.primaries[i % Colors.primaries.length]
..style = PaintingStyle.fill;
canvas.drawCircle(
Offset(x, y + _itemSize / 2),
_itemSize / 3,
paint,
);
}
canvas.restore();
}
}
五、大数据量优化策略
大数据量场景下需要综合运用多种优化策略来保证性能。
📊 5.1 列表项复用与缓存
通过 KeepAlive 和 AutomaticKeepAlive 实现列表项的状态保持。
dart
/// 列表项复用与缓存示例
class ListCacheDemo extends StatelessWidget {
const ListCacheDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('列表项缓存')),
body: ListView.builder(
itemCount: 100,
cacheExtent: 500,
addAutomaticKeepAlives: true,
addRepaintBoundaries: true,
itemBuilder: (context, index) {
return _CachedListItem(index: index);
},
),
);
}
}
class _CachedListItem extends StatefulWidget {
final int index;
const _CachedListItem({required this.index});
@override
State<_CachedListItem> createState() => _CachedListItemState();
}
class _CachedListItemState extends State<_CachedListItem>
with AutomaticKeepAliveClientMixin {
int _tapCount = 0;
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
leading: CircleAvatar(
child: Text('${widget.index}'),
),
title: Text('列表项 ${widget.index}'),
subtitle: Text('点击次数: $_tapCount'),
trailing: ElevatedButton(
onPressed: () {
setState(() {
_tapCount++;
});
},
child: const Text('点击'),
),
),
);
}
}
🔄 5.2 分页加载实现
分页加载是处理大数据量的核心策略。
dart
/// 分页加载示例
class PaginationDemo extends StatefulWidget {
const PaginationDemo({super.key});
@override
State<PaginationDemo> createState() => _PaginationDemoState();
}
class _PaginationDemoState extends State<PaginationDemo> {
final List<DataItem> _items = [];
final int _pageSize = 20;
int _currentPage = 0;
bool _isLoading = false;
bool _hasMore = true;
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_loadFirstPage();
_scrollController.addListener(_onScroll);
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
Future<void> _loadFirstPage() async {
await _loadPage();
}
void _onScroll() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) {
_loadMore();
}
}
Future<void> _loadPage() async {
if (_isLoading || !_hasMore) return;
setState(() => _isLoading = true);
await Future.delayed(const Duration(milliseconds: 500));
final newItems = await _fetchItems(_currentPage, _pageSize);
setState(() {
_items.addAll(newItems);
_currentPage++;
_hasMore = newItems.length == _pageSize;
_isLoading = false;
});
}
Future<void> _loadMore() async {
await _loadPage();
}
Future<List<DataItem>> _fetchItems(int page, int size) async {
return List.generate(size, (index) {
final globalIndex = page * size + index;
return DataItem(
id: globalIndex,
title: 'Item $globalIndex',
subtitle: 'Page ${page + 1}',
);
});
}
Future<void> _onRefresh() async {
setState(() {
_items.clear();
_currentPage = 0;
_hasMore = true;
});
await _loadPage();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('分页加载')),
body: RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
controller: _scrollController,
itemCount: _items.length + (_hasMore ? 1 : 0),
itemBuilder: (context, index) {
if (index == _items.length) {
return _buildLoadingFooter();
}
return _buildItem(_items[index]);
},
),
),
);
}
Widget _buildItem(DataItem item) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
leading: CircleAvatar(child: Text('${item.id}')),
title: Text(item.title),
subtitle: Text(item.subtitle),
),
);
}
Widget _buildLoadingFooter() {
return Container(
padding: const EdgeInsets.all(16),
alignment: Alignment.center,
child: _isLoading
? const CircularProgressIndicator()
: const Text('没有更多数据'),
);
}
}
class DataItem {
final int id;
final String title;
final String subtitle;
DataItem({
required this.id,
required this.title,
required this.subtitle,
});
}
🎯 5.3 虚拟化性能监控
实现虚拟化列表的性能监控和优化指标。
dart
/// 虚拟化性能监控示例
class PerformanceMonitorDemo extends StatefulWidget {
const PerformanceMonitorDemo({super.key});
@override
State<PerformanceMonitorDemo> createState() => _PerformanceMonitorDemoState();
}
class _PerformanceMonitorDemoState extends State<PerformanceMonitorDemo> {
final ScrollController _scrollController = ScrollController();
int _visibleItemCount = 0;
int _totalItemCount = 10000;
double _scrollOffset = 0;
int _buildCount = 0;
DateTime? _lastBuildTime;
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void _onScroll() {
setState(() {
_scrollOffset = _scrollController.offset;
});
}
void _onBuild() {
final now = DateTime.now();
if (_lastBuildTime != null) {
final diff = now.difference(_lastBuildTime!).inMilliseconds;
if (diff < 16) {
_buildCount++;
}
}
_lastBuildTime = now;
}
void _updateVisibleCount(bool visible) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {
if (visible) {
_visibleItemCount++;
} else {
_visibleItemCount = _visibleItemCount > 0
? _visibleItemCount - 1
: 0;
}
});
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('性能监控')),
body: Column(
children: [
_buildPerformancePanel(),
Expanded(
child: ListView.builder(
controller: _scrollController,
itemCount: _totalItemCount,
cacheExtent: 500,
itemBuilder: (context, index) {
_onBuild();
return _PerformanceMonitoredItem(
index: index,
onVisible: _updateVisibleCount,
);
},
),
),
],
),
);
}
Widget _buildPerformancePanel() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue.shade50,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildMetric('总项数', '$_totalItemCount'),
_buildMetric('可见项', '$_visibleItemCount'),
_buildMetric('滚动位置', '${_scrollOffset.toStringAsFixed(0)}px'),
],
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildMetric('构建次数', '$_buildCount'),
_buildMetric('帧率', _buildCount < 60 ? '60fps' : '低'),
_buildMetric('内存', '虚拟化'),
],
),
],
),
);
}
Widget _buildMetric(String label, String value) {
return Column(
children: [
Text(
value,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
Text(
label,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
);
}
}
class _PerformanceMonitoredItem extends StatefulWidget {
final int index;
final Function(bool) onVisible;
const _PerformanceMonitoredItem({
required this.index,
required this.onVisible,
});
@override
State<_PerformanceMonitoredItem> createState() =>
_PerformanceMonitoredItemState();
}
class _PerformanceMonitoredItemState
extends State<_PerformanceMonitoredItem> {
@override
void initState() {
super.initState();
widget.onVisible(true);
}
@override
void dispose() {
widget.onVisible(false);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
leading: CircleAvatar(child: Text('${widget.index}')),
title: Text('Item ${widget.index}'),
subtitle: Text('构建时间: ${DateTime.now().millisecondsSinceEpoch}'),
),
);
}
}
六、OpenHarmony 平台适配
在 OpenHarmony 平台上使用列表虚拟化需要注意平台特性适配。
📱 6.1 OpenHarmony 滚动物理效果适配
dart
import 'dart:io';
/// OpenHarmony 滚动物理效果适配
class PlatformScrollPhysics {
static ScrollPhysics getPhysics() {
if (Platform.isAndroid) {
return const ClampingScrollPhysics();
}
return const BouncingScrollPhysics();
}
}
/// 平台适配列表示例
class PlatformAdaptedListDemo extends StatelessWidget {
const PlatformAdaptedListDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('平台适配列表')),
body: ListView.builder(
physics: PlatformScrollPhysics.getPhysics(),
itemCount: 100,
itemBuilder: (context, index) {
return ListTile(
title: Text('Item $index'),
subtitle: Text('平台: ${Platform.operatingSystem}'),
);
},
),
);
}
}
🔄 6.2 OpenHarmony 手势适配
dart
/// OpenHarmony 手势适配示例
class OpenHarmonyGestureDemo extends StatefulWidget {
const OpenHarmonyGestureDemo({super.key});
@override
State<OpenHarmonyGestureDemo> createState() =>
_OpenHarmonyGestureDemoState();
}
class _OpenHarmonyGestureDemoState extends State<OpenHarmonyGestureDemo> {
final ScrollController _scrollController = ScrollController();
double _dragDistance = 0;
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('OpenHarmony 手势适配')),
body: GestureDetector(
onVerticalDragUpdate: (details) {
setState(() {
_dragDistance += details.delta.dy;
});
},
onVerticalDragEnd: (details) {
if (_dragDistance.abs() > 100) {
if (_dragDistance > 0) {
_scrollController.animateTo(
_scrollController.offset - 200,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
} else {
_scrollController.animateTo(
_scrollController.offset + 200,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
}
}
setState(() {
_dragDistance = 0;
});
},
child: ListView.builder(
controller: _scrollController,
itemCount: 50,
itemBuilder: (context, index) {
return Card(
margin: const EdgeInsets.all(8),
child: ListTile(
leading: CircleAvatar(child: Text('$index')),
title: Text('列表项 $index'),
subtitle: Text('拖拽距离: ${_dragDistance.toStringAsFixed(1)}'),
),
);
},
),
),
);
}
}
七、最佳实践与调试技巧
🎯 7.1 性能优化最佳实践
┌─────────────────────────────────────────────────────────────┐
│ 列表虚拟化最佳实践 │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 使用 ListView.builder 替代 ListView(children:[])│ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 2. 合理设置 cacheExtent 预加载范围 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 3. 使用 addRepaintBoundaries 避免重绘 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 4. 列表项使用 const 构造函数 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 5. 避免在 itemBuilder 中执行耗时操作 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 6. 使用 AutomaticKeepAliveClientMixin 保持状态 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 7. 分页加载避免一次性加载大量数据 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
🔧 7.2 常见问题与解决方案
| 问题 | 解决方案 |
|---|---|
| 列表滚动卡顿 | 检查 itemBuilder 是否有耗时操作 |
| 内存占用过高 | 减小 cacheExtent,使用分页加载 |
| 列表项状态丢失 | 使用 AutomaticKeepAliveClientMixin |
| 滚动位置跳动 | 使用 ScrollController 保持位置 |
| 列表项重建频繁 | 使用 const 构造函数,添加 key |
| 滚动性能差 | 添加 RepaintBoundary,优化布局层级 |
📊 7.3 调试技巧
dart
/// 列表调试工具
class ListDebugTools {
static void printScrollInfo(ScrollController controller) {
print('=== 滚动信息 ===');
print('当前位置: ${controller.offset}');
print('最大滚动: ${controller.position.maxScrollExtent}');
print('最小滚动: ${controller.position.minScrollExtent}');
print('视口高度: ${controller.position.viewportDimension}');
print('===============');
}
static void printBuildInfo(int index, DateTime startTime) {
final duration = DateTime.now().difference(startTime);
print('Item $index 构建耗时: ${duration.inMilliseconds}ms');
}
}
/// 调试列表示例
class DebugListDemo extends StatefulWidget {
const DebugListDemo({super.key});
@override
State<DebugListDemo> createState() => _DebugListDemoState();
}
class _DebugListDemoState extends State<DebugListDemo> {
final ScrollController _scrollController = ScrollController();
int _buildCount = 0;
@override
void initState() {
super.initState();
_scrollController.addListener(() {
ListDebugTools.printScrollInfo(_scrollController);
});
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('调试列表'),
actions: [
IconButton(
icon: const Icon(Icons.info),
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('调试信息'),
content: Text('构建次数: $_buildCount'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('确定'),
),
],
),
);
},
),
],
),
body: ListView.builder(
controller: _scrollController,
itemCount: 100,
itemBuilder: (context, index) {
_buildCount++;
final startTime = DateTime.now();
return Builder(
builder: (context) {
ListDebugTools.printBuildInfo(index, startTime);
return ListTile(
title: Text('Item $index'),
subtitle: Text('构建次数: $_buildCount'),
);
},
);
},
),
);
}
}
八、完整示例代码
以下是完整的列表虚拟化系统示例代码:
dart
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'dart:math' as math;
import 'dart:io';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '高性能列表虚拟化系统',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const ListVirtualizationHomePage(),
);
}
}
class ListVirtualizationHomePage extends StatelessWidget {
const ListVirtualizationHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('📊 高性能列表虚拟化系统'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildSectionCard(
context,
title: 'ListView.builder',
description: '基础虚拟化列表',
icon: Icons.list,
color: Colors.blue,
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const BasicListViewDemo()),
),
),
_buildSectionCard(
context,
title: '高级 ListView',
description: '下拉刷新与上拉加载',
icon: Icons.refresh,
color: Colors.green,
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const AdvancedListViewDemo()),
),
),
_buildSectionCard(
context,
title: 'GridView',
description: '网格虚拟化布局',
icon: Icons.grid_view,
color: Colors.orange,
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const GridViewDemo()),
),
),
_buildSectionCard(
context,
title: 'CustomScrollView',
description: '组合滚动效果',
icon: Icons.view_agenda,
color: Colors.purple,
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const CustomScrollViewDemo()),
),
),
_buildSectionCard(
context,
title: 'SliverAnimatedList',
description: '动画列表效果',
icon: Icons.animation,
color: Colors.teal,
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const SliverAnimatedListDemo()),
),
),
_buildSectionCard(
context,
title: '自定义 Sliver',
description: 'RenderSliver 实现',
icon: Icons.brush,
color: Colors.indigo,
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const CustomRenderSliverDemo()),
),
),
_buildSectionCard(
context,
title: '分页加载',
description: '大数据量分页处理',
icon: Icons.pages,
color: Colors.cyan,
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const PaginationDemo()),
),
),
_buildSectionCard(
context,
title: '性能监控',
description: '虚拟化性能分析',
icon: Icons.speed,
color: Colors.red,
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const PerformanceMonitorDemo()),
),
),
],
),
);
}
Widget _buildSectionCard(
BuildContext context, {
required String title,
required String description,
required IconData icon,
required Color color,
required VoidCallback onTap,
}) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: color, size: 28),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
description,
style: TextStyle(color: Colors.grey[600], fontSize: 14),
),
],
),
),
Icon(Icons.chevron_right, color: Colors.grey[400]),
],
),
),
),
);
}
}
class BasicListViewDemo extends StatelessWidget {
const BasicListViewDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('ListView.builder 基础')),
body: ListView.builder(
itemCount: 10000,
cacheExtent: 500,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(
child: Text('${index + 1}'),
),
title: Text('列表项 ${index + 1}'),
subtitle: Text('这是第 ${index + 1} 个列表项的描述'),
trailing: const Icon(Icons.chevron_right),
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('点击了第 ${index + 1} 项')),
);
},
);
},
),
);
}
}
class AdvancedListViewDemo extends StatefulWidget {
const AdvancedListViewDemo({super.key});
@override
State<AdvancedListViewDemo> createState() => _AdvancedListViewDemoState();
}
class _AdvancedListViewDemoState extends State<AdvancedListViewDemo> {
final ScrollController _scrollController = ScrollController();
final List<String> _items = List.generate(100, (index) => 'Item $index');
bool _isLoading = false;
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
@override
void dispose() {
_scrollController.removeListener(_onScroll);
_scrollController.dispose();
super.dispose();
}
void _onScroll() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) {
_loadMore();
}
}
Future<void> _loadMore() async {
if (_isLoading) return;
setState(() => _isLoading = true);
await Future.delayed(const Duration(seconds: 1));
setState(() {
final startIndex = _items.length;
for (int i = 0; i < 20; i++) {
_items.add('Item ${startIndex + i}');
}
_isLoading = false;
});
}
Future<void> _onRefresh() async {
await Future.delayed(const Duration(seconds: 1));
setState(() {
_items.clear();
_items.addAll(List.generate(100, (index) => 'Item $index (刷新)'));
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('高级 ListView 配置')),
body: RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
controller: _scrollController,
physics: const BouncingScrollPhysics(),
cacheExtent: 1000,
itemCount: _items.length + 1,
itemBuilder: (context, index) {
if (index == _items.length) {
return _buildLoadingIndicator();
}
return _buildListItem(index);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_scrollController.animateTo(
0,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
},
child: const Icon(Icons.arrow_upward),
),
);
}
Widget _buildListItem(int index) {
return Dismissible(
key: Key(_items[index]),
background: Container(
color: Colors.red,
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: 20),
child: const Icon(Icons.delete, color: Colors.white),
),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
setState(() {
_items.removeAt(index);
});
},
child: Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
leading: Hero(
tag: 'item-$index',
child: CircleAvatar(
backgroundColor: Colors.primaries[index % Colors.primaries.length],
child: Text('${index + 1}'),
),
),
title: Text(_items[index]),
subtitle: Text('索引: $index'),
trailing: const Icon(Icons.chevron_right),
),
),
);
}
Widget _buildLoadingIndicator() {
return Container(
padding: const EdgeInsets.all(16),
alignment: Alignment.center,
child: _isLoading
? const CircularProgressIndicator()
: const Text('上拉加载更多'),
);
}
}
class GridViewDemo extends StatelessWidget {
const GridViewDemo({super.key});
List<Color> get _colors => List.generate(
100,
(index) => Color.fromRGBO(
(index * 25) % 256,
(index * 50) % 256,
(index * 75) % 256,
1.0,
),
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('GridView 虚拟化')),
body: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 4,
crossAxisSpacing: 4,
childAspectRatio: 1.0,
),
itemCount: 100,
cacheExtent: 500,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => GridDetailPage(
index: index,
color: _colors[index],
),
),
);
},
child: Hero(
tag: 'grid-item-$index',
child: Container(
color: _colors[index],
child: Center(
child: Text(
'${index + 1}',
style: const TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
),
),
),
);
},
),
);
}
}
class GridDetailPage extends StatelessWidget {
final int index;
final Color color;
const GridDetailPage({
super.key,
required this.index,
required this.color,
});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: color,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
title: Text('详情 ${index + 1}'),
),
body: Center(
child: Hero(
tag: 'grid-item-$index',
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Center(
child: Text(
'${index + 1}',
style: const TextStyle(
color: Colors.white,
fontSize: 48,
fontWeight: FontWeight.bold,
),
),
),
),
),
),
);
}
}
class CustomScrollViewDemo extends StatelessWidget {
const CustomScrollViewDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
_buildSliverAppBar(),
_buildSliverPersistentHeader(),
_buildSliverGrid(),
_buildSliverList(),
_buildSliverFillRemaining(),
],
),
);
}
Widget _buildSliverAppBar() {
return SliverAppBar(
expandedHeight: 200,
floating: false,
pinned: true,
snap: false,
flexibleSpace: FlexibleSpaceBar(
title: const Text('CustomScrollView'),
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Colors.blue, Colors.purple],
),
),
child: const Center(
child: Icon(
Icons.list_alt,
size: 80,
color: Colors.white54,
),
),
),
),
);
}
Widget _buildSliverPersistentHeader() {
return SliverPersistentHeader(
pinned: true,
delegate: _SectionHeaderDelegate(
title: '网格区域',
color: Colors.blue.shade100,
),
);
}
Widget _buildSliverGrid() {
return SliverGrid(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 150,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 1.0,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
margin: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.primaries[index % Colors.primaries.length].withOpacity(0.3),
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text(
'Grid $index',
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
);
},
childCount: 12,
),
);
}
Widget _buildSliverList() {
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
leading: CircleAvatar(
child: Text('$index'),
),
title: Text('列表项 $index'),
subtitle: Text('这是第 $index 个列表项'),
trailing: const Icon(Icons.chevron_right),
),
);
},
childCount: 20,
),
);
}
Widget _buildSliverFillRemaining() {
return SliverFillRemaining(
hasScrollBody: false,
child: Container(
color: Colors.grey.shade200,
padding: const EdgeInsets.all(32),
child: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.info_outline, size: 48, color: Colors.grey),
SizedBox(height: 16),
Text(
'列表底部区域',
style: TextStyle(fontSize: 18, color: Colors.grey),
),
],
),
),
);
}
}
class _SectionHeaderDelegate extends SliverPersistentHeaderDelegate {
final String title;
final Color color;
_SectionHeaderDelegate({required this.title, required this.color});
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
color: color,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
);
}
@override
double get maxExtent => 50;
@override
double get minExtent => 50;
@override
bool shouldRebuild(covariant _SectionHeaderDelegate oldDelegate) {
return title != oldDelegate.title || color != oldDelegate.color;
}
}
class SliverAnimatedListDemo extends StatefulWidget {
const SliverAnimatedListDemo({super.key});
@override
State<SliverAnimatedListDemo> createState() => _SliverAnimatedListDemoState();
}
class _SliverAnimatedListDemoState extends State<SliverAnimatedListDemo> {
final GlobalKey<SliverAnimatedListState> _listKey = GlobalKey();
final List<String> _items = List.generate(10, (index) => 'Item $index');
int _counter = 10;
void _addItem() {
final index = _items.length;
_items.insert(index, 'Item $_counter');
_listKey.currentState?.insertItem(index);
_counter++;
}
void _removeItem(int index) {
_listKey.currentState?.removeItem(
index,
(context, animation) => _buildRemovedItem(_items[index], animation),
);
_items.removeAt(index);
}
Widget _buildRemovedItem(String item, Animation<double> animation) {
return SizeTransition(
sizeFactor: animation,
child: Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
color: Colors.red.shade100,
child: ListTile(
title: Text(item),
trailing: const Icon(Icons.delete, color: Colors.red),
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('SliverAnimatedList')),
body: CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
title: const Text('动画列表'),
actions: [
IconButton(
icon: const Icon(Icons.add),
onPressed: _addItem,
),
],
),
SliverAnimatedList(
key: _listKey,
initialItemCount: _items.length,
itemBuilder: (context, index, animation) {
return _buildItem(_items[index], index, animation);
},
),
],
),
);
}
Widget _buildItem(String item, int index, Animation<double> animation) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1, 0),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeOut,
)),
child: SizeTransition(
sizeFactor: animation,
child: Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
leading: CircleAvatar(
child: Text('$index'),
),
title: Text(item),
subtitle: Text('索引: $index'),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => _removeItem(index),
),
),
),
),
);
}
}
class CustomRenderSliverDemo extends StatelessWidget {
const CustomRenderSliverDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('自定义 RenderSliver')),
body: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Container(
height: 100,
color: Colors.blue.shade100,
child: const Center(child: Text('顶部区域')),
),
),
const SpiralSliver(
itemCount: 30,
itemSize: 60,
),
SliverToBoxAdapter(
child: Container(
height: 100,
color: Colors.green.shade100,
child: const Center(child: Text('底部区域')),
),
),
],
),
);
}
}
class SpiralSliver extends LeafRenderObjectWidget {
final int itemCount;
final double itemSize;
const SpiralSliver({
super.key,
required this.itemCount,
this.itemSize = 50,
});
@override
RenderObject createRenderObject(BuildContext context) {
return RenderSpiralSliver(
itemCount: itemCount,
itemSize: itemSize,
);
}
@override
void updateRenderObject(BuildContext context, RenderSpiralSliver renderObject) {
renderObject
..itemCount = itemCount
..itemSize = itemSize;
}
}
class RenderSpiralSliver extends RenderSliver {
int _itemCount;
double _itemSize;
RenderSpiralSliver({
required int itemCount,
required double itemSize,
}) : _itemCount = itemCount,
_itemSize = itemSize;
int get itemCount => _itemCount;
set itemCount(int value) {
if (_itemCount != value) {
_itemCount = value;
markNeedsLayout();
}
}
double get itemSize => _itemSize;
set itemSize(double value) {
if (_itemSize != value) {
_itemSize = value;
markNeedsLayout();
}
}
@override
void performLayout() {
final double totalHeight = _itemCount * _itemSize * 0.5;
geometry = SliverGeometry(
scrollExtent: totalHeight,
paintExtent: math.min(totalHeight, constraints.remainingPaintExtent),
maxPaintExtent: totalHeight,
hasVisualOverflow: totalHeight > constraints.remainingPaintExtent,
);
}
@override
void paint(PaintingContext context, Offset offset) {
final canvas = context.canvas;
canvas.save();
canvas.translate(offset.dx, offset.dy);
final visibleHeight = geometry!.paintExtent;
for (int i = 0; i < _itemCount; i++) {
final y = i * _itemSize * 0.5;
if (y + _itemSize < 0 || y > visibleHeight) continue;
final angle = i * 0.3;
final x = 150 + 50 * math.sin(angle);
final paint = Paint()
..color = Colors.primaries[i % Colors.primaries.length]
..style = PaintingStyle.fill;
canvas.drawCircle(
Offset(x, y + _itemSize / 2),
_itemSize / 3,
paint,
);
}
canvas.restore();
}
}
class PaginationDemo extends StatefulWidget {
const PaginationDemo({super.key});
@override
State<PaginationDemo> createState() => _PaginationDemoState();
}
class _PaginationDemoState extends State<PaginationDemo> {
final List<DataItem> _items = [];
final int _pageSize = 20;
int _currentPage = 0;
bool _isLoading = false;
bool _hasMore = true;
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_loadFirstPage();
_scrollController.addListener(_onScroll);
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
Future<void> _loadFirstPage() async {
await _loadPage();
}
void _onScroll() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) {
_loadMore();
}
}
Future<void> _loadPage() async {
if (_isLoading || !_hasMore) return;
setState(() => _isLoading = true);
await Future.delayed(const Duration(milliseconds: 500));
final newItems = await _fetchItems(_currentPage, _pageSize);
setState(() {
_items.addAll(newItems);
_currentPage++;
_hasMore = newItems.length == _pageSize;
_isLoading = false;
});
}
Future<void> _loadMore() async {
await _loadPage();
}
Future<List<DataItem>> _fetchItems(int page, int size) async {
return List.generate(size, (index) {
final globalIndex = page * size + index;
return DataItem(
id: globalIndex,
title: 'Item $globalIndex',
subtitle: 'Page ${page + 1}',
);
});
}
Future<void> _onRefresh() async {
setState(() {
_items.clear();
_currentPage = 0;
_hasMore = true;
});
await _loadPage();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('分页加载')),
body: RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
controller: _scrollController,
itemCount: _items.length + (_hasMore ? 1 : 0),
itemBuilder: (context, index) {
if (index == _items.length) {
return _buildLoadingFooter();
}
return _buildItem(_items[index]);
},
),
),
);
}
Widget _buildItem(DataItem item) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
leading: CircleAvatar(child: Text('${item.id}')),
title: Text(item.title),
subtitle: Text(item.subtitle),
),
);
}
Widget _buildLoadingFooter() {
return Container(
padding: const EdgeInsets.all(16),
alignment: Alignment.center,
child: _isLoading
? const CircularProgressIndicator()
: const Text('没有更多数据'),
);
}
}
class DataItem {
final int id;
final String title;
final String subtitle;
DataItem({
required this.id,
required this.title,
required this.subtitle,
});
}
class PerformanceMonitorDemo extends StatefulWidget {
const PerformanceMonitorDemo({super.key});
@override
State<PerformanceMonitorDemo> createState() => _PerformanceMonitorDemoState();
}
class _PerformanceMonitorDemoState extends State<PerformanceMonitorDemo> {
final ScrollController _scrollController = ScrollController();
int _visibleItemCount = 0;
int _totalItemCount = 10000;
double _scrollOffset = 0;
int _buildCount = 0;
DateTime? _lastBuildTime;
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
void _onScroll() {
setState(() {
_scrollOffset = _scrollController.offset;
});
}
void _onBuild() {
final now = DateTime.now();
if (_lastBuildTime != null) {
final diff = now.difference(_lastBuildTime!).inMilliseconds;
if (diff < 16) {
_buildCount++;
}
}
_lastBuildTime = now;
}
void _updateVisibleCount(bool visible) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
setState(() {
if (visible) {
_visibleItemCount++;
} else {
_visibleItemCount = _visibleItemCount > 0
? _visibleItemCount - 1
: 0;
}
});
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('性能监控')),
body: Column(
children: [
_buildPerformancePanel(),
Expanded(
child: ListView.builder(
controller: _scrollController,
itemCount: _totalItemCount,
cacheExtent: 500,
itemBuilder: (context, index) {
_onBuild();
return _PerformanceMonitoredItem(
index: index,
onVisible: _updateVisibleCount,
);
},
),
),
],
),
);
}
Widget _buildPerformancePanel() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue.shade50,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildMetric('总项数', '$_totalItemCount'),
_buildMetric('可见项', '$_visibleItemCount'),
_buildMetric('滚动位置', '${_scrollOffset.toStringAsFixed(0)}px'),
],
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildMetric('构建次数', '$_buildCount'),
_buildMetric('帧率', _buildCount < 60 ? '60fps' : '低'),
_buildMetric('内存', '虚拟化'),
],
),
],
),
);
}
Widget _buildMetric(String label, String value) {
return Column(
children: [
Text(
value,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
Text(
label,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
);
}
}
class _PerformanceMonitoredItem extends StatefulWidget {
final int index;
final Function(bool) onVisible;
const _PerformanceMonitoredItem({
required this.index,
required this.onVisible,
});
@override
State<_PerformanceMonitoredItem> createState() =>
_PerformanceMonitoredItemState();
}
class _PerformanceMonitoredItemState
extends State<_PerformanceMonitoredItem> {
@override
void initState() {
super.initState();
widget.onVisible(true);
}
@override
void dispose() {
widget.onVisible(false);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
leading: CircleAvatar(child: Text('${widget.index}')),
title: Text('Item ${widget.index}'),
subtitle: Text('构建时间: ${DateTime.now().millisecondsSinceEpoch}'),
),
);
}
}
九、总结
本文深入探讨了 Flutter for OpenHarmony 的高性能列表虚拟化系统,从架构原理到具体实现,涵盖了以下核心内容:
📚 核心知识点回顾
- 虚拟化架构:理解 Viewport、Sliver、Scrollable 三层架构的工作原理
- 基础实现:掌握 ListView.builder、GridView 的虚拟化配置
- Sliver 家族:灵活运用 CustomScrollView 组合各种滚动效果
- 自定义 Sliver:通过 RenderSliver 实现独特的滚动布局
- 优化策略:列表项缓存、分页加载、性能监控
- 平台适配:OpenHarmony 平台的滚动物理效果和手势适配
🎯 最佳实践要点
- 始终使用 builder 构造函数处理大数据量
- 合理配置 cacheExtent 平衡性能与体验
- 使用 AutomaticKeepAliveClientMixin 保持状态
- 分页加载避免内存压力
- 添加 RepaintBoundary 优化重绘
🚀 进阶方向
- 深入研究 RenderSliver 的布局协议
- 实现更复杂的自定义滚动效果
- 探索列表项的预加载策略
- 结合 Isolate 处理大数据计算
通过掌握这些技术,你可以构建出高性能、流畅的大数据量列表应用,为用户提供优质的滚动体验。
💡 提示:在实际项目中,建议使用 Flutter DevTools 进行性能分析,及时发现和解决列表性能问题。