Implement getDryBaseline for Stack and Overlay (#146253)

This commit is contained in:
LongCatIsLooong 2024-05-02 08:31:12 +08:00 committed by GitHub
parent 603416233c
commit ab8ecd5e74
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 176 additions and 74 deletions

View File

@ -229,6 +229,29 @@ class StackParentData extends ContainerBoxParentData<RenderBox> {
/// children in the stack.
bool get isPositioned => top != null || right != null || bottom != null || left != null || width != null || height != null;
/// Computes the [BoxConstraints] the stack layout algorithm would give to
/// this child, given the [Size] of the stack.
///
/// This method should only be called when [isPositioned] is true for the child.
BoxConstraints positionedChildConstraints(Size stackSize) {
assert(isPositioned);
final double? width = switch ((left, right)) {
(final double left?, final double right?) => stackSize.width - right - left,
(_, _) => this.width,
};
final double? height = switch ((top, bottom)) {
(final double top?, final double bottom?) => stackSize.height - bottom - top,
(_, _) => this.height,
};
assert(height == null || !height.isNaN);
assert(width == null || !width.isNaN);
return BoxConstraints.tightFor(
width: width == null ? null : math.max(0.0, width),
height: height == null ? null : math.max(0.0, height),
);
}
@override
String toString() {
final List<String> values = <String>[
@ -354,17 +377,11 @@ class RenderStack extends RenderBox
}
}
Alignment? _resolvedAlignment;
void _resolve() {
if (_resolvedAlignment != null) {
return;
}
_resolvedAlignment = alignment.resolve(textDirection);
}
Alignment get _resolvedAlignment => _resolvedAlignmentCache ??= alignment.resolve(textDirection);
Alignment? _resolvedAlignmentCache;
void _markNeedResolution() {
_resolvedAlignment = null;
_resolvedAlignmentCache = null;
markNeedsLayout();
}
@ -489,53 +506,59 @@ class RenderStack extends RenderBox
static bool layoutPositionedChild(RenderBox child, StackParentData childParentData, Size size, Alignment alignment) {
assert(childParentData.isPositioned);
assert(child.parentData == childParentData);
bool hasVisualOverflow = false;
BoxConstraints childConstraints = const BoxConstraints();
if (childParentData.left != null && childParentData.right != null) {
childConstraints = childConstraints.tighten(width: size.width - childParentData.right! - childParentData.left!);
} else if (childParentData.width != null) {
childConstraints = childConstraints.tighten(width: childParentData.width);
}
if (childParentData.top != null && childParentData.bottom != null) {
childConstraints = childConstraints.tighten(height: size.height - childParentData.bottom! - childParentData.top!);
} else if (childParentData.height != null) {
childConstraints = childConstraints.tighten(height: childParentData.height);
}
final BoxConstraints childConstraints = childParentData.positionedChildConstraints(size);
child.layout(childConstraints, parentUsesSize: true);
final double x;
if (childParentData.left != null) {
x = childParentData.left!;
} else if (childParentData.right != null) {
x = size.width - childParentData.right! - child.size.width;
} else {
x = alignment.alongOffset(size - child.size as Offset).dx;
}
final double x = switch (childParentData) {
StackParentData(:final double left?) => left,
StackParentData(:final double right?) => size.width - right - child.size.width,
StackParentData() => alignment.alongOffset(size - child.size as Offset).dx,
};
if (x < 0.0 || x + child.size.width > size.width) {
hasVisualOverflow = true;
}
final double y;
if (childParentData.top != null) {
y = childParentData.top!;
} else if (childParentData.bottom != null) {
y = size.height - childParentData.bottom! - child.size.height;
} else {
y = alignment.alongOffset(size - child.size as Offset).dy;
}
if (y < 0.0 || y + child.size.height > size.height) {
hasVisualOverflow = true;
}
final double y = switch (childParentData) {
StackParentData(:final double top?) => top,
StackParentData(:final double bottom?) => size.height - bottom - child.size.height,
StackParentData() => alignment.alongOffset(size - child.size as Offset).dy,
};
childParentData.offset = Offset(x, y);
return x < 0.0 || x + child.size.width > size.width
|| y < 0.0 || y + child.size.height > size.height;
}
return hasVisualOverflow;
static double? _baselineForChild(RenderBox child, Size stackSize, BoxConstraints nonPositionedChildConstraints, Alignment alignment, TextBaseline baseline) {
final StackParentData childParentData = child.parentData! as StackParentData;
final BoxConstraints childConstraints = childParentData.isPositioned
? childParentData.positionedChildConstraints(stackSize)
: nonPositionedChildConstraints;
final double? baselineOffset = child.getDryBaseline(childConstraints, baseline);
if (baselineOffset == null) {
return null;
}
final double y = switch (childParentData) {
StackParentData(:final double top?) => top,
StackParentData(:final double bottom?) => stackSize.height - bottom - child.getDryLayout(childConstraints).height,
StackParentData() => alignment.alongOffset(stackSize - child.getDryLayout(childConstraints) as Offset).dy,
};
return baselineOffset + y;
}
@override
double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) {
final BoxConstraints nonPositionedChildConstraints = switch (fit) {
StackFit.loose => constraints.loosen(),
StackFit.expand => BoxConstraints.tight(constraints.biggest),
StackFit.passthrough => constraints,
};
final Alignment alignment = _resolvedAlignment;
final Size size = getDryLayout(constraints);
BaselineOffset baselineOffset = BaselineOffset.noBaseline;
for (RenderBox? child = firstChild; child != null; child = childAfter(child)) {
baselineOffset = baselineOffset.minOf(BaselineOffset(_baselineForChild(child, size, nonPositionedChildConstraints, alignment, baseline)));
}
return baselineOffset.offset;
}
@override
@ -548,8 +571,6 @@ class RenderStack extends RenderBox
}
Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
_resolve();
assert(_resolvedAlignment != null);
bool hasNonPositionedChildren = false;
if (childCount == 0) {
return (constraints.biggest.isFinite) ? constraints.biggest : constraints.smallest;
@ -603,15 +624,15 @@ class RenderStack extends RenderBox
layoutChild: ChildLayoutHelper.layoutChild,
);
assert(_resolvedAlignment != null);
final Alignment resolvedAlignment = _resolvedAlignment;
RenderBox? child = firstChild;
while (child != null) {
final StackParentData childParentData = child.parentData! as StackParentData;
if (!childParentData.isPositioned) {
childParentData.offset = _resolvedAlignment!.alongOffset(size - child.size as Offset);
childParentData.offset = resolvedAlignment.alongOffset(size - child.size as Offset);
} else {
_hasVisualOverflow = layoutPositionedChild(child, childParentData, size, _resolvedAlignment!) || _hasVisualOverflow;
_hasVisualOverflow = layoutPositionedChild(child, childParentData, size, resolvedAlignment) || _hasVisualOverflow;
}
assert(child.parentData == childParentData);
@ -740,6 +761,25 @@ class RenderIndexedStack extends RenderStack {
return offset.offset;
}
@override
double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) {
final RenderBox? displayedChild = _childAtIndex();
if (displayedChild == null) {
return null;
}
final BoxConstraints nonPositionedChildConstraints = switch (fit) {
StackFit.loose => constraints.loosen(),
StackFit.expand => BoxConstraints.tight(constraints.biggest),
StackFit.passthrough => constraints,
};
final Alignment alignment = _resolvedAlignment;
final Size size = getDryLayout(constraints);
return RenderStack._baselineForChild(displayedChild, size, nonPositionedChildConstraints, alignment, baseline);
}
@override
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
final RenderBox? displayedChild = _childAtIndex();

