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
_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 - 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(
semanticBounds.left - cacheExtent,
semanticBounds.left - remainingOffset,
semanticBounds.top,
semanticBounds.right + cacheExtent,
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(
......
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