我希望将数据显示在列表中,并使用粘性标题对每个记录进行分组。我找到了很多关于如何在水平上做到这一点的例子,但我只有两个。所以每条记录都会有一个主组和一个子组。因此,当用户滚动时,我希望当前的主组和当前的子组具有粘性。
数据集示例
Main Group 1
Sub Group 1
Record 1
...
Record n
Sub Group 2
...
Record n
...
Sub Group n
...
Record n
...
Main Group n
...
Sub Group n
...
Record n
Run Code Online (Sandbox Code Playgroud)
我已经成功地嵌套了 3 个 ListView 并获取所有要渲染的数据,并且还使用了 Sticky_headers 包中的 StickyHeader 来使主组粘性,但是当在子组上使用 StickyHeader 时,它只是向右滚动通过主组团体
Main Group 1
Sub Group 1
Record 1
...
Record n
Sub Group 2
...
Record n
...
Sub Group n
...
Record n
...
Main Group n
...
Sub Group n
...
Record n
Run Code Online (Sandbox Code Playgroud)
在最坏的情况下,数据集将有大约 100 条记录,这些记录被分组在不同的主组和子组中,因此可以在嵌套列表中使用收缩换行为 true,但如果有另一种方法可以避免这种情况,那就是最好的。
有人对如何解决这个问题有任何想法吗?
我能够通过将ScrollController
父级传递ListView.builder
给父级和子级来创建嵌套的粘性标头StickyHeader
,以便两个标头都将获得相同的滚动相关信息。
我扩展了这些类StickyHeader
,RenderStickyHeader
以便我们可以添加偏移量,使子标题在滚动时保持在父标题下方。
另请注意,这里我假设父标题和子标题的高度相同,如果情况并非如此,那么您应该将父标题的高度作为参数发送给方法determineStuckOffsetWithHeight
中的方法。performLayout
_MyRenderStickyHeader
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
final ScrollController scrollController = ScrollController();
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView.builder(
itemCount: 10,
controller: scrollController,
itemBuilder: (BuildContext context, int mainGroupIndex) {
return StickyHeader(
header: Text('Main Group: ${mainGroupIndex + 1}'),
controller: scrollController,
overlapHeaders: false,
content: ListView.builder(
itemCount: 10,
primary: false,
shrinkWrap: true,
itemBuilder: (BuildContext context, int subGroupIndex) {
final list = ListView.builder(
itemCount: 10,
primary: false,
shrinkWrap: true,
itemBuilder: (BuildContext context, int recordIndex) {
return Text('Record: ${recordIndex + 1}');
},
);
return _MyStickyHeader(
header: Text('Sub Group: ${subGroupIndex + 1}'),
controller: scrollController,
content: list,
);
},
),
);
},
),
);
}
}
class _MyStickyHeader extends StickyHeader {
_MyStickyHeader({
Key? key,
required this.header,
required this.content,
this.overlapHeaders: false,
this.controller,
this.callback,
}) : super(
key: key,
header: header,
content: content,
overlapHeaders: overlapHeaders,
controller: controller,
callback: callback,
);
final Widget header;
final Widget content;
final bool overlapHeaders;
final ScrollController? controller;
final RenderStickyHeaderCallback? callback;
@override
_MyRenderStickyHeader createRenderObject(BuildContext context) {
final scrollPosition =
this.controller?.position ?? Scrollable.of(context)!.position;
return _MyRenderStickyHeader(
scrollPosition: scrollPosition,
callback: this.callback,
overlapHeaders: this.overlapHeaders,
);
}
@override
void updateRenderObject(
BuildContext context, _MyRenderStickyHeader renderObject) {
final scrollPosition =
this.controller?.position ?? Scrollable.of(context)!.position;
renderObject
..scrollPosition = scrollPosition
..callback = this.callback
..overlapHeaders = this.overlapHeaders;
}
}
class _MyRenderStickyHeader extends RenderStickyHeader {
bool _overlapHeaders;
RenderStickyHeaderCallback? _callback;
ScrollPosition _scrollPosition;
_MyRenderStickyHeader({
required ScrollPosition scrollPosition,
RenderStickyHeaderCallback? callback,
bool overlapHeaders: false,
RenderBox? header,
RenderBox? content,
}) : _overlapHeaders = overlapHeaders,
_callback = callback,
_scrollPosition = scrollPosition,
super(
scrollPosition: scrollPosition,
callback: callback,
overlapHeaders: overlapHeaders,
header: header,
content: content,
);
RenderBox get _headerBox => lastChild!;
RenderBox get _contentBox => firstChild!;
@override
void performLayout() {
assert(childCount == 2);
final childConstraints = constraints.loosen();
_headerBox.layout(childConstraints, parentUsesSize: true);
_contentBox.layout(childConstraints, parentUsesSize: true);
final headerHeight = _headerBox.size.height;
final contentHeight = _contentBox.size.height;
final width = max(constraints.minWidth, _contentBox.size.width);
final height = max(constraints.minHeight,
_overlapHeaders ? contentHeight : headerHeight + contentHeight);
size = Size(width, height);
assert(size.width == constraints.constrainWidth(width));
assert(size.height == constraints.constrainHeight(height));
assert(size.isFinite);
final contentParentData =
_contentBox.parentData as MultiChildLayoutParentData;
contentParentData.offset =
Offset(0.0, _overlapHeaders ? 0.0 : headerHeight);
final double stuckOffset = determineStuckOffsetWithHeight(headerHeight);
final double maxOffset = height - headerHeight;
final headerParentData =
_headerBox.parentData as MultiChildLayoutParentData;
headerParentData.offset =
Offset(0.0, max(0.0, min(-stuckOffset, maxOffset)));
if (_callback != null) {
final stuckAmount =
max(min(headerHeight, stuckOffset), -headerHeight) / headerHeight;
_callback!(stuckAmount);
}
}
double determineStuckOffsetWithHeight(double headerHeight) {
final scrollBox =
_scrollPosition.context.notificationContext!.findRenderObject();
if (scrollBox?.attached ?? false) {
try {
return localToGlobal(Offset.zero, ancestor: scrollBox).dy -
headerHeight;
} catch (e) {
// ignore and fall-through and return 0.0
}
}
return 0.0;
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
1502 次 |
最近记录: |