Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/pointer_interceptor/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
## NEXT
## 0.9.1

* Removed `android` and `ios` directories from `example`, as the example doesn't
build for those platforms.
* Added `intercepting` field to allow for conditional pointer interception

## 0.9.0+1

Expand Down
32 changes: 32 additions & 0 deletions packages/pointer_interceptor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,38 @@ There's two ways that the `PointerInterceptor` widget can be used to solve the p
)
```

### `intercepting`

A common use of the `PointerInterceptor` widget is to block clicks only under
certain conditions (`isVideoShowing`, `isPanelOpen`...).

The `intercepting` property allows the `PointerInterceptor` widget to render
itself (or not) depending on a boolean value, instead of having to manually
write an `if/else` on the Flutter App widget tree, so code like this:

```dart
if (someCondition) {
return PointerInterceptor(
child: ElevatedButton(...),
)
} else {
return ElevatedButton(...),
}
```

can be rewritten as:

```dart
return PointerInterceptor(
intercepting: someCondition,
child: ElevatedButton(...),
)
```

Note: when `intercepting` is false, the `PointerInterceptor` will not render
_anything_ in flutter, and just return its `child`. The code is exactly
equivalent to the example above.

### `debug`

The `PointerInterceptor` widget has a `debug` property, that will render it visibly on the screen (similar to the images above).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ void main() {
group('Widget', () {
final Finder nonClickableButtonFinder =
find.byKey(const Key('transparent-button'));
final Finder clickableWrappedButtonFinder =
find.byKey(const Key('wrapped-transparent-button'));
final Finder clickableButtonFinder =
find.byKey(const Key('clickable-button'));

Expand All @@ -41,6 +43,27 @@ void main() {
}
});

testWidgets(
'on wrapped elements with intercepting set to false, the browser hits the background-html-view',
(WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();

final html.Element? element =
_getHtmlElementFromFinder(clickableWrappedButtonFinder, tester);

if (html.document.querySelector('flt-glass-pane')?.shadowRoot != null) {
// In flutter master...
expect(element?.id, 'background-html-view');
} else {
// In previous versions (--web-renderer=html only)...
expect(element?.tagName.toLowerCase(), 'flt-platform-view');
final html.Element? platformViewRoot =
element?.shadowRoot?.getElementById('background-html-view');
expect(platformViewRoot, isNotNull);
}
});

testWidgets(
'on unwrapped elements, the browser hits the background-html-view',
(WidgetTester tester) async {
Expand Down
10 changes: 10 additions & 0 deletions packages/pointer_interceptor/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ class _MyHomePageState extends State<MyHomePage> {
_clickedOn('transparent-button');
},
),
PointerInterceptor(
intercepting: false,
child: ElevatedButton(
key: const Key('wrapped-transparent-button'),
child: const Text('Never calls onPressed'),
onPressed: () {
_clickedOn('wrapped-transparent-button');
},
),
),
PointerInterceptor(
child: ElevatedButton(
key: const Key('clickable-button'),
Expand Down
4 changes: 4 additions & 0 deletions packages/pointer_interceptor/lib/src/mobile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ class PointerInterceptor extends StatelessWidget {
/// Create a `PointerInterceptor` wrapping a `child`.
const PointerInterceptor({
required this.child,
this.intercepting = true,
this.debug = false,
Key? key,
}) : super(key: key);

/// The `Widget` that is being wrapped by this `PointerInterceptor`.
final Widget child;

/// Whether or not this `PointerInterceptor` should intercept pointer events.
final bool intercepting;

/// When true, the widget renders with a semi-transparent red background, for debug purposes.
///
/// This is useful when rendering this as a "layout" widget, like the root child
Expand Down
8 changes: 8 additions & 0 deletions packages/pointer_interceptor/lib/src/web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class PointerInterceptor extends StatelessWidget {
/// Creates a PointerInterceptor for the web.
PointerInterceptor({
required this.child,
this.intercepting = true,
this.debug = false,
Key? key,
}) : super(key: key) {
Expand All @@ -49,6 +50,9 @@ class PointerInterceptor extends StatelessWidget {
/// The `Widget` that is being wrapped by this `PointerInterceptor`.
final Widget child;

/// Whether or not this `PointerInterceptor` should intercept pointer events.
final bool intercepting;

/// When true, the widget renders with a semi-transparent red background, for debug purposes.
///
/// This is useful when rendering this as a "layout" widget, like the root child
Expand All @@ -70,6 +74,10 @@ class PointerInterceptor extends StatelessWidget {

@override
Widget build(BuildContext context) {
if (!intercepting) {
return child;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't at all how the other classes you referenced behave. What's the use case for having a property vs. clients just not using the wrapper if this is the behavior they want?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using floating_material_search_bar which always has a widget filling the whole screen for hit testing. I need the widget to pass through touches to a Google Map when the search bar is not focused, but intercept them when the search bar is focused.

I didn't look at the implementation of Ignore/AbsorbPointer, but how is this different than how they behave?

AbsorbPointer documentation:

When [absorbing] is true, this widget prevents its subtree from receiving pointer events by terminating hit testing at itself.

IgnorePointer documentation:

When [ignoring] is true, this widget (and its subtree) is invisible to hit testing.

PointerInterceptor(
  intercepting: intercepting,
  child: const Text(''),
);

Is a lot simpler than:

Builder(
  builder: (context) {
    if (intercepting) {
      return const PointerInterceptor(
        child: Text(''),
      );
    } else {
      return const Text('');
    }
  },
);

I used mobile.dart as reference for what to do when the flag is false.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't look at the implementation of Ignore/AbsorbPointer, but how is this different than how they behave?

They don't conditionally change what build returns, unless I missed something.

[...]
Is a lot simpler than:
[...]

That's true, but that's a false choice because you're duplicating the code to create the child. If what you want is to conditionally wrap something in a PointerInterceptor you could make a trivial local helper method that does that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On implementation

There's two ways to do this. One is what this PR does or the helper method that @stuartmorgan is suggecting (let's call it "conditional build"). Another one is to do it at the RenderObject level (let's call it "conditional render"). There are pros and cons.

Pros of conditional build:

  • If the value of intercepting doesn't change over time (or changes rarely), then it's more efficient for the false case because it doesn't increase the size of the Element tree.
  • Simple implementation (as this PR shows)

Pros of conditional render:

  • More efficient if the value of intercepting changes frequently. Because pointer interceptor is a platform view it alwaysNeedsCompositing, so it will break up pictures into more layers than when there's no interceptor. If the widget flips between intercepting and not, it will significantly alter the shape of the render tree and especially of the layer tree. At the render object level the tree could be kept more stable between intercepting or not because it could keep the same layer structure.

Conditional rendering would have to be implemented in this package. Conditional build can be implemented as a helper function.

On ergonomics

I really prefer the ergonomics of a flag compared to having to write a helper function. You won't have many interceptors in the same library, but you may have many interceptors across your codebase and the discovery of a custom helper function like this will be low. In most cases we want people to just be able to import this package and get good ergonomics from it out of the box. If enabling/disabling the interceptor is an important use-case, then I'd argue it should also be ergonomic out of the box. Honestly, I wouldn't want to write that if/else even once. If the interceptor is in the middle of a deep widget hierarchy, making it conditional will mess up all of the surrounding code.

}

final String viewType = _getViewType(debug: debug);
return Stack(
alignment: Alignment.center,
Expand Down
2 changes: 1 addition & 1 deletion packages/pointer_interceptor/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: pointer_interceptor
description: A widget to prevent clicks from being swallowed by underlying HtmlElementViews on the web.
repository: https://github.com/flutter/packages/tree/main/packages/pointer_interceptor
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+pointer_interceptor%22
version: 0.9.0+1
version: 0.9.1

environment:
sdk: ">=2.12.0 <3.0.0"
Expand Down