Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
1e0a1a26
Unverified
Commit
1e0a1a26
authored
May 24, 2022
by
Bruno Leroux
Committed by
GitHub
May 24, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add an example and update `GestureDetector` documentation (#102360)
parent
336aa267
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
271 additions
and
0 deletions
+271
-0
gesture_detector.2.dart
.../api/lib/widgets/gesture_detector/gesture_detector.2.dart
+127
-0
gesture_detector.2_test.dart
...est/widgets/gesture_detector/gesture_detector.2_test.dart
+94
-0
gesture_detector.dart
packages/flutter/lib/src/widgets/gesture_detector.dart
+50
-0
No files found.
examples/api/lib/widgets/gesture_detector/gesture_detector.2.dart
0 → 100644
View file @
1e0a1a26
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/gestures.dart'
;
import
'package:flutter/material.dart'
;
void
main
(
)
{
debugPrintGestureArenaDiagnostics
=
true
;
runApp
(
const
NestedGestureDetectorsApp
());
}
enum
_OnTapWinner
{
none
,
yellow
,
green
}
class
NestedGestureDetectorsApp
extends
StatelessWidget
{
const
NestedGestureDetectorsApp
({
super
.
key
});
@override
Widget
build
(
BuildContext
context
)
{
return
MaterialApp
(
home:
Scaffold
(
appBar:
AppBar
(
title:
const
Text
(
'Nested GestureDetectors'
)),
body:
const
NestedGestureDetectorsExample
(),
),
);
}
}
class
NestedGestureDetectorsExample
extends
StatefulWidget
{
const
NestedGestureDetectorsExample
({
super
.
key
});
@override
State
<
NestedGestureDetectorsExample
>
createState
()
=>
_NestedGestureDetectorsExampleState
();
}
class
_NestedGestureDetectorsExampleState
extends
State
<
NestedGestureDetectorsExample
>
{
bool
_isYellowTranslucent
=
false
;
_OnTapWinner
_winner
=
_OnTapWinner
.
none
;
final
Border
highlightBorder
=
Border
.
all
(
color:
Colors
.
red
,
width:
5
);
@override
Widget
build
(
BuildContext
context
)
{
return
Column
(
children:
<
Widget
>[
Expanded
(
child:
GestureDetector
(
onTap:
()
{
debugPrint
(
'Green onTap'
);
setState
(()
{
_winner
=
_OnTapWinner
.
green
;
});
},
onTapDown:
(
_
)
=>
debugPrint
(
'Green onTapDown'
),
onTapCancel:
()
=>
debugPrint
(
'Green onTapCancel'
),
child:
Container
(
alignment:
Alignment
.
center
,
decoration:
BoxDecoration
(
border:
_winner
==
_OnTapWinner
.
green
?
highlightBorder
:
null
,
color:
Colors
.
green
,
),
child:
GestureDetector
(
// Setting behavior to transparent or opaque as no impact on
// parent-child hit testing. A tap on 'Yellow' is also in
// 'Green' bounds. Both enter the gesture arena, 'Yellow' wins
// because it is in front.
behavior:
_isYellowTranslucent
?
HitTestBehavior
.
translucent
:
HitTestBehavior
.
opaque
,
onTap:
()
{
debugPrint
(
'Yellow onTap'
);
setState
(()
{
_winner
=
_OnTapWinner
.
yellow
;
});
},
child:
Container
(
alignment:
Alignment
.
center
,
decoration:
BoxDecoration
(
border:
_winner
==
_OnTapWinner
.
yellow
?
highlightBorder
:
null
,
color:
Colors
.
amber
,
),
width:
200
,
height:
200
,
child:
Text
(
'HitTextBehavior.
${_isYellowTranslucent ? 'translucent' : 'opaque'}
'
,
textAlign:
TextAlign
.
center
,
),
),
),
),
),
),
Padding
(
padding:
const
EdgeInsets
.
all
(
8.0
),
child:
Row
(
children:
<
Widget
>[
ElevatedButton
(
child:
const
Text
(
'Reset'
),
onPressed:
()
{
setState
(()
{
_isYellowTranslucent
=
false
;
_winner
=
_OnTapWinner
.
none
;
});
},
),
const
SizedBox
(
width:
8
),
ElevatedButton
(
child:
Text
(
'Set Yellow behavior to
${_isYellowTranslucent ? 'opaque' : 'translucent'}
'
,
),
onPressed:
()
{
setState
(()
=>
_isYellowTranslucent
=
!
_isYellowTranslucent
);
},
),
],
),
),
],
);
}
@override
void
dispose
()
{
debugPrintGestureArenaDiagnostics
=
false
;
super
.
dispose
();
}
}
examples/api/test/widgets/gesture_detector/gesture_detector.2_test.dart
0 → 100644
View file @
1e0a1a26
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/material.dart'
;
import
'package:flutter_api_samples/widgets/gesture_detector/gesture_detector.2.dart'
as
example
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
void
expectBorders
(
WidgetTester
tester
,
{
required
bool
expectGreenHasBorder
,
required
bool
expectYellowHasBorder
,
})
{
final
Finder
containerFinder
=
find
.
byType
(
Container
);
final
Finder
greenFinder
=
containerFinder
.
first
;
final
Finder
yellowFinder
=
containerFinder
.
last
;
final
Container
greenContainer
=
tester
.
firstWidget
<
Container
>(
greenFinder
);
final
BoxDecoration
?
greenDecoration
=
greenContainer
.
decoration
as
BoxDecoration
?;
expect
(
greenDecoration
?.
border
,
expectGreenHasBorder
?
isNot
(
null
)
:
null
);
final
Container
yellowContainer
=
tester
.
firstWidget
<
Container
>(
yellowFinder
);
final
BoxDecoration
?
yellowDecoration
=
yellowContainer
.
decoration
as
BoxDecoration
?;
expect
(
yellowDecoration
?.
border
,
expectYellowHasBorder
?
isNot
(
null
)
:
null
);
}
void
expectInnerGestureDetectorBehavior
(
WidgetTester
tester
,
HitTestBehavior
behavior
)
{
// Note that there is a third GestureDetector added by Scaffold
final
Finder
innerGestureDetectorFinder
=
find
.
byType
(
GestureDetector
).
at
(
1
);
final
GestureDetector
innerGestureDetector
=
tester
.
firstWidget
<
GestureDetector
>(
innerGestureDetectorFinder
);
expect
(
innerGestureDetector
.
behavior
,
behavior
);
}
testWidgets
(
'Only the green Container shows a red border when tapped'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
example
.
NestedGestureDetectorsApp
(),
);
final
Finder
greenFinder
=
find
.
byType
(
Container
).
first
;
final
Offset
greenTopLeftCorner
=
tester
.
getTopLeft
(
greenFinder
);
await
tester
.
tapAt
(
greenTopLeftCorner
);
await
tester
.
pumpAndSettle
();
expectBorders
(
tester
,
expectGreenHasBorder:
true
,
expectYellowHasBorder:
false
);
// Tap on the button to toggle inner GestureDetector.behavior
final
Finder
toggleBehaviorFinder
=
find
.
byType
(
ElevatedButton
).
last
;
await
tester
.
tap
(
toggleBehaviorFinder
);
await
tester
.
pump
();
expectInnerGestureDetectorBehavior
(
tester
,
HitTestBehavior
.
translucent
);
// Tap again on the green container, expect nothing changed
await
tester
.
tapAt
(
greenTopLeftCorner
);
await
tester
.
pump
();
expectBorders
(
tester
,
expectGreenHasBorder:
true
,
expectYellowHasBorder:
false
);
// Tap on the reset button
final
Finder
resetFinder
=
find
.
byType
(
ElevatedButton
).
first
;
await
tester
.
tap
(
resetFinder
);
await
tester
.
pump
();
expectInnerGestureDetectorBehavior
(
tester
,
HitTestBehavior
.
opaque
);
});
testWidgets
(
'Only the yellow Container shows a red border when tapped'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
example
.
NestedGestureDetectorsApp
(),
);
final
Finder
yellowFinder
=
find
.
byType
(
Container
).
last
;
final
Offset
yellowTopLeftCorner
=
tester
.
getTopLeft
(
yellowFinder
);
await
tester
.
tapAt
(
yellowTopLeftCorner
);
await
tester
.
pump
();
expectBorders
(
tester
,
expectGreenHasBorder:
false
,
expectYellowHasBorder:
true
);
// Tap on the button to toggle inner GestureDetector.behavior
final
Finder
toggleBehaviorFinder
=
find
.
byType
(
ElevatedButton
).
last
;
await
tester
.
tap
(
toggleBehaviorFinder
);
await
tester
.
pump
();
expectInnerGestureDetectorBehavior
(
tester
,
HitTestBehavior
.
translucent
);
// Tap again on the yellow container, expect nothing changed
await
tester
.
tapAt
(
yellowTopLeftCorner
);
await
tester
.
pump
();
expectBorders
(
tester
,
expectGreenHasBorder:
false
,
expectYellowHasBorder:
true
);
// Tap on the reset button
final
Finder
resetFinder
=
find
.
byType
(
ElevatedButton
).
first
;
await
tester
.
tap
(
resetFinder
);
await
tester
.
pump
();
expectInnerGestureDetectorBehavior
(
tester
,
HitTestBehavior
.
opaque
);
});
}
packages/flutter/lib/src/widgets/gesture_detector.dart
View file @
1e0a1a26
...
...
@@ -150,6 +150,56 @@ class GestureRecognizerFactoryWithHandlers<T extends GestureRecognizer> extends
/// ** See code in examples/api/lib/widgets/gesture_detector/gesture_detector.1.dart **
/// {@end-tool}
///
/// ### Troubleshooting
///
/// Why isn't my parent [GestureDetector.onTap] method called?
///
/// Given a parent [GestureDetector] with an onTap callback, and a child
/// GestureDetector that also defines an onTap callback, when the inner
/// GestureDetector is tapped, both GestureDetectors send a [GestureRecognizer]
/// into the gesture arena. This is because the pointer coordinates are within the
/// bounds of both GestureDetectors. The child GestureDetector wins in this
/// scenario because it was the first to enter the arena, resolving as first come,
/// first served. The child onTap is called, and the parent's is not as the gesture has
/// been consumed.
/// For more information on gesture disambiguation see:
/// [Gesture disambiguation](https://docs.flutter.dev/development/ui/advanced/gestures#gesture-disambiguation).
///
/// Setting [GestureDetector.behavior] to [HitTestBehavior.opaque]
/// or [HitTestBehavior.translucent] has no impact on parent-child relationships:
/// both GestureDetectors send a GestureRecognizer into the gesture arena, only one wins.
///
/// Some callbacks (e.g. onTapDown) can fire before a recognizer wins the arena,
/// and others (e.g. onTapCancel) fire even when it loses the arena. Therefore,
/// the parent detector in the example above may call some of its callbacks even
/// though it loses in the arena.
///
/// {@tool dartpad}
/// This example uses a [GestureDetector] that wraps a green [Container] and a second
/// GestureDetector that wraps a yellow Container. The second GestureDetector is
/// a child of the green Container.
/// Both GestureDetectors define an onTap callback. When the callback is called it
/// adds a red border to the corresponding Container.
///
/// When the green Container is tapped, it's parent GestureDetector enters
/// the gesture arena. It wins because there is no competing GestureDetector and
/// the green Container shows a red border.
/// When the yellow Container is tapped, it's parent GestureDetector enters
/// the gesture arena. The GestureDetector that wraps the green Container also
/// enters the gesture arena (the pointer events coordinates are inside both
/// GestureDetectors bounds). The GestureDetector that wraps the yellow Container
/// wins because it was the first detector to enter the arena.
///
/// This example sets [debugPrintGestureArenaDiagnostics] to true.
/// This flag prints useful information about gesture arenas.
///
/// Changing the [GestureDetector.behavior] property to [HitTestBehavior.translucent]
/// or [HitTestBehavior.opaque] has no impact: both GestureDetectors send a [GestureRecognizer]
/// into the gesture arena, only one wins.
///
/// ** See code in examples/api/lib/widgets/gesture_detector/gesture_detector.2.dart **
/// {@end-tool}
///
/// ## Debugging
///
/// To see how large the hit test box of a [GestureDetector] is for debugging
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment