Skip to content

Commit 9f17a43

Browse files
authored
SliverIgnorePointer (flutter#45127)
1 parent 33d3022 commit 9f17a43

File tree

5 files changed

+297
-5
lines changed

5 files changed

+297
-5
lines changed

packages/flutter/lib/src/rendering/proxy_box.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -3016,8 +3016,8 @@ class RenderRepaintBoundary extends RenderProxyBox {
30163016
class RenderIgnorePointer extends RenderProxyBox {
30173017
/// Creates a render object that is invisible to hit testing.
30183018
///
3019-
/// The [ignoring] argument must not be null. If [ignoringSemantics], this
3020-
/// render object will be ignored for semantics if [ignoring] is true.
3019+
/// The [ignoring] argument must not be null. If [ignoringSemantics] is null,
3020+
/// this render object will be ignored for semantics if [ignoring] is true.
30213021
RenderIgnorePointer({
30223022
RenderBox child,
30233023
bool ignoring = true,
@@ -3039,7 +3039,7 @@ class RenderIgnorePointer extends RenderProxyBox {
30393039
if (value == _ignoring)
30403040
return;
30413041
_ignoring = value;
3042-
if (ignoringSemantics == null)
3042+
if (_ignoringSemantics == null || !_ignoringSemantics)
30433043
markNeedsSemanticsUpdate();
30443044
}
30453045

packages/flutter/lib/src/rendering/sliver.dart

+122
Original file line numberDiff line numberDiff line change
@@ -1817,3 +1817,125 @@ class RenderSliverToBoxAdapter extends RenderSliverSingleBoxAdapter {
18171817
setChildParentData(child, constraints, geometry);
18181818
}
18191819
}
1820+
1821+
/// A render object that is invisible during hit testing.
1822+
///
1823+
/// When [ignoring] is true, this render object (and its subtree) is invisible
1824+
/// to hit testing. It still consumes space during layout and paints its sliver
1825+
/// child as usual. It just cannot be the target of located events, because its
1826+
/// render object returns false from [hitTest].
1827+
///
1828+
/// When [ignoringSemantics] is true, the subtree will be invisible to the
1829+
/// semantics layer (and thus e.g. accessibility tools). If [ignoringSemantics]
1830+
/// is null, it uses the value of [ignoring].
1831+
class RenderSliverIgnorePointer extends RenderSliver with RenderObjectWithChildMixin<RenderSliver> {
1832+
/// Creates a render object that is invisible to hit testing.
1833+
///
1834+
/// The [ignoring] argument must not be null. If [ignoringSemantics] is null,
1835+
/// this render object will be ignored for semantics if [ignoring] is true.
1836+
RenderSliverIgnorePointer({
1837+
RenderSliver sliver,
1838+
bool ignoring = true,
1839+
bool ignoringSemantics,
1840+
}) : assert(ignoring != null),
1841+
_ignoring = ignoring,
1842+
_ignoringSemantics = ignoringSemantics {
1843+
child = sliver;
1844+
}
1845+
1846+
/// Whether this render object is ignored during hit testing.
1847+
///
1848+
/// Regardless of whether this render object is ignored during hit testing, it
1849+
/// will still consume space during layout and be visible during painting.
1850+
bool get ignoring => _ignoring;
1851+
bool _ignoring;
1852+
set ignoring(bool value) {
1853+
assert(value != null);
1854+
if (value == _ignoring)
1855+
return;
1856+
_ignoring = value;
1857+
if (_ignoringSemantics == null || !_ignoringSemantics)
1858+
markNeedsSemanticsUpdate();
1859+
}
1860+
1861+
/// Whether the semantics of this render object is ignored when compiling the
1862+
/// semantics tree.
1863+
///
1864+
/// If null, defaults to value of [ignoring].
1865+
///
1866+
/// See [SemanticsNode] for additional information about the semantics tree.
1867+
bool get ignoringSemantics => _ignoringSemantics;
1868+
bool _ignoringSemantics;
1869+
set ignoringSemantics(bool value) {
1870+
if (value == _ignoringSemantics)
1871+
return;
1872+
final bool oldEffectiveValue = _effectiveIgnoringSemantics;
1873+
_ignoringSemantics = value;
1874+
if (oldEffectiveValue != _effectiveIgnoringSemantics)
1875+
markNeedsSemanticsUpdate();
1876+
}
1877+
1878+
bool get _effectiveIgnoringSemantics => ignoringSemantics ?? ignoring;
1879+
1880+
@override
1881+
void setupParentData(RenderObject child) {
1882+
if (child.parentData is! SliverPhysicalParentData)
1883+
child.parentData = SliverPhysicalParentData();
1884+
}
1885+
1886+
@override
1887+
void performLayout() {
1888+
assert(child != null);
1889+
child.layout(constraints, parentUsesSize: true);
1890+
geometry = child.geometry;
1891+
}
1892+
1893+
@override
1894+
bool hitTest(SliverHitTestResult result, {double mainAxisPosition, double crossAxisPosition}) {
1895+
return !ignoring
1896+
&& super.hitTest(
1897+
result,
1898+
mainAxisPosition: mainAxisPosition,
1899+
crossAxisPosition: crossAxisPosition,
1900+
);
1901+
}
1902+
1903+
@override
1904+
bool hitTestChildren(SliverHitTestResult result, {double mainAxisPosition, double crossAxisPosition}) {
1905+
return child != null
1906+
&& child.geometry.hitTestExtent > 0
1907+
&& child.hitTest(
1908+
result,
1909+
mainAxisPosition: mainAxisPosition,
1910+
crossAxisPosition: crossAxisPosition,
1911+
);
1912+
}
1913+
1914+
@override
1915+
void applyPaintTransform(RenderObject child, Matrix4 transform) {
1916+
assert(child != null);
1917+
final SliverPhysicalParentData childParentData = child.parentData;
1918+
childParentData.applyPaintTransform(transform);
1919+
}
1920+
1921+
@override
1922+
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
1923+
if (child != null && !_effectiveIgnoringSemantics)
1924+
visitor(child);
1925+
}
1926+
1927+
@override
1928+
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1929+
super.debugFillProperties(properties);
1930+
properties.add(DiagnosticsProperty<bool>('ignoring', ignoring));
1931+
properties.add(
1932+
DiagnosticsProperty<bool>(
1933+
'ignoringSemantics',
1934+
_effectiveIgnoringSemantics,
1935+
description: ignoringSemantics == null ?
1936+
'implicitly $_effectiveIgnoringSemantics' :
1937+
null,
1938+
),
1939+
);
1940+
}
1941+
}

packages/flutter/lib/src/widgets/basic.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -6026,8 +6026,8 @@ class RepaintBoundary extends SingleChildRenderObjectWidget {
60266026
class IgnorePointer extends SingleChildRenderObjectWidget {
60276027
/// Creates a widget that is invisible to hit testing.
60286028
///
6029-
/// The [ignoring] argument must not be null. If [ignoringSemantics], this
6030-
/// render object will be ignored for semantics if [ignoring] is true.
6029+
/// The [ignoring] argument must not be null. If [ignoringSemantics] is null,
6030+
/// this render object will be ignored for semantics if [ignoring] is true.
60316031
const IgnorePointer({
60326032
Key key,
60336033
this.ignoring = true,

packages/flutter/lib/src/widgets/sliver.dart

+60
Original file line numberDiff line numberDiff line change
@@ -1625,6 +1625,66 @@ class SliverFillRemaining extends SingleChildRenderObjectWidget {
16251625
}
16261626
}
16271627

1628+
/// A sliver widget that is invisible during hit testing.
1629+
///
1630+
/// When [ignoring] is true, this widget (and its subtree) is invisible
1631+
/// to hit testing. It still consumes space during layout and paints its sliver
1632+
/// child as usual. It just cannot be the target of located events, because it
1633+
/// returns false from [RenderSliver.hitTest].
1634+
///
1635+
/// When [ignoringSemantics] is true, the subtree will be invisible to
1636+
/// the semantics layer (and thus e.g. accessibility tools). If
1637+
/// [ignoringSemantics] is null, it uses the value of [ignoring].
1638+
class SliverIgnorePointer extends SingleChildRenderObjectWidget {
1639+
/// Creates a sliver widget that is invisible to hit testing.
1640+
///
1641+
/// The [ignoring] argument must not be null. If [ignoringSemantics] is null,
1642+
/// this render object will be ignored for semantics if [ignoring] is true.
1643+
const SliverIgnorePointer({
1644+
Key key,
1645+
this.ignoring = true,
1646+
this.ignoringSemantics,
1647+
Widget sliver,
1648+
}) : assert(ignoring != null),
1649+
super(key: key, child: sliver);
1650+
1651+
/// Whether this sliver is ignored during hit testing.
1652+
///
1653+
/// Regardless of whether this sliver is ignored during hit testing, it will
1654+
/// still consume space during layout and be visible during painting.
1655+
final bool ignoring;
1656+
1657+
/// Whether the semantics of this sliver is ignored when compiling the
1658+
/// semantics tree.
1659+
///
1660+
/// If null, defaults to value of [ignoring].
1661+
///
1662+
/// See [SemanticsNode] for additional information about the semantics tree.
1663+
final bool ignoringSemantics;
1664+
1665+
@override
1666+
RenderSliverIgnorePointer createRenderObject(BuildContext context) {
1667+
return RenderSliverIgnorePointer(
1668+
ignoring: ignoring,
1669+
ignoringSemantics: ignoringSemantics,
1670+
);
1671+
}
1672+
1673+
@override
1674+
void updateRenderObject(BuildContext context, RenderSliverIgnorePointer renderObject) {
1675+
renderObject
1676+
..ignoring = ignoring
1677+
..ignoringSemantics = ignoringSemantics;
1678+
}
1679+
1680+
@override
1681+
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1682+
super.debugFillProperties(properties);
1683+
properties.add(DiagnosticsProperty<bool>('ignoring', ignoring));
1684+
properties.add(DiagnosticsProperty<bool>('ignoringSemantics', ignoringSemantics, defaultValue: null));
1685+
}
1686+
}
1687+
16281688
/// Mark a child as needing to stay alive even when it's in a lazy list that
16291689
/// would otherwise remove it.
16301690
///

packages/flutter/test/widgets/slivers_test.dart

+110
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
// found in the LICENSE file.
44

55
import 'package:flutter_test/flutter_test.dart';
6+
import 'package:flutter/material.dart';
67
import 'package:flutter/widgets.dart';
78
import 'package:flutter/rendering.dart';
89

10+
import 'semantics_tester.dart';
11+
912
Future<void> test(WidgetTester tester, double offset, { double anchor = 0.0 }) {
1013
return tester.pumpWidget(
1114
Directionality(
@@ -418,6 +421,113 @@ void main() {
418421
// It will be corrected after a auto scroll animation.
419422
expect(controller.offset, 800.0);
420423
});
424+
425+
group('SliverIgnorePointer - ', () {
426+
Widget _boilerPlate(Widget sliver) {
427+
return Localizations(
428+
locale: const Locale('en', 'us'),
429+
delegates: const <LocalizationsDelegate<dynamic>>[
430+
DefaultWidgetsLocalizations.delegate,
431+
DefaultMaterialLocalizations.delegate,
432+
],
433+
child: Directionality(
434+
textDirection: TextDirection.ltr,
435+
child: MediaQuery(
436+
data: const MediaQueryData(),
437+
child: CustomScrollView(slivers: <Widget>[sliver])
438+
)
439+
)
440+
);
441+
}
442+
443+
testWidgets('ignores pointer events', (WidgetTester tester) async {
444+
final SemanticsTester semantics = SemanticsTester(tester);
445+
final List<String> events = <String>[];
446+
await tester.pumpWidget(_boilerPlate(
447+
SliverIgnorePointer(
448+
ignoring: true,
449+
ignoringSemantics: false,
450+
sliver: SliverToBoxAdapter(
451+
child: GestureDetector(
452+
child: const Text('a'),
453+
onTap: () {
454+
events.add('tap');
455+
},
456+
)
457+
)
458+
)
459+
));
460+
expect(semantics.nodesWith(label: 'a'), hasLength(1));
461+
await tester.tap(find.byType(GestureDetector));
462+
expect(events, equals(<String>[]));
463+
});
464+
465+
testWidgets('ignores semantics', (WidgetTester tester) async {
466+
final SemanticsTester semantics = SemanticsTester(tester);
467+
final List<String> events = <String>[];
468+
await tester.pumpWidget(_boilerPlate(
469+
SliverIgnorePointer(
470+
ignoring: false,
471+
ignoringSemantics: true,
472+
sliver: SliverToBoxAdapter(
473+
child: GestureDetector(
474+
child: const Text('a'),
475+
onTap: () {
476+
events.add('tap');
477+
},
478+
)
479+
)
480+
)
481+
));
482+
expect(semantics.nodesWith(label: 'a'), hasLength(0));
483+
await tester.tap(find.byType(GestureDetector));
484+
expect(events, equals(<String>['tap']));
485+
});
486+
487+
testWidgets('ignores pointer events & semantics', (WidgetTester tester) async {
488+
final SemanticsTester semantics = SemanticsTester(tester);
489+
final List<String> events = <String>[];
490+
await tester.pumpWidget(_boilerPlate(
491+
SliverIgnorePointer(
492+
ignoring: true,
493+
ignoringSemantics: true,
494+
sliver: SliverToBoxAdapter(
495+
child: GestureDetector(
496+
child: const Text('a'),
497+
onTap: () {
498+
events.add('tap');
499+
},
500+
)
501+
)
502+
)
503+
));
504+
expect(semantics.nodesWith(label: 'a'), hasLength(0));
505+
await tester.tap(find.byType(GestureDetector));
506+
expect(events, equals(<String>[]));
507+
});
508+
509+
testWidgets('ignores nothing', (WidgetTester tester) async {
510+
final SemanticsTester semantics = SemanticsTester(tester);
511+
final List<String> events = <String>[];
512+
await tester.pumpWidget(_boilerPlate(
513+
SliverIgnorePointer(
514+
ignoring: false,
515+
ignoringSemantics: false,
516+
sliver: SliverToBoxAdapter(
517+
child: GestureDetector(
518+
child: const Text('a'),
519+
onTap: () {
520+
events.add('tap');
521+
},
522+
)
523+
)
524+
)
525+
));
526+
expect(semantics.nodesWith(label: 'a'), hasLength(1));
527+
await tester.tap(find.byType(GestureDetector));
528+
expect(events, equals(<String>['tap']));
529+
});
530+
});
421531
}
422532

423533
bool isRight(Offset a, Offset b) => b.dx > a.dx;

0 commit comments

Comments
 (0)