mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
MinimumTapTargetGuideline skips nodes at scrollable boundaries (#124615)
fixes https://github.com/flutter/flutter/issues/107615 ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/wiki/Tree-hygiene#overview [Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests [Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo [Features we expect every widget to implement]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/wiki/Chat
This commit is contained in:
parent
70ca469792
commit
8ac94c16b2
@ -121,6 +121,13 @@ class MinimumTapTargetGuideline extends AccessibilityGuideline {
|
||||
/// A link describing the tap target guidelines for a platform.
|
||||
final String link;
|
||||
|
||||
/// The gap between targets to their parent scrollables to be consider as valid
|
||||
/// tap targets.
|
||||
///
|
||||
/// This avoid cases where a tap target is partially scrolled off-screen that
|
||||
/// result in a smaller tap area.
|
||||
static const double _kMinimumGapToBoundary = 0.001;
|
||||
|
||||
@override
|
||||
FutureOr<Evaluation> evaluate(WidgetTester tester) {
|
||||
Evaluation result = const Evaluation.pass();
|
||||
@ -149,27 +156,30 @@ class MinimumTapTargetGuideline extends AccessibilityGuideline {
|
||||
}
|
||||
Rect paintBounds = node.rect;
|
||||
SemanticsNode? current = node;
|
||||
|
||||
while (current != null) {
|
||||
final Matrix4? transform = current.transform;
|
||||
if (transform != null) {
|
||||
paintBounds = MatrixUtils.transformRect(transform, paintBounds);
|
||||
}
|
||||
// skip node if it is touching the edge scrollable, since it might
|
||||
// be partially scrolled offscreen.
|
||||
if (current.hasFlag(SemanticsFlag.hasImplicitScrolling) &&
|
||||
_isAtBoundary(paintBounds, current.rect)) {
|
||||
return result;
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
// skip node if it is touching the edge of the screen, since it might
|
||||
// be partially scrolled offscreen.
|
||||
const double delta = 0.001;
|
||||
final Size physicalSize = view.physicalSize;
|
||||
if (paintBounds.left <= delta ||
|
||||
paintBounds.top <= delta ||
|
||||
(paintBounds.bottom - physicalSize.height).abs() <= delta ||
|
||||
(paintBounds.right - physicalSize.width).abs() <= delta) {
|
||||
|
||||
final Rect viewRect = Offset.zero & view.physicalSize;
|
||||
if (_isAtBoundary(paintBounds, viewRect)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// shrink by device pixel ratio.
|
||||
final Size candidateSize = paintBounds.size / view.devicePixelRatio;
|
||||
if (candidateSize.width < size.width - delta ||
|
||||
candidateSize.height < size.height - delta) {
|
||||
if (candidateSize.width < size.width - precisionErrorTolerance ||
|
||||
candidateSize.height < size.height - precisionErrorTolerance) {
|
||||
result += Evaluation.fail(
|
||||
'$node: expected tap target size of at least $size, '
|
||||
'but found $candidateSize\n'
|
||||
@ -179,6 +189,16 @@ class MinimumTapTargetGuideline extends AccessibilityGuideline {
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool _isAtBoundary(Rect child, Rect parent) {
|
||||
if (child.left - parent.left > _kMinimumGapToBoundary &&
|
||||
parent.right - child.right > _kMinimumGapToBoundary &&
|
||||
child.top - parent.top > _kMinimumGapToBoundary &&
|
||||
parent.bottom - child.bottom > _kMinimumGapToBoundary) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Returns whether [SemanticsNode] should be skipped for minimum tap target
|
||||
/// guideline.
|
||||
///
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
@ -816,6 +817,36 @@ void main() {
|
||||
await expectLater(tester, meetsGuideline(androidTapTargetGuideline));
|
||||
handle.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Tap size test can handle partially off-screen items', (WidgetTester tester) async {
|
||||
final ScrollController controller = ScrollController();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(title: const Text('Foo')),
|
||||
body: ListView(
|
||||
controller: controller,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10, right: 10),
|
||||
child: SizedBox(
|
||||
width: 100,
|
||||
height: 100,
|
||||
child: Semantics(container: true, onTap: () {}, child: const Text('hello'))),
|
||||
),
|
||||
Container(
|
||||
height: 1000,
|
||||
color: Colors.red,
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
controller.jumpTo(90);
|
||||
await tester.pump();
|
||||
await expectLater(tester, meetsGuideline(iOSTapTargetGuideline));
|
||||
});
|
||||
});
|
||||
|
||||
group('Labeled tappable node guideline', () {
|
||||
|
Loading…
Reference in New Issue
Block a user