mirror of
https://github.com/flutter/flutter
synced 2024-10-14 04:02:56 +00:00
Exclude SemanticNodes that are fully covered by an overlapping sliver (#11826)
* Exclude SemanticNodes that are fully covered by an overlapping sliver * simplification * formatting * nits * add test for center widget
This commit is contained in:
parent
98c8366bba
commit
bfa788512e
|
@ -285,6 +285,36 @@ abstract class RenderSliverMultiBoxAdaptor extends RenderSliver
|
|||
visitor(child);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
|
||||
switch (constraints.normalizedGrowthDirection) {
|
||||
case GrowthDirection.forward:
|
||||
super.visitChildrenForSemantics((RenderObject child) {
|
||||
// The sliver is overlapped at the leading edge.
|
||||
final Offset bottomLeftInViewport = MatrixUtils.transformPoint(
|
||||
child.getTransformTo(parent), child.semanticBounds.bottomLeft
|
||||
);
|
||||
final double endOverlap = constraints.overlap;
|
||||
if ((constraints.axis == Axis.vertical && bottomLeftInViewport.dy > endOverlap) ||
|
||||
(constraints.axis == Axis.horizontal && bottomLeftInViewport.dx > endOverlap))
|
||||
visitor(child);
|
||||
});
|
||||
break;
|
||||
case GrowthDirection.reverse:
|
||||
super.visitChildrenForSemantics((RenderObject child) {
|
||||
// The sliver is overlapped at the trailing edge.
|
||||
final Offset topRightInViewport = MatrixUtils.transformPoint(
|
||||
child.getTransformTo(parent), child.semanticBounds.topRight
|
||||
);
|
||||
final double startOverlap = constraints.remainingPaintExtent - constraints.overlap;
|
||||
if ((constraints.axis == Axis.vertical && topRightInViewport.dy < startOverlap) ||
|
||||
(constraints.axis == Axis.horizontal && topRightInViewport.dx < startOverlap))
|
||||
visitor(child);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Called during layout to create and add the child with the given index and
|
||||
/// scroll offset.
|
||||
///
|
||||
|
|
|
@ -97,7 +97,8 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
|
|||
@override
|
||||
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
|
||||
for (RenderSliver sliver in childrenInPaintOrder) {
|
||||
if (sliver.geometry.paintExtent != 0)
|
||||
if (sliver.geometry.paintExtent != 0 &&
|
||||
sliver.constraints.overlap < sliver.geometry.paintOrigin + sliver.geometry.paintExtent)
|
||||
visitor(sliver);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:flutter_test/flutter_test.dart';
|
|||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
import 'semantics_tester.dart';
|
||||
|
||||
|
@ -281,4 +282,394 @@ void main() {
|
|||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('SemanticsNodes of a sliver fully covered by another overlapping sliver are excluded', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
final List<Widget> listChildren = new List<Widget>.generate(10, (int i) {
|
||||
return new Container(
|
||||
height: 200.0,
|
||||
child: new Text('Item $i'),
|
||||
);
|
||||
});
|
||||
final ScrollController controller = new ScrollController(initialScrollOffset: 280.0);
|
||||
await tester.pumpWidget(
|
||||
new MediaQuery(
|
||||
data: new MediaQueryData(),
|
||||
child: new CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
new SliverAppBar(
|
||||
pinned: true,
|
||||
expandedHeight: 100.0,
|
||||
),
|
||||
new SliverList(
|
||||
delegate: new SliverChildListDelegate(listChildren),
|
||||
),
|
||||
],
|
||||
controller: controller,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// 'Item 0' is covered by app bar.
|
||||
expect(semantics, isNot(includesNodeWith(label: 'Item 0')));
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 20,
|
||||
rect: TestSemantics.fullScreen,
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 25,
|
||||
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
|
||||
rect: TestSemantics.fullScreen,
|
||||
children: <TestSemantics>[
|
||||
// Item 0 is missing because its covered by the app bar.
|
||||
new TestSemantics(
|
||||
id: 21,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
// Item 1 starts 20.0dp below edge, so there would be room for Item 0.
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 20.0, 0.0)),
|
||||
label: 'Item 1',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 22,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 220.0, 0.0)),
|
||||
label: 'Item 2',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 23,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 420.0, 0.0)),
|
||||
label: 'Item 3',
|
||||
),
|
||||
],
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 24,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 56.0),
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.excludeFromScrolling],
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Slivers fully covered by another overlapping sliver are excluded', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
final ScrollController controller = new ScrollController(initialScrollOffset: 280.0);
|
||||
final List<Widget> slivers = new List<Widget>.generate(10, (int i) {
|
||||
return new SliverToBoxAdapter(
|
||||
child: new Container(
|
||||
height: 200.0,
|
||||
child: new Text('Item $i'),
|
||||
),
|
||||
);
|
||||
});
|
||||
await tester.pumpWidget(
|
||||
new MediaQuery(
|
||||
data: new MediaQueryData(),
|
||||
child: new CustomScrollView(
|
||||
controller: controller,
|
||||
slivers: <Widget>[
|
||||
new SliverAppBar(
|
||||
pinned: true,
|
||||
expandedHeight: 100.0,
|
||||
),
|
||||
]..addAll(slivers),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// 'Item 0' is covered by app bar.
|
||||
expect(semantics, isNot(includesNodeWith(label: 'Item 0')));
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 26,
|
||||
rect: TestSemantics.fullScreen,
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 31,
|
||||
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
|
||||
rect: TestSemantics.fullScreen,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 27,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 420.0, 0.0)),
|
||||
label: 'Item 3',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 28,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 220.0, 0.0)),
|
||||
label: 'Item 2',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 29,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
// Item 1 starts 20.0dp below edge, so there would be room for Item 0.
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 20.0, 0.0)),
|
||||
label: 'Item 1',
|
||||
),
|
||||
// Item 0 is missing because its covered by the app bar.
|
||||
],
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 30,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 56.0),
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.excludeFromScrolling],
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('SemanticsNodes of a sliver fully covered by another overlapping sliver are excluded (reverse)', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
final List<Widget> listChildren = new List<Widget>.generate(10, (int i) {
|
||||
return new Container(
|
||||
height: 200.0,
|
||||
child: new Text('Item $i'),
|
||||
);
|
||||
});
|
||||
final ScrollController controller = new ScrollController(initialScrollOffset: 280.0);
|
||||
await tester.pumpWidget(
|
||||
new MediaQuery(
|
||||
data: new MediaQueryData(),
|
||||
child: new CustomScrollView(
|
||||
reverse: true, // This is the important setting for this test.
|
||||
slivers: <Widget>[
|
||||
new SliverAppBar(
|
||||
pinned: true,
|
||||
expandedHeight: 100.0,
|
||||
),
|
||||
new SliverList(
|
||||
delegate: new SliverChildListDelegate(listChildren),
|
||||
),
|
||||
],
|
||||
controller: controller,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// 'Item 0' is covered by app bar.
|
||||
expect(semantics, isNot(includesNodeWith(label: 'Item 0')));
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 32,
|
||||
rect: TestSemantics.fullScreen,
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 37,
|
||||
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
|
||||
rect: TestSemantics.fullScreen,
|
||||
children: <TestSemantics>[
|
||||
// Item 0 is missing because its covered by the app bar.
|
||||
new TestSemantics(
|
||||
id: 33,
|
||||
// Item 1 ends at 580dp, so there would be 20dp space for Item 0.
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 380.0, 0.0)),
|
||||
label: 'Item 1',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 34,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 180.0, 0.0)),
|
||||
label: 'Item 2',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 35,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, -20.0, 0.0)),
|
||||
label: 'Item 3',
|
||||
),
|
||||
],
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 36,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 56.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)),
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.excludeFromScrolling],
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Slivers fully covered by another overlapping sliver are excluded (reverse)', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
final ScrollController controller = new ScrollController(initialScrollOffset: 280.0);
|
||||
final List<Widget> slivers = new List<Widget>.generate(10, (int i) {
|
||||
return new SliverToBoxAdapter(
|
||||
child: new Container(
|
||||
height: 200.0,
|
||||
child: new Text('Item $i'),
|
||||
),
|
||||
);
|
||||
});
|
||||
await tester.pumpWidget(
|
||||
new MediaQuery(
|
||||
data: new MediaQueryData(),
|
||||
child: new CustomScrollView(
|
||||
reverse: true, // This is the important setting for this test.
|
||||
controller: controller,
|
||||
slivers: <Widget>[
|
||||
new SliverAppBar(
|
||||
pinned: true,
|
||||
expandedHeight: 100.0,
|
||||
),
|
||||
]..addAll(slivers),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// 'Item 0' is covered by app bar.
|
||||
expect(semantics, isNot(includesNodeWith(label: 'Item 0')));
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 38,
|
||||
rect: TestSemantics.fullScreen,
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.useTwoPaneSemantics],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 43,
|
||||
actions: SemanticsAction.scrollUp.index | SemanticsAction.scrollDown.index,
|
||||
rect: TestSemantics.fullScreen,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 39,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, -20.0, 0.0)),
|
||||
label: 'Item 3',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 40,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 180.0, 0.0)),
|
||||
label: 'Item 2',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 41,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 200.0),
|
||||
// Item 1 ends at 580dp, so there would be 20dp space for Item 0.
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 380.0, 0.0)),
|
||||
label: 'Item 1',
|
||||
),
|
||||
// Item 0 is missing because its covered by the app bar.
|
||||
],
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 42,
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 56.0),
|
||||
transform: new Matrix4.translation(new Vector3(0.0, 544.0, 0.0)),
|
||||
tags: <SemanticsTag>[RenderSemanticsGestureHandler.excludeFromScrolling],
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Slivers fully covered by another overlapping sliver are excluded (with center sliver)', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
final ScrollController controller = new ScrollController(initialScrollOffset: 280.0);
|
||||
final GlobalKey forwardAppBarKey = new GlobalKey(debugLabel: 'forward app bar');
|
||||
final List<Widget> forwardChildren = new List<Widget>.generate(10, (int i) {
|
||||
return new Container(
|
||||
height: 200.0,
|
||||
child: new Text('Forward Item $i'),
|
||||
);
|
||||
});
|
||||
final List<Widget> backwardChildren = new List<Widget>.generate(10, (int i) {
|
||||
return new Container(
|
||||
height: 200.0,
|
||||
child: new Text('Backward Item $i'),
|
||||
);
|
||||
});
|
||||
await tester.pumpWidget(
|
||||
new MediaQuery(
|
||||
data: new MediaQueryData(),
|
||||
child: new Scrollable(
|
||||
controller: controller,
|
||||
viewportBuilder: (BuildContext context, ViewportOffset offset) {
|
||||
return new Viewport(
|
||||
offset: offset,
|
||||
center: forwardAppBarKey,
|
||||
slivers: <Widget>[
|
||||
new SliverList(
|
||||
delegate: new SliverChildListDelegate(backwardChildren),
|
||||
),
|
||||
new SliverAppBar(
|
||||
pinned: true,
|
||||
expandedHeight: 100.0,
|
||||
flexibleSpace: const FlexibleSpaceBar(
|
||||
title: const Text('Backward app bar'),
|
||||
),
|
||||
),
|
||||
new SliverAppBar(
|
||||
pinned: true,
|
||||
key: forwardAppBarKey,
|
||||
expandedHeight: 100.0,
|
||||
flexibleSpace: const FlexibleSpaceBar(
|
||||
title: const Text('Forward app bar'),
|
||||
),
|
||||
),
|
||||
new SliverList(
|
||||
delegate: new SliverChildListDelegate(forwardChildren),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// 'Forward Item 0' is covered by app bar.
|
||||
expect(semantics, isNot(includesNodeWith(label: 'Forward Item 0')));
|
||||
expect(semantics, includesNodeWith(label: 'Forward Item 1'));
|
||||
|
||||
controller.jumpTo(-880.0);
|
||||
await tester.pumpAndSettle();
|
||||
expect(semantics, isNot(includesNodeWith(label: 'Backward Item 0')));
|
||||
expect(semantics, includesNodeWith(label: 'Backward Item 1'));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue