Unverified Commit 9f17a43e authored by Kate Lovett's avatar Kate Lovett Committed by GitHub

SliverIgnorePointer (#45127)

parent 33d30224
......@@ -3016,8 +3016,8 @@ class RenderRepaintBoundary extends RenderProxyBox {
class RenderIgnorePointer extends RenderProxyBox {
/// Creates a render object that is invisible to hit testing.
///
/// The [ignoring] argument must not be null. If [ignoringSemantics], this
/// render object will be ignored for semantics if [ignoring] is true.
/// The [ignoring] argument must not be null. If [ignoringSemantics] is null,
/// this render object will be ignored for semantics if [ignoring] is true.
RenderIgnorePointer({
RenderBox child,
bool ignoring = true,
......@@ -3039,7 +3039,7 @@ class RenderIgnorePointer extends RenderProxyBox {
if (value == _ignoring)
return;
_ignoring = value;
if (ignoringSemantics == null)
if (_ignoringSemantics == null || !_ignoringSemantics)
markNeedsSemanticsUpdate();
}
......
......@@ -1817,3 +1817,125 @@ class RenderSliverToBoxAdapter extends RenderSliverSingleBoxAdapter {
setChildParentData(child, constraints, geometry);
}
}
/// A render object that is invisible during hit testing.
///
/// When [ignoring] is true, this render object (and its subtree) is invisible
/// to hit testing. It still consumes space during layout and paints its sliver
/// child as usual. It just cannot be the target of located events, because its
/// render object returns false from [hitTest].
///
/// When [ignoringSemantics] is true, the subtree will be invisible to the
/// semantics layer (and thus e.g. accessibility tools). If [ignoringSemantics]
/// is null, it uses the value of [ignoring].
class RenderSliverIgnorePointer extends RenderSliver with RenderObjectWithChildMixin<RenderSliver> {
/// Creates a render object that is invisible to hit testing.
///
/// The [ignoring] argument must not be null. If [ignoringSemantics] is null,
/// this render object will be ignored for semantics if [ignoring] is true.
RenderSliverIgnorePointer({
RenderSliver sliver,
bool ignoring = true,
bool ignoringSemantics,
}) : assert(ignoring != null),
_ignoring = ignoring,
_ignoringSemantics = ignoringSemantics {
child = sliver;
}
/// Whether this render object is ignored during hit testing.
///
/// Regardless of whether this render object is ignored during hit testing, it
/// will still consume space during layout and be visible during painting.
bool get ignoring => _ignoring;
bool _ignoring;
set ignoring(bool value) {
assert(value != null);
if (value == _ignoring)
return;
_ignoring = value;
if (_ignoringSemantics == null || !_ignoringSemantics)
markNeedsSemanticsUpdate();
}
/// Whether the semantics of this render object is ignored when compiling the
/// semantics tree.
///
/// If null, defaults to value of [ignoring].
///
/// See [SemanticsNode] for additional information about the semantics tree.
bool get ignoringSemantics => _ignoringSemantics;
bool _ignoringSemantics;
set ignoringSemantics(bool value) {
if (value == _ignoringSemantics)
return;
final bool oldEffectiveValue = _effectiveIgnoringSemantics;
_ignoringSemantics = value;
if (oldEffectiveValue != _effectiveIgnoringSemantics)
markNeedsSemanticsUpdate();
}
bool get _effectiveIgnoringSemantics => ignoringSemantics ?? ignoring;
@override
void setupParentData(RenderObject child) {
if (child.parentData is! SliverPhysicalParentData)
child.parentData = SliverPhysicalParentData();
}
@override
void performLayout() {
assert(child != null);
child.layout(constraints, parentUsesSize: true);
geometry = child.geometry;
}
@override
bool hitTest(SliverHitTestResult result, {double mainAxisPosition, double crossAxisPosition}) {
return !ignoring
&& super.hitTest(
result,
mainAxisPosition: mainAxisPosition,
crossAxisPosition: crossAxisPosition,
);
}
@override
bool hitTestChildren(SliverHitTestResult result, {double mainAxisPosition, double crossAxisPosition}) {
return child != null
&& child.geometry.hitTestExtent > 0
&& child.hitTest(
result,
mainAxisPosition: mainAxisPosition,
crossAxisPosition: crossAxisPosition,
);
}
@override
void applyPaintTransform(RenderObject child, Matrix4 transform) {
assert(child != null);
final SliverPhysicalParentData childParentData = child.parentData;
childParentData.applyPaintTransform(transform);
}
@override
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
if (child != null && !_effectiveIgnoringSemantics)
visitor(child);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<bool>('ignoring', ignoring));
properties.add(
DiagnosticsProperty<bool>(
'ignoringSemantics',
_effectiveIgnoringSemantics,
description: ignoringSemantics == null ?
'implicitly $_effectiveIgnoringSemantics' :
null,
),
);
}
}
......@@ -6026,8 +6026,8 @@ class RepaintBoundary extends SingleChildRenderObjectWidget {
class IgnorePointer extends SingleChildRenderObjectWidget {
/// Creates a widget that is invisible to hit testing.
///
/// The [ignoring] argument must not be null. If [ignoringSemantics], this
/// render object will be ignored for semantics if [ignoring] is true.
/// The [ignoring] argument must not be null. If [ignoringSemantics] is null,
/// this render object will be ignored for semantics if [ignoring] is true.
const IgnorePointer({
Key key,
this.ignoring = true,
......
......@@ -1625,6 +1625,66 @@ class SliverFillRemaining extends SingleChildRenderObjectWidget {
}
}
/// A sliver widget that is invisible during hit testing.
///
/// When [ignoring] is true, this widget (and its subtree) is invisible
/// to hit testing. It still consumes space during layout and paints its sliver
/// child as usual. It just cannot be the target of located events, because it
/// returns false from [RenderSliver.hitTest].
///
/// When [ignoringSemantics] is true, the subtree will be invisible to
/// the semantics layer (and thus e.g. accessibility tools). If
/// [ignoringSemantics] is null, it uses the value of [ignoring].
class SliverIgnorePointer extends SingleChildRenderObjectWidget {
/// Creates a sliver widget that is invisible to hit testing.
///
/// The [ignoring] argument must not be null. If [ignoringSemantics] is null,
/// this render object will be ignored for semantics if [ignoring] is true.
const SliverIgnorePointer({
Key key,
this.ignoring = true,
this.ignoringSemantics,
Widget sliver,
}) : assert(ignoring != null),
super(key: key, child: sliver);
/// Whether this sliver is ignored during hit testing.
///
/// Regardless of whether this sliver is ignored during hit testing, it will
/// still consume space during layout and be visible during painting.
final bool ignoring;
/// Whether the semantics of this sliver is ignored when compiling the
/// semantics tree.
///
/// If null, defaults to value of [ignoring].
///
/// See [SemanticsNode] for additional information about the semantics tree.
final bool ignoringSemantics;
@override
RenderSliverIgnorePointer createRenderObject(BuildContext context) {
return RenderSliverIgnorePointer(
ignoring: ignoring,
ignoringSemantics: ignoringSemantics,
);
}
@override
void updateRenderObject(BuildContext context, RenderSliverIgnorePointer renderObject) {
renderObject
..ignoring = ignoring
..ignoringSemantics = ignoringSemantics;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<bool>('ignoring', ignoring));
properties.add(DiagnosticsProperty<bool>('ignoringSemantics', ignoringSemantics, defaultValue: null));
}
}
/// Mark a child as needing to stay alive even when it's in a lazy list that
/// would otherwise remove it.
///
......
......@@ -3,9 +3,12 @@
// found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/rendering.dart';
import 'semantics_tester.dart';
Future<void> test(WidgetTester tester, double offset, { double anchor = 0.0 }) {
return tester.pumpWidget(
Directionality(
......@@ -418,6 +421,113 @@ void main() {
// It will be corrected after a auto scroll animation.
expect(controller.offset, 800.0);
});
group('SliverIgnorePointer - ', () {
Widget _boilerPlate(Widget sliver) {
return Localizations(
locale: const Locale('en', 'us'),
delegates: const <LocalizationsDelegate<dynamic>>[
DefaultWidgetsLocalizations.delegate,
DefaultMaterialLocalizations.delegate,
],
child: Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: CustomScrollView(slivers: <Widget>[sliver])
)
)
);
}
testWidgets('ignores pointer events', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final List<String> events = <String>[];
await tester.pumpWidget(_boilerPlate(
SliverIgnorePointer(
ignoring: true,
ignoringSemantics: false,
sliver: SliverToBoxAdapter(
child: GestureDetector(
child: const Text('a'),
onTap: () {
events.add('tap');
},
)
)
)
));
expect(semantics.nodesWith(label: 'a'), hasLength(1));
await tester.tap(find.byType(GestureDetector));
expect(events, equals(<String>[]));
});
testWidgets('ignores semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final List<String> events = <String>[];
await tester.pumpWidget(_boilerPlate(
SliverIgnorePointer(
ignoring: false,
ignoringSemantics: true,
sliver: SliverToBoxAdapter(
child: GestureDetector(
child: const Text('a'),
onTap: () {
events.add('tap');
},
)
)
)
));
expect(semantics.nodesWith(label: 'a'), hasLength(0));
await tester.tap(find.byType(GestureDetector));
expect(events, equals(<String>['tap']));
});
testWidgets('ignores pointer events & semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final List<String> events = <String>[];
await tester.pumpWidget(_boilerPlate(
SliverIgnorePointer(
ignoring: true,
ignoringSemantics: true,
sliver: SliverToBoxAdapter(
child: GestureDetector(
child: const Text('a'),
onTap: () {
events.add('tap');
},
)
)
)
));
expect(semantics.nodesWith(label: 'a'), hasLength(0));
await tester.tap(find.byType(GestureDetector));
expect(events, equals(<String>[]));
});
testWidgets('ignores nothing', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final List<String> events = <String>[];
await tester.pumpWidget(_boilerPlate(
SliverIgnorePointer(
ignoring: false,
ignoringSemantics: false,
sliver: SliverToBoxAdapter(
child: GestureDetector(
child: const Text('a'),
onTap: () {
events.add('tap');
},
)
)
)
));
expect(semantics.nodesWith(label: 'a'), hasLength(1));
await tester.tap(find.byType(GestureDetector));
expect(events, equals(<String>['tap']));
});
});
}
bool isRight(Offset a, Offset b) => b.dx > a.dx;
......
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