SingleChildScrollView does not clip semantics child (#114194)
This commit is contained in:
parent
0dea659f98
commit
fa174b2343
@ -326,16 +326,13 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
|
||||
_RenderSingleChildViewport({
|
||||
AxisDirection axisDirection = AxisDirection.down,
|
||||
required ViewportOffset offset,
|
||||
double cacheExtent = RenderAbstractViewport.defaultCacheExtent,
|
||||
RenderBox? child,
|
||||
required Clip clipBehavior,
|
||||
}) : assert(axisDirection != null),
|
||||
assert(offset != null),
|
||||
assert(cacheExtent != null),
|
||||
assert(clipBehavior != null),
|
||||
_axisDirection = axisDirection,
|
||||
_offset = offset,
|
||||
_cacheExtent = cacheExtent,
|
||||
_clipBehavior = clipBehavior {
|
||||
this.child = child;
|
||||
}
|
||||
@ -370,18 +367,6 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// {@macro flutter.rendering.RenderViewportBase.cacheExtent}
|
||||
double get cacheExtent => _cacheExtent;
|
||||
double _cacheExtent;
|
||||
set cacheExtent(double value) {
|
||||
assert(value != null);
|
||||
if (value == _cacheExtent) {
|
||||
return;
|
||||
}
|
||||
_cacheExtent = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
/// {@macro flutter.material.Material.clipBehavior}
|
||||
///
|
||||
/// Defaults to [Clip.none], and must not be null.
|
||||
@ -700,19 +685,34 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
|
||||
@override
|
||||
Rect describeSemanticsClip(RenderObject child) {
|
||||
assert(axis != null);
|
||||
switch (axis) {
|
||||
case Axis.vertical:
|
||||
final double remainingOffset = _maxScrollExtent - offset.pixels;
|
||||
switch (axisDirection) {
|
||||
case AxisDirection.up:
|
||||
return Rect.fromLTRB(
|
||||
semanticBounds.left,
|
||||
semanticBounds.top - cacheExtent,
|
||||
semanticBounds.top - remainingOffset,
|
||||
semanticBounds.right,
|
||||
semanticBounds.bottom + cacheExtent,
|
||||
semanticBounds.bottom + offset.pixels,
|
||||
);
|
||||
case Axis.horizontal:
|
||||
case AxisDirection.right:
|
||||
return Rect.fromLTRB(
|
||||
semanticBounds.left - cacheExtent,
|
||||
semanticBounds.left - offset.pixels,
|
||||
semanticBounds.top,
|
||||
semanticBounds.right + cacheExtent,
|
||||
semanticBounds.right + remainingOffset,
|
||||
semanticBounds.bottom,
|
||||
);
|
||||
case AxisDirection.down:
|
||||
return Rect.fromLTRB(
|
||||
semanticBounds.left,
|
||||
semanticBounds.top - offset.pixels,
|
||||
semanticBounds.right,
|
||||
semanticBounds.bottom + remainingOffset,
|
||||
);
|
||||
case AxisDirection.left:
|
||||
return Rect.fromLTRB(
|
||||
semanticBounds.left - remainingOffset,
|
||||
semanticBounds.top,
|
||||
semanticBounds.right + offset.pixels,
|
||||
semanticBounds.bottom,
|
||||
);
|
||||
}
|
||||
|
@ -2995,14 +2995,15 @@ void main() {
|
||||
const String tab0title = 'This is a very wide tab #0\nTab 1 of 20';
|
||||
const String tab10title = 'This is a very wide tab #10\nTab 11 of 20';
|
||||
|
||||
const List<SemanticsFlag> hiddenFlags = <SemanticsFlag>[SemanticsFlag.isHidden, SemanticsFlag.isFocusable];
|
||||
expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.scrollLeft]));
|
||||
expect(semantics, includesNodeWith(label: tab0title));
|
||||
expect(semantics, isNot(includesNodeWith(label: tab10title)));
|
||||
expect(semantics, includesNodeWith(label: tab10title, flags: hiddenFlags));
|
||||
|
||||
controller.index = 10;
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(semantics, isNot(includesNodeWith(label: tab0title)));
|
||||
expect(semantics, includesNodeWith(label: tab0title, flags: hiddenFlags));
|
||||
expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.scrollLeft, SemanticsAction.scrollRight]));
|
||||
expect(semantics, includesNodeWith(label: tab10title));
|
||||
|
||||
@ -3016,7 +3017,7 @@ void main() {
|
||||
|
||||
expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.scrollLeft]));
|
||||
expect(semantics, includesNodeWith(label: tab0title));
|
||||
expect(semantics, isNot(includesNodeWith(label: tab10title)));
|
||||
expect(semantics, includesNodeWith(label: tab10title, flags: hiddenFlags));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
@ -624,6 +624,25 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
final List<TestSemantics> children = <TestSemantics>[];
|
||||
for (int index = 0; index < 30; index += 1) {
|
||||
final bool isHidden = index < 15 || index > 17;
|
||||
children.add(
|
||||
TestSemantics(
|
||||
flags: isHidden ? <SemanticsFlag>[SemanticsFlag.isHidden] : 0,
|
||||
label: 'Item ${index}a',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
);
|
||||
children.add(
|
||||
TestSemantics(
|
||||
flags: isHidden ? <SemanticsFlag>[SemanticsFlag.isHidden] : 0,
|
||||
label: 'item ${index}b',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
@ -638,72 +657,7 @@ void main() {
|
||||
SemanticsAction.scrollUp,
|
||||
SemanticsAction.scrollDown,
|
||||
],
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
|
||||
label: 'Item 13a',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
|
||||
label: 'item 13b',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
|
||||
label: 'Item 14a',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
|
||||
label: 'item 14b',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
label: 'Item 15a',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
label: 'item 15b',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
label: 'Item 16a',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
label: 'item 16b',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
label: 'Item 17a',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
label: 'item 17b',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
|
||||
label: 'Item 18a',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
|
||||
label: 'item 18b',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
|
||||
label: 'Item 19a',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isHidden],
|
||||
label: 'item 19b',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
],
|
||||
children: children,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -305,6 +305,19 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
List<TestSemantics> generateSemanticsChildren({int startHidden = -1, int endHidden = 30}) {
|
||||
final List<TestSemantics> children = <TestSemantics>[];
|
||||
for (int index = 0; index < 30; index += 1) {
|
||||
final bool isHidden = index <= startHidden || index >= endHidden;
|
||||
children.add(TestSemantics(
|
||||
label: 'Tile $index',
|
||||
textDirection: TextDirection.ltr,
|
||||
flags: isHidden ? const <SemanticsFlag>[SemanticsFlag.isHidden] : 0,
|
||||
));
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
TestSemantics(
|
||||
children: <TestSemantics>[
|
||||
@ -315,33 +328,7 @@ void main() {
|
||||
actions: <SemanticsAction>[
|
||||
SemanticsAction.scrollUp,
|
||||
],
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
label: r'Tile 0',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
label: r'Tile 1',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
label: r'Tile 2',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isHidden,
|
||||
],
|
||||
label: r'Tile 3',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isHidden,],
|
||||
label: r'Tile 4',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
],
|
||||
children: generateSemanticsChildren(endHidden: 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -362,48 +349,7 @@ void main() {
|
||||
SemanticsAction.scrollUp,
|
||||
SemanticsAction.scrollDown,
|
||||
],
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isHidden,
|
||||
],
|
||||
label: r'Tile 13',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isHidden,
|
||||
],
|
||||
label: r'Tile 14',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
label: r'Tile 15',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
label: r'Tile 16',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
label: r'Tile 17',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isHidden,
|
||||
],
|
||||
label: r'Tile 18',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isHidden,
|
||||
],
|
||||
label: r'Tile 19',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
],
|
||||
children: generateSemanticsChildren(startHidden: 14, endHidden: 18),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -423,34 +369,7 @@ void main() {
|
||||
actions: <SemanticsAction>[
|
||||
SemanticsAction.scrollDown,
|
||||
],
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isHidden,
|
||||
],
|
||||
label: r'Tile 25',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isHidden,
|
||||
],
|
||||
label: r'Tile 26',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
label: r'Tile 27',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
label: r'Tile 28',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
label: r'Tile 29',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
],
|
||||
children: generateSemanticsChildren(startHidden: 26),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -460,6 +379,85 @@ void main() {
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('SingleChildScrollView semantics clips cover entire child vertical', (WidgetTester tester) async {
|
||||
final ScrollController controller = ScrollController();
|
||||
final UniqueKey scrollView = UniqueKey();
|
||||
final UniqueKey childBox = UniqueKey();
|
||||
const double length = 10000;
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: SingleChildScrollView(
|
||||
key: scrollView,
|
||||
controller: controller,
|
||||
child: SizedBox(key: childBox, height: length),
|
||||
),
|
||||
),
|
||||
);
|
||||
final RenderObject scrollRenderObject = tester.renderObject(find.byKey(scrollView));
|
||||
RenderAbstractViewport? viewport;
|
||||
void findsRenderViewPort(RenderObject child) {
|
||||
if (viewport != null) {
|
||||
return;
|
||||
}
|
||||
if (child is RenderAbstractViewport) {
|
||||
viewport = child;
|
||||
return;
|
||||
}
|
||||
child.visitChildren(findsRenderViewPort);
|
||||
}
|
||||
scrollRenderObject.visitChildren(findsRenderViewPort);
|
||||
expect(viewport, isNotNull);
|
||||
final RenderObject childRenderObject = tester.renderObject(find.byKey(childBox));
|
||||
Rect semanticsClip = viewport!.describeSemanticsClip(childRenderObject)!;
|
||||
expect(semanticsClip.size.height, length);
|
||||
|
||||
controller.jumpTo(2000);
|
||||
await tester.pump();
|
||||
semanticsClip = viewport!.describeSemanticsClip(childRenderObject)!;
|
||||
expect(semanticsClip.size.height, length);
|
||||
});
|
||||
|
||||
testWidgets('SingleChildScrollView semantics clips cover entire child', (WidgetTester tester) async {
|
||||
final ScrollController controller = ScrollController();
|
||||
final UniqueKey scrollView = UniqueKey();
|
||||
final UniqueKey childBox = UniqueKey();
|
||||
const double length = 10000;
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: SingleChildScrollView(
|
||||
key: scrollView,
|
||||
scrollDirection: Axis.horizontal,
|
||||
controller: controller,
|
||||
child: SizedBox(key: childBox, width: length),
|
||||
),
|
||||
),
|
||||
);
|
||||
final RenderObject scrollRenderObject = tester.renderObject(find.byKey(scrollView));
|
||||
RenderAbstractViewport? viewport;
|
||||
void findsRenderViewPort(RenderObject child) {
|
||||
if (viewport != null) {
|
||||
return;
|
||||
}
|
||||
if (child is RenderAbstractViewport) {
|
||||
viewport = child;
|
||||
return;
|
||||
}
|
||||
child.visitChildren(findsRenderViewPort);
|
||||
}
|
||||
scrollRenderObject.visitChildren(findsRenderViewPort);
|
||||
expect(viewport, isNotNull);
|
||||
final RenderObject childRenderObject = tester.renderObject(find.byKey(childBox));
|
||||
Rect semanticsClip = viewport!.describeSemanticsClip(childRenderObject)!;
|
||||
expect(semanticsClip.size.width, length);
|
||||
|
||||
controller.jumpTo(2000);
|
||||
await tester.pump();
|
||||
semanticsClip = viewport!.describeSemanticsClip(childRenderObject)!;
|
||||
expect(semanticsClip.size.width, length);
|
||||
});
|
||||
|
||||
testWidgets('SingleChildScrollView getOffsetToReveal - down', (WidgetTester tester) async {
|
||||
List<Widget> children;
|
||||
await tester.pumpWidget(
|
||||
|
Loading…
x
Reference in New Issue
Block a user