View File

@ -3,7 +3,6 @@
// found in the LICENSE file.
import 'dart:collection';
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
@ -967,6 +966,35 @@ mixin _RenderTheaterMixin on RenderBox {
}
}
@override
double? computeDistanceToActualBaseline(TextBaseline baseline) {
assert(!debugNeedsLayout);
BaselineOffset baselineOffset = BaselineOffset.noBaseline;
for (final RenderBox child in _childrenInPaintOrder()) {
assert(!child.debugNeedsLayout);
final StackParentData childParentData = child.parentData! as StackParentData;
baselineOffset = baselineOffset.minOf(BaselineOffset(child.getDistanceToActualBaseline(baseline)) + childParentData.offset.dy);
}
return baselineOffset.offset;
}
static double? baselineForChild(RenderBox child, Size theaterSize, BoxConstraints nonPositionedChildConstraints, Alignment alignment, TextBaseline baseline) {
final StackParentData childParentData = child.parentData! as StackParentData;
final BoxConstraints childConstraints = childParentData.isPositioned
? childParentData.positionedChildConstraints(theaterSize)
: nonPositionedChildConstraints;
final double? baselineOffset = child.getDryBaseline(childConstraints, baseline);
if (baselineOffset == null) {
return null;
}
final double y = switch (childParentData) {
StackParentData(:final double top?) => top,
StackParentData(:final double bottom?) => theaterSize.height - bottom - child.getDryLayout(childConstraints).height,
StackParentData() => alignment.alongOffset(theaterSize - child.getDryLayout(childConstraints) as Offset).dy,
};
return baselineOffset + y;
}
void layoutChild(RenderBox child, BoxConstraints nonPositionedChildConstraints) {
final StackParentData childParentData = child.parentData! as StackParentData;
final Alignment alignment = theater._resolvedAlignment;
@ -1201,25 +1229,20 @@ class _RenderTheater extends RenderBox with ContainerRenderObjectMixin<RenderBox
}
@override
double? computeDistanceToActualBaseline(TextBaseline baseline) {
assert(!debugNeedsLayout);
double? result;
RenderBox? child = _firstOnstageChild;
while (child != null) {
assert(!child.debugNeedsLayout);
final StackParentData childParentData = child.parentData! as StackParentData;
double? candidate = child.getDistanceToActualBaseline(baseline);
if (candidate != null) {
candidate += childParentData.offset.dy;
if (result != null) {
result = math.min(result, candidate);
} else {
result = candidate;
double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) {
final Size size = constraints.biggest.isFinite
? constraints.biggest
: _findSizeDeterminingChild().getDryLayout(constraints);
final BoxConstraints nonPositionedChildConstraints = BoxConstraints.tight(size);
final Alignment alignment = theater._resolvedAlignment;
BaselineOffset baselineOffset = BaselineOffset.noBaseline;
for (final RenderBox child in _childrenInPaintOrder()) {
baselineOffset = baselineOffset.minOf(BaselineOffset(
_RenderTheaterMixin.baselineForChild(child, size, nonPositionedChildConstraints, alignment, baseline),
));
}
}
child = childParentData.nextSibling;
}
return result;
return baselineOffset.offset;
}
@override
@ -2247,6 +2270,15 @@ final class _RenderDeferredLayoutBox extends RenderProxyBox with _RenderTheaterM
super.markNeedsLayout();
}
@override
double? computeDryBaseline(BoxConstraints constraints, TextBaseline baseline) {
final RenderBox? child = this.child;
if (child == null) {
return null;
}
return _RenderTheaterMixin.baselineForChild(child, constraints.biggest, constraints, theater._resolvedAlignment, baseline);
}
@override
RenderObject? get debugLayoutParent => _layoutSurrogate;

View File

@ -10,6 +10,36 @@ import 'rendering_tester.dart';
void main() {
TestRenderingFlutterBinding.ensureInitialized();
test('StackParentData basic test', () {
final StackParentData parentData = StackParentData();
const Size stackSize = Size(800.0, 600.0);
expect(parentData.isPositioned, isFalse);
parentData.width = -100.0;
expect(parentData.isPositioned, isTrue);
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 0.0));
parentData.width = 100.0;
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 100.0));
parentData.left = 0.0;
parentData.right = 0.0;
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 800.0));
parentData.height = -100.0;
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 800.0, height: 0.0));
parentData.height = 100.0;
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 800.0, height: 100.0));
parentData.top = 0.0;
parentData.bottom = 0.0;
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 800.0, height: 600.0));
parentData.bottom = 1000.0;
expect(parentData.positionedChildConstraints(stackSize), const BoxConstraints.tightFor(width: 800.0, height: 0.0));
});
test('Stack can layout with top, right, bottom, left 0.0', () {
final RenderBox size = RenderConstrainedBox(
additionalConstraints: BoxConstraints.tight(const Size(100.0, 100.0)),