-
-
Notifications
You must be signed in to change notification settings - Fork 67
Closed
Description
问题描述
当使用 ListView.padding 参数或在 ListView 外层包裹 Padding widget 时,ListViewObserver 的 onObserve 回调完全不触发。更严重的是,即使移除 padding 配置,回调也无法恢复。
复现步骤
我创建了一个最小测试用例,可以通过三个按钮切换不同的 padding 配置:
import 'package:flutter/material.dart';
import 'package:scrollview_observer/scrollview_observer.dart';
void main() => runApp(const MinimalTestApp());
class MinimalTestApp extends StatelessWidget {
const MinimalTestApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(home: MinimalTestPage());
}
}
class MinimalTestPage extends StatefulWidget {
const MinimalTestPage({Key? key}) : super(key: key);
@override
State<MinimalTestPage> createState() => _MinimalTestPageState();
}
class _MinimalTestPageState extends State<MinimalTestPage> {
final ScrollController scrollController = ScrollController();
late ListObserverController observerController;
int callbackCount = 0;
int testMode = 0; // 0: 无padding, 1: ListView.padding, 2: 外层Padding
@override
void initState() {
super.initState();
observerController = ListObserverController(controller: scrollController);
// 手动触发观察
WidgetsBinding.instance.endOfFrame.then((_) {
if (mounted) {
observerController.dispatchOnceObserve();
}
});
}
@override
void dispose() {
scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('最简测试 - 回调: $callbackCount'),
backgroundColor: callbackCount > 0 ? Colors.green : Colors.red,
),
body: Column(
children: [
// 控制面板
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
setState(() {
testMode = 0;
callbackCount = 0;
});
},
style: ElevatedButton.styleFrom(
backgroundColor: testMode == 0 ? Colors.blue : Colors.grey,
),
child: const Text('0. 无 padding'),
),
ElevatedButton(
onPressed: () {
setState(() {
testMode = 1;
callbackCount = 0;
});
},
style: ElevatedButton.styleFrom(
backgroundColor: testMode == 1
? Colors.orange
: Colors.grey,
),
child: const Text('1. ListView.padding'),
),
ElevatedButton(
onPressed: () {
setState(() {
testMode = 2;
callbackCount = 0;
});
},
style: ElevatedButton.styleFrom(
backgroundColor: testMode == 2
? Colors.purple
: Colors.grey,
),
child: const Text('2. 外层 Padding'),
),
],
),
),
// 说明文字
Container(
padding: const EdgeInsets.all(8),
color: Colors.yellow.shade100,
child: Text(
_getDescription(),
style: const TextStyle(fontSize: 12),
textAlign: TextAlign.center,
),
),
// ListView 区域
Expanded(child: _buildListView()),
],
),
);
}
String _getDescription() {
switch (testMode) {
case 0:
return '✅ 预期工作:无任何 padding,只有 itemExtent';
case 1:
return '⚠️ 预期失败:使用 ListView.padding 参数(创建 SliverPadding)';
case 2:
return '❓ 待验证:外层包裹 Padding widget';
default:
return '';
}
}
Widget _buildListView() {
// 基础 ListView(无 padding)
Widget listView = ListView.builder(
controller: scrollController,
padding: testMode == 1
? const EdgeInsets.all(16)
: null, // 测试点1: ListView.padding
itemExtent: 50.0,
itemCount: 50,
itemBuilder: (context, index) {
return Container(
color: Colors.black12,
child: Center(child: Text('Item $index')),
);
},
);
// 测试点2: 外层 Padding
if (testMode == 2) {
listView = Padding(padding: const EdgeInsets.all(16), child: listView);
}
// 包装 Observer
return ListViewObserver(
controller: observerController,
autoTriggerObserveTypes: const [ObserverAutoTriggerObserveType.scrollEnd],
triggerOnObserveType: ObserverTriggerOnObserveType.directly,
onObserve: (resultModel) {
setState(() {
callbackCount++;
});
debugPrint('✅ onObserve #$callbackCount - mode: $testMode');
debugPrint(' firstChild: ${resultModel.firstChild?.index}');
debugPrint(' displaying: ${resultModel.displayingChildIndexList}');
},
child: listView,
);
}
}预期行为
- 模式 0(无 padding):✅ 回调正常触发,AppBar 显示绿色
- 模式 1(
ListView.padding):应该正常工作,但实际回调不触发 - 模式 2(外层
Padding):应该正常工作,但实际回调不触发
实际行为
- 模式 0:✅ 正常工作
- 模式 1:❌
onObserve完全不触发,AppBar 保持红色 - 模式 2:❌
onObserve完全不触发,AppBar 保持红色
关键问题:一旦切换到模式 1 或 2,即使再切换回模式 0,回调也永久失效,必须重启应用。
初步分析
通过分析源码,我发现可能的原因:
-
ListView.padding 创建 SliverPadding 包装层:使用
ListView.padding参数会在 widget 树中创建SliverPadding(Viewport → SliverPadding → SliverList),而不是直接的Viewport → SliverList -
fetchTargetSliverContexts 的 visitor 无法穿透 SliverPadding:
observer_widget.dart中的fetchTargetSliverContexts()使用visitChildElements递归查找 Sliver,但可能无法穿透SliverPadding找到内部的SliverList -
缓存机制导致永久失效:
targetSliverContexts使用了缓存(if (ctxs.isEmpty)才重新查找),一旦首次查找失败(因为 SliverPadding 阻挡),后续即使移除 padding 也不会重新查找
环境信息
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 3.32.1, on Microsoft Windows [版本 10.0.22631.5189], locale zh-CN)
[√] Windows Version (11 专业版 64-bit, 23H2, 2009)
[!] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses
[√] Chrome - develop for the web
[√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.14.20)
[√] Android Studio (version 2024.2)
[√] IntelliJ IDEA Ultimate Edition (version 2024.3)
[√] VS Code (version 1.106.0)
[√] Connected device (3 available)
[!] Network resources
X A network error occurred while checking "https://maven.google.com/": 信号灯超时时间已到
- Flutter: 3.32.1 (stable)
- scrollview_observer: 1.26.2
- Platform: Windows 11
建议的解决方案
- 修复 visitor 遍历逻辑:使
fetchTargetSliverContexts()能够穿透SliverPadding等包装层 - 改进缓存策略:在 widget 树结构变化时清除
targetSliverContexts缓存 - 文档说明:如果这是设计限制,应在文档中明确说明不支持
ListView.padding和外层Padding
临时解决方案
目前我只能通过不使用任何 padding 来规避此问题,但这对实际应用开发造成很大不便。
感谢开发团队对这个问题的关注!
Metadata
Metadata
Assignees
Labels
No labels