mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-20 03:06:59 +08:00
feat: sliver wrap (#1858)
* feat: sliver wrap * opt: list * update --------- Co-authored-by: dom <githubaccount56556@proton.me>
This commit is contained in:
committed by
GitHub
parent
01a74e191a
commit
c01318c066
@@ -3,17 +3,19 @@ import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class HttpError extends StatelessWidget {
|
||||
const HttpError({
|
||||
super.key,
|
||||
this.isSliver = true,
|
||||
this.errMsg,
|
||||
this.onReload,
|
||||
this.btnText,
|
||||
super.key,
|
||||
this.safeArea = true,
|
||||
});
|
||||
|
||||
final bool isSliver;
|
||||
final String? errMsg;
|
||||
final VoidCallback? onReload;
|
||||
final String? btnText;
|
||||
final bool safeArea;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -57,7 +59,8 @@ class HttpError extends StatelessWidget {
|
||||
style: TextStyle(color: theme.colorScheme.primary),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 40 + MediaQuery.viewPaddingOf(context).bottom),
|
||||
if (safeArea)
|
||||
SizedBox(height: 40 + MediaQuery.viewPaddingOf(context).bottom),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
352
lib/common/widgets/sliver_wrap.dart
Normal file
352
lib/common/widgets/sliver_wrap.dart
Normal file
@@ -0,0 +1,352 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class SliverFixedWrap extends SliverMultiBoxAdaptorWidget {
|
||||
final double mainAxisExtent;
|
||||
final double spacing;
|
||||
final double runSpacing;
|
||||
|
||||
const SliverFixedWrap({
|
||||
super.key,
|
||||
required super.delegate,
|
||||
required this.mainAxisExtent,
|
||||
this.spacing = 0,
|
||||
this.runSpacing = 0,
|
||||
});
|
||||
|
||||
@override
|
||||
SliverWrapElement createElement() =>
|
||||
SliverWrapElement(this, replaceMovedChildren: true);
|
||||
|
||||
@override
|
||||
RenderSliverFixedWrap createRenderObject(BuildContext context) {
|
||||
return RenderSliverFixedWrap(
|
||||
childManager: context as SliverWrapElement,
|
||||
mainAxisExtent: mainAxisExtent,
|
||||
spacing: spacing,
|
||||
runSpacing: runSpacing,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
RenderSliverFixedWrap renderObject,
|
||||
) {
|
||||
renderObject
|
||||
..mainAxisExtent = mainAxisExtent
|
||||
..spacing = spacing
|
||||
..runSpacing = runSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
class SliverWrapParentData extends SliverMultiBoxAdaptorParentData {
|
||||
double crossAxisOffset = 0.0;
|
||||
|
||||
@override
|
||||
String toString() => 'crossAxisOffset=$crossAxisOffset; ${super.toString()}';
|
||||
}
|
||||
|
||||
class _Row {
|
||||
final int startIndex;
|
||||
final int endIndex;
|
||||
final List<double> childWidths;
|
||||
|
||||
_Row({
|
||||
required this.startIndex,
|
||||
required this.endIndex,
|
||||
required this.childWidths,
|
||||
});
|
||||
}
|
||||
|
||||
class RenderSliverFixedWrap extends RenderSliverMultiBoxAdaptor {
|
||||
RenderSliverFixedWrap({
|
||||
required super.childManager,
|
||||
required double mainAxisExtent,
|
||||
double spacing = 0.0,
|
||||
double runSpacing = 0.0,
|
||||
}) : _mainAxisExtent = mainAxisExtent,
|
||||
_spacing = spacing,
|
||||
_runSpacing = runSpacing {
|
||||
assert(mainAxisExtent > 0.0 && mainAxisExtent.isFinite);
|
||||
}
|
||||
|
||||
double _mainAxisExtent;
|
||||
double get mainAxisExtent => _mainAxisExtent;
|
||||
set mainAxisExtent(double value) {
|
||||
if (_mainAxisExtent == value) return;
|
||||
_mainAxisExtent = value;
|
||||
markRowsDirty();
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
double _spacing;
|
||||
double get spacing => _spacing;
|
||||
set spacing(double value) {
|
||||
if (_spacing == value) return;
|
||||
_spacing = value;
|
||||
markRowsDirty();
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
double _runSpacing;
|
||||
double get runSpacing => _runSpacing;
|
||||
set runSpacing(double value) {
|
||||
if (_runSpacing == value) return;
|
||||
_runSpacing = value;
|
||||
markRowsDirty();
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
final List<_Row> _rows = [];
|
||||
|
||||
void markRowsDirty() {
|
||||
_rows.clear();
|
||||
}
|
||||
|
||||
@override
|
||||
void setupParentData(RenderObject child) {
|
||||
if (child.parentData is! SliverWrapParentData) {
|
||||
child.parentData = SliverWrapParentData();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
double childCrossAxisPosition(RenderBox child) {
|
||||
return (child.parentData as SliverWrapParentData).crossAxisOffset;
|
||||
}
|
||||
|
||||
double _childCrossExtent(RenderBox child) {
|
||||
assert(child.hasSize);
|
||||
return switch (constraints.axis) {
|
||||
Axis.horizontal => child.size.height,
|
||||
Axis.vertical => child.size.width,
|
||||
};
|
||||
}
|
||||
|
||||
RenderBox _getOrCreateChildAtIndex(
|
||||
int index,
|
||||
BoxConstraints constraints,
|
||||
RenderBox? child,
|
||||
) {
|
||||
assert(firstChild != null);
|
||||
|
||||
if (index < indexOf(firstChild!)) {
|
||||
do {
|
||||
child = insertAndLayoutLeadingChild(constraints, parentUsesSize: true);
|
||||
assert(child != null);
|
||||
} while (indexOf(child!) > index);
|
||||
|
||||
assert(indexOf(child) == index);
|
||||
|
||||
return child;
|
||||
} else if (index > indexOf(lastChild!)) {
|
||||
do {
|
||||
child = insertAndLayoutChild(
|
||||
constraints,
|
||||
after: lastChild,
|
||||
parentUsesSize: true,
|
||||
);
|
||||
assert(child != null);
|
||||
} while (indexOf(child!) < index);
|
||||
|
||||
assert(indexOf(child) == index);
|
||||
|
||||
return child;
|
||||
} else {
|
||||
child = firstChild;
|
||||
while (indexOf(child!) < index) {
|
||||
child = childAfter(child);
|
||||
}
|
||||
if (indexOf(child) == index) {
|
||||
child.layout(constraints, parentUsesSize: true);
|
||||
return child;
|
||||
}
|
||||
throw RangeError.value(index, 'index', 'Value not included in children');
|
||||
}
|
||||
}
|
||||
|
||||
bool _buildNextRow(int start, BoxConstraints childConstraints) {
|
||||
final int childCount = childManager.childCount;
|
||||
|
||||
if (start >= childCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final crossAxisExtent = constraints.crossAxisExtent;
|
||||
|
||||
final List<double> widths = [];
|
||||
int idx = start;
|
||||
RenderBox? child;
|
||||
for (var totalWidth = -_spacing; idx < childCount; idx++) {
|
||||
child = _getOrCreateChildAtIndex(idx, childConstraints, child);
|
||||
final childWidth = _childCrossExtent(child);
|
||||
totalWidth += childWidth + _spacing;
|
||||
|
||||
if (totalWidth <= crossAxisExtent) {
|
||||
widths.add(childWidth);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_rows.add(_Row(startIndex: start, endIndex: idx - 1, childWidths: widths));
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
childManager
|
||||
..didStartLayout()
|
||||
..setDidUnderflow(false);
|
||||
|
||||
final constraints = this.constraints;
|
||||
final childCount = childManager.childCount;
|
||||
|
||||
final rowHeight = _mainAxisExtent + _runSpacing;
|
||||
|
||||
final scrollOffset = constraints.scrollOffset;
|
||||
|
||||
final firstCacheOffset = scrollOffset + constraints.cacheOrigin;
|
||||
final lastCacheOffset = scrollOffset + constraints.remainingCacheExtent;
|
||||
|
||||
final firstNeededRow = math.max(0, firstCacheOffset ~/ rowHeight);
|
||||
final lastNeededRow = math.max(0, lastCacheOffset ~/ rowHeight);
|
||||
|
||||
if (firstChild == null) {
|
||||
if (!addInitialChild()) {
|
||||
geometry = SliverGeometry.zero;
|
||||
childManager.didFinishLayout();
|
||||
return;
|
||||
}
|
||||
firstChild!.layout(
|
||||
constraints.toFixedConstraints(_mainAxisExtent),
|
||||
parentUsesSize: true,
|
||||
);
|
||||
}
|
||||
|
||||
while (_rows.length <= lastNeededRow) {
|
||||
final int startIndex = _rows.isEmpty ? 0 : _rows.last.endIndex + 1;
|
||||
if (!_buildNextRow(
|
||||
startIndex,
|
||||
constraints.toFixedConstraints(_mainAxisExtent),
|
||||
)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert(firstNeededRow >= 0);
|
||||
|
||||
final int firstKeptRow = firstNeededRow.clamp(0, _rows.length - 1);
|
||||
final int lastKeptRow = lastNeededRow.clamp(0, _rows.length - 1);
|
||||
|
||||
final int firstKeptIndex = _rows[firstKeptRow].startIndex;
|
||||
final int lastKeptIndex = _rows[lastKeptRow].endIndex;
|
||||
|
||||
collectGarbage(
|
||||
calculateLeadingGarbage(firstIndex: firstKeptIndex),
|
||||
calculateTrailingGarbage(lastIndex: lastKeptIndex),
|
||||
);
|
||||
|
||||
RenderBox? child;
|
||||
for (var r = firstKeptRow; r <= lastKeptRow; r++) {
|
||||
final row = _rows[r];
|
||||
final rowStartOffset = r * rowHeight;
|
||||
double crossOffset = 0.0;
|
||||
for (var i = row.startIndex; i <= row.endIndex; i++) {
|
||||
child = _getOrCreateChildAtIndex(
|
||||
i,
|
||||
constraints.toFixedConstraints(_mainAxisExtent),
|
||||
child,
|
||||
);
|
||||
(child.parentData as SliverWrapParentData)
|
||||
..layoutOffset = rowStartOffset
|
||||
..crossAxisOffset = crossOffset;
|
||||
crossOffset += row.childWidths[i - row.startIndex] + _spacing;
|
||||
}
|
||||
}
|
||||
|
||||
final endOffset = _rows.last.endIndex == childCount - 1
|
||||
? (_rows.length * rowHeight)
|
||||
: (_rows.last.startIndex + 1) * rowHeight;
|
||||
|
||||
final double estimatedMaxScrollOffset;
|
||||
if (_rows.length <= lastNeededRow || childCount == 0) {
|
||||
estimatedMaxScrollOffset = childManager.estimateMaxScrollOffset(
|
||||
constraints,
|
||||
firstIndex: firstKeptIndex,
|
||||
lastIndex: lastKeptIndex,
|
||||
leadingScrollOffset: firstKeptRow * rowHeight,
|
||||
trailingScrollOffset: endOffset,
|
||||
);
|
||||
} else {
|
||||
estimatedMaxScrollOffset = _rows.length * rowHeight;
|
||||
}
|
||||
|
||||
final double paintExtent = calculatePaintOffset(
|
||||
constraints,
|
||||
from: firstKeptRow * rowHeight,
|
||||
to: endOffset,
|
||||
);
|
||||
final double cacheExtent = calculateCacheOffset(
|
||||
constraints,
|
||||
from: firstCacheOffset,
|
||||
to: lastCacheOffset,
|
||||
);
|
||||
|
||||
geometry = SliverGeometry(
|
||||
scrollExtent: estimatedMaxScrollOffset,
|
||||
paintExtent: paintExtent,
|
||||
cacheExtent: cacheExtent,
|
||||
maxPaintExtent: estimatedMaxScrollOffset,
|
||||
hasVisualOverflow:
|
||||
endOffset >
|
||||
constraints.scrollOffset + constraints.remainingPaintExtent,
|
||||
);
|
||||
|
||||
if (estimatedMaxScrollOffset <= endOffset) {
|
||||
childManager.setDidUnderflow(true);
|
||||
}
|
||||
|
||||
childManager.didFinishLayout();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
markRowsDirty();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class SliverWrapElement extends SliverMultiBoxAdaptorElement {
|
||||
SliverWrapElement(SliverFixedWrap super.widget, {super.replaceMovedChildren});
|
||||
|
||||
@override
|
||||
void performRebuild() {
|
||||
(renderObject as RenderSliverFixedWrap).markRowsDirty();
|
||||
super.performRebuild();
|
||||
}
|
||||
}
|
||||
|
||||
extension on SliverConstraints {
|
||||
BoxConstraints toFixedConstraints(double mainAxisExtent) {
|
||||
switch (axis) {
|
||||
case Axis.horizontal:
|
||||
return BoxConstraints(
|
||||
minHeight: 0,
|
||||
maxHeight: crossAxisExtent,
|
||||
minWidth: mainAxisExtent,
|
||||
maxWidth: mainAxisExtent,
|
||||
);
|
||||
case Axis.vertical:
|
||||
return BoxConstraints(
|
||||
minWidth: 0,
|
||||
maxWidth: crossAxisExtent,
|
||||
minHeight: mainAxisExtent,
|
||||
maxHeight: mainAxisExtent,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user