-
-
Notifications
You must be signed in to change notification settings - Fork 67
Closed
Description
问题描述:
版本: 1.26.1
import` 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:scrollview_observer/scrollview_observer.dart';
class MessageItem {
final String title;
bool isExpand;
final String content;
MessageItem(
{required this.title, required this.content, this.isExpand = false});
}
class ScrollListPage extends StatefulWidget {
const ScrollListPage({super.key});
@override
State<ScrollListPage> createState() => _scrollListPage();
}
class _scrollListPage extends State<ScrollListPage>
with WidgetsBindingObserver {
final ScrollController scrollController = ScrollController();
late ListObserverController observerController;
late ChatScrollObserver chatObserver;
List<MessageItem> messages = [
MessageItem(title: "这是第二条内容", content: "这是第二条内容", isExpand: false),
MessageItem(
isExpand: false,
title: '''这条消息第一个展开内容很多长展开有问题,位置不能固定''',
content:
'''Identify the cause of the user's headache and provide possible solutions or recommendations.</summary>[Query vital signs data to check for any abnormalities that could be associated with headaches.]:- bodyTemperatures:Data is not available for 2025-01-06 - 2025-01-13. Here is the last valid data on 2024-11-18 17:23:00: 37.78 °CYou can kindly remind the user to synchronize the data.- restingHeartRates:Data is not available for 2025-01-06 - 2025-01-13. Here is the last valid data on 2024-11-18 17:25:00: 30.0 BPMYou can kindly remind the user to synchronize the data.- bloodGlucoses:Data is not available for 2025-01-06 - 2025-01-13. Here is the last valid data on 2024-11-18 17:23:00: 100.0 mg/dLYou can kindly remind the user to synchronize the data.- systolicPressures:Data is not available for 2025-01-06 - 2025-01-13. Here is the last valid data on 2024-11-18 17:24:00: 100.0 mmHgYou can kindly remind the user to synchronize the data.- diastolicPressures:Data is not available for 2025-01-06 - 2025-01-13. Here is the last valid data on 2024-11-18 17:24:00: 60.0 mmHgYou can kindly remind the user to synchronize the data.- oxygenSaturations:Data is not available for 2025-01-06 - 2025-01-13. Here is the last valid data on 2024-11-18 17:24:00: 1.0 %You can kindly remind the user to synchronize the data.- hrvDatas:Data is not available for 2025-01-06 - 2025-01-13. Here is the last valid data on 2024-11-18 17:25:00: 30.0 msYou can kindly remind the user to synchronize the data.- heartRates:Data is not available for 2025-01-06 - 2025-01-13. Here is the last valid data on 2024-11-18 20:22:00: 101.0 BPMYou can kindly remind the user to synchronize the data.- respiratoryRates:Data is not available for 2025-01-06 - 2025-01-13. Here is the last valid data on 2024-11-18 17:20:00: 12.0 BPMYou can kindly remind the user to synchronize the data.Reference: Apple Health[Retrieve heart-related metrics to identify any cardiovascular factors contributing to headaches.]:- hrvDatas:Data is not available for 2025-01-06 - 2025-01-13. Here is the last valid data on 2024-11-18 17:25:00: 30.0 msYou can kindly remind the user to synchronize the data.- heartRates:Data is not available for 2025-01-06 - 2025-01-13. Here is the last valid data on 2024-11-18 20:22:00: 101.0 BPMYou can kindly remind the user to synchronize the data.- restingHeartRates:Data is not available for 2025-01-06 - 2025-01-13. Here is the last valid data on 2024-11-18 17:25:00: 30.0 BPMYou can kindly remind the user to synchronize the data.Reference: Apple Health[Retrieve respiratory health metrics to assess any breathing-related issues linked to headaches.]:- respiratoryRates:Data is not available for 2025-01-06 - 2025-01-13. Here is the last valid data on 2024-11-18 17:20:00: 12.0 BPMYou can kindly remind the user to synchronize the data.- oxygenSaturations:Data is not available for 2025-01-06 - 2025-01-13. Here is the last valid data on 2024-11-18 17:24:00: 1.0 %You can kindly remind the user to synchronize the data.Reference: Apple Health[Query lab results for indicators related to metabolic or systemic conditions that might cause headaches.]:No related information was found. Please verify if the relevant data has been uploaded or synchronized''',
),
];
@override
void initState() {
super.initState();
initChatObserver();
}
@override
void dispose() {
observerController.controller?.dispose();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeMetrics() {
super.didChangeMetrics();
chatObserver.observeSwitchShrinkWrap();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
Widget resultWidget = EasyRefresh.builder(
footer: const ClassicFooter(
position: IndicatorPosition.above,
infiniteOffset: null,
),
onLoad: () async {
await Future.delayed(const Duration(seconds: 2));
},
childBuilder: (context, physics) {
var scrollViewPhysics = physics.applyTo(
ChatObserverClampingScrollPhysics(
observer: chatObserver,
),
);
// 新增的代码
var listViewPhysics =
physics.applyTo(ChatObserverClampingScrollPhysics(
observer: chatObserver,
));
if (chatObserver.isShrinkWrap) {
listViewPhysics = const NeverScrollableScrollPhysics()
.applyTo(listViewPhysics);
}
Widget resultWidget = ListView.separated(
physics: listViewPhysics,
padding: const EdgeInsets.only(
left: 10,
right: 10,
top: 15,
bottom: 15,
),
shrinkWrap: chatObserver.isShrinkWrap,
reverse: true,
controller: scrollController,
cacheExtent: 200 * messages.length + 200,
itemBuilder: ((context, index) {
return _itemBuilder(context, index);
}),
separatorBuilder: (_, __) {
return Container(height: 20);
},
itemCount: messages.length,
);
if (chatObserver.isShrinkWrap) {
resultWidget = SingleChildScrollView(
reverse: true,
physics: scrollViewPhysics,
child: Container(
alignment: Alignment.topCenter,
height: constraints.maxHeight + 0.001,
child: resultWidget,
),
);
}
return resultWidget;
},
);
resultWidget = ListViewObserver(
controller: observerController,
child: resultWidget,
);
resultWidget = Align(
alignment: Alignment.topCenter,
child: resultWidget,
);
return resultWidget;
},
),
);
}
Widget _itemBuilder(BuildContext context, int index) {
MessageItem item = messages[index];
print("## _itemBuilder :$index ${item.isExpand}");
return Container(
margin: const EdgeInsets.only(bottom: 10),
width: 150,
color: Colors.grey[200],
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ExpandBox(
childContent: item.content,
isExpanded: item.isExpand,
callback: (bool isExpand) {
item.isExpand = isExpand;
expandCb(index);
},
index: index,
),
Text(item.title),
],
),
);
}
void initChatObserver() {
observerController = ListObserverController(
controller: scrollController,
)..cacheJumpIndexOffset = false;
chatObserver = ChatScrollObserver(observerController)
// ..fixedPositionOffset = -1
..fixedPositionOffset = 5
..toRebuildScrollViewCallback = () {
setState(() {});
};
chatObserver.standby(
changeCount: messages.length,
mode: ChatScrollObserverHandleMode.specified,
refIndexType: ChatScrollObserverRefIndexType.itemIndex,
);
}
void expandCb(int index) {
final isLastItem = index == messages.length - 1;
// 默认是对比上一个 item 来定位
// 最后一个 item,没有上一个可以对比,则对比自身
// 此处对比自身无意义,因为由 customAdjustPosition 去完全控制保持位置的计算,忽略即可。
final refItemIndex = isLastItem ? index : index + 1;
chatObserver.standby(
mode: ChatScrollObserverHandleMode.specified,
refIndexType: ChatScrollObserverRefIndexType.itemIndex,
refItemIndex: refItemIndex,
refItemIndexAfterUpdate: refItemIndex,
customAdjustPosition: (model) {
// 仅处理最后一个 item 的情况
if (!isLastItem) return null;
// 使用 变化前后的底部间距差 来保持位置
final delta =
model.newPosition.extentAfter - model.oldPosition.extentAfter;
return model.adjustPosition + delta;
},
customAdjustPositionDelta: (model) {
// 仅处理不是最后一个 item 的情况
if (isLastItem) return null;
// 以 变化前后的 item 偏移差 来保持位置
final adjustPosition = model.adjustPosition;
final delta = model.currentItemModel.layoutOffset -
model.observer.refItemLayoutOffset;
if (delta < 0) {
// 收起
// 因消息的高度太大,在收起时,Flutter 内部对列表的偏移计算有问题
// 所以这里调整计算方式,改为:视口的当前偏移量 - 内容变化量
// 注:减去 adjustPosition,是因为保持位置功能的内部会加上 adjustPosition
return model.currentItemModel.viewportPixels + delta - adjustPosition;
}
// 展开
return delta;
},
);
}
}
class ExpandBox extends StatefulWidget {
final String childContent;
final Function(bool)? callback;
final int index;
final bool isExpanded;
const ExpandBox({
super.key,
required this.childContent,
this.callback,
required this.index,
required this.isExpanded,
});
@override
State<ExpandBox> createState() => _ExpandBoxState();
}
class _ExpandBoxState extends State<ExpandBox> {
bool isExpanded = false;
@override
void initState() {
super.initState();
isExpanded = widget.isExpanded;
print("## initState $isExpanded");
}
@override
Widget build(
BuildContext context,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ElevatedButton(
onPressed: () {
final value = !isExpanded;
widget.callback!(value);
setState(() {
isExpanded = value;
});
},
child: isExpanded ? const Text('收起') : const Text('展开')),
if (isExpanded) Text(widget.childContent),
if (isExpanded) const Text('Expanded content'),
],
);
}
}Metadata
Metadata
Assignees
Labels
No labels