Unverified Commit fa174b23 authored by chunhtai's avatar chunhtai Committed by GitHub

SingleChildScrollView does not clip semantics child (#114194)

parent 0dea659f
...@@ -326,16 +326,13 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix ...@@ -326,16 +326,13 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
_RenderSingleChildViewport({ _RenderSingleChildViewport({
AxisDirection axisDirection = AxisDirection.down, AxisDirection axisDirection = AxisDirection.down,
required ViewportOffset offset, required ViewportOffset offset,
double cacheExtent = RenderAbstractViewport.defaultCacheExtent,
RenderBox? child, RenderBox? child,
required Clip clipBehavior, required Clip clipBehavior,
}) : assert(axisDirection != null), }) : assert(axisDirection != null),
assert(offset != null), assert(offset != null),
assert(cacheExtent != null),
assert(clipBehavior != null), assert(clipBehavior != null),
_axisDirection = axisDirection, _axisDirection = axisDirection,
_offset = offset, _offset = offset,
_cacheExtent = cacheExtent,
_clipBehavior = clipBehavior { _clipBehavior = clipBehavior {
this.child = child; this.child = child;
} }
...@@ -370,18 +367,6 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix ...@@ -370,18 +367,6 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
markNeedsLayout(); 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} /// {@macro flutter.material.Material.clipBehavior}
/// ///
/// Defaults to [Clip.none], and must not be null. /// Defaults to [Clip.none], and must not be null.
...@@ -700,19 +685,34 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix ...@@ -700,19 +685,34 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
@override @override
Rect describeSemanticsClip(RenderObject child) { Rect describeSemanticsClip(RenderObject child) {
assert(axis != null); assert(axis != null);
switch (axis) { final double remainingOffset = _maxScrollExtent - offset.pixels;
case Axis.vertical: switch (axisDirection) {
case AxisDirection.up:
return Rect.fromLTRB( return Rect.fromLTRB(
semanticBounds.left, semanticBounds.left,
semanticBounds.top - cacheExtent, semanticBounds.top - remainingOffset,
semanticBounds.right, semanticBounds.right,
semanticBounds.bottom + cacheExtent, semanticBounds.bottom + offset.pixels,
); );
case Axis.horizontal: case AxisDirection.right:
return Rect.fromLTRB(
semanticBounds.left - offset.pixels,
semanticBounds.top,
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( return Rect.fromLTRB(
semanticBounds.left - cacheExtent, semanticBounds.left - remainingOffset,
semanticBounds.top, semanticBounds.top,
semanticBounds.right + cacheExtent, semanticBounds.right + offset.pixels,
semanticBounds.bottom, semanticBounds.bottom,
); );
} }
......
...@@ -2995,14 +2995,15 @@ void main() { ...@@ -2995,14 +2995,15 @@ void main() {
const String tab0title = 'This is a very wide tab #0\nTab 1 of 20'; 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 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(actions: <SemanticsAction>[SemanticsAction.scrollLeft]));
expect(semantics, includesNodeWith(label: tab0title)); expect(semantics, includesNodeWith(label: tab0title));
expect(semantics, isNot(includesNodeWith(label: tab10title))); expect(semantics, includesNodeWith(label: tab10title, flags: hiddenFlags));
controller.index = 10; controller.index = 10;
await tester.pumpAndSettle(); 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(actions: <SemanticsAction>[SemanticsAction.scrollLeft, SemanticsAction.scrollRight]));
expect(semantics, includesNodeWith(label: tab10title)); expect(semantics, includesNodeWith(label: tab10title));
...@@ -3016,7 +3017,7 @@ void main() { ...@@ -3016,7 +3017,7 @@ void main() {
expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.scrollLeft])); expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.scrollLeft]));
expect(semantics, includesNodeWith(label: tab0title)); expect(semantics, includesNodeWith(label: tab0title));
expect(semantics, isNot(includesNodeWith(label: tab10title))); expect(semantics, includesNodeWith(label: tab10title, flags: hiddenFlags));
semantics.dispose(); semantics.dispose();
}); });
......
...@@ -624,6 +624,25 @@ void main() { ...@@ -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( expect(semantics, hasSemantics(
TestSemantics.root( TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
...@@ -638,72 +657,7 @@ void main() { ...@@ -638,72 +657,7 @@ void main() {
SemanticsAction.scrollUp, SemanticsAction.scrollUp,
SemanticsAction.scrollDown, SemanticsAction.scrollDown,
], ],
children: <TestSemantics>[ children: children,
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,
),
],
), ),
], ],
), ),
......
...@@ -305,6 +305,19 @@ void main() { ...@@ -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( expect(semantics, hasSemantics(
TestSemantics( TestSemantics(
children: <TestSemantics>[ children: <TestSemantics>[
...@@ -315,33 +328,7 @@ void main() { ...@@ -315,33 +328,7 @@ void main() {
actions: <SemanticsAction>[ actions: <SemanticsAction>[
SemanticsAction.scrollUp, SemanticsAction.scrollUp,
], ],
children: <TestSemantics>[ children: generateSemanticsChildren(endHidden: 3),
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,
),
],
), ),
], ],
), ),
...@@ -362,48 +349,7 @@ void main() { ...@@ -362,48 +349,7 @@ void main() {
SemanticsAction.scrollUp, SemanticsAction.scrollUp,
SemanticsAction.scrollDown, SemanticsAction.scrollDown,
], ],
children: <TestSemantics>[ children: generateSemanticsChildren(startHidden: 14, endHidden: 18),
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,
),
],
), ),
], ],
), ),
...@@ -423,34 +369,7 @@ void main() { ...@@ -423,34 +369,7 @@ void main() {
actions: <SemanticsAction>[ actions: <SemanticsAction>[
SemanticsAction.scrollDown, SemanticsAction.scrollDown,
], ],
children: <TestSemantics>[ children: generateSemanticsChildren(startHidden: 26),
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,
),
],
), ),
], ],
), ),
...@@ -460,6 +379,85 @@ void main() { ...@@ -460,6 +379,85 @@ void main() {
semantics.dispose(); 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 { testWidgets('SingleChildScrollView getOffsetToReveal - down', (WidgetTester tester) async {
List<Widget> children; List<Widget> children;
await tester.pumpWidget( await tester.pumpWidget(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment