PlatformViewLink handles focus (#38643)
In the build of PlatformViewLink, Added a FocusNode wrapping around the surface widget. The focus node will ask platform view to clear its focus when necessary through [PlatformViewController.clearFocus]. The platform view can notify the framework it wants to gain focus by calling [PlatformViewCreationParams.onFocusChanged]
This commit is contained in:
parent
27a080659e
commit
7f5540faac
@ -734,4 +734,7 @@ abstract class PlatformViewController {
|
||||
///
|
||||
/// The [PlatformViewController] is unusable after calling dispose.
|
||||
void dispose();
|
||||
|
||||
/// Clears the view's focus on the platform side.
|
||||
void clearFocus();
|
||||
}
|
||||
|
@ -588,7 +588,8 @@ class PlatformViewCreationParams {
|
||||
|
||||
const PlatformViewCreationParams._({
|
||||
@required this.id,
|
||||
@required this.onPlatformViewCreated
|
||||
@required this.onPlatformViewCreated,
|
||||
@required this.onFocusChanged,
|
||||
}) : assert(id != null),
|
||||
assert(onPlatformViewCreated != null);
|
||||
|
||||
@ -599,6 +600,11 @@ class PlatformViewCreationParams {
|
||||
|
||||
/// Callback invoked after the platform view has been created.
|
||||
final PlatformViewCreatedCallback onPlatformViewCreated;
|
||||
|
||||
/// Callback invoked when the platform view's focus is changed on the platform side.
|
||||
///
|
||||
/// The value is true when the platform view gains focus and false when it loses focus.
|
||||
final ValueChanged<bool> onFocusChanged;
|
||||
}
|
||||
|
||||
/// A factory for a surface presenting a platform view as part of the widget hierarchy.
|
||||
@ -679,6 +685,7 @@ class _PlatformViewLinkState extends State<PlatformViewLink> {
|
||||
PlatformViewController _controller;
|
||||
bool _platformViewCreated = false;
|
||||
Widget _surface;
|
||||
FocusNode _focusNode;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -686,27 +693,51 @@ class _PlatformViewLinkState extends State<PlatformViewLink> {
|
||||
return const SizedBox.expand();
|
||||
}
|
||||
_surface ??= widget._surfaceFactory(context, _controller);
|
||||
return _surface;
|
||||
return Focus(
|
||||
focusNode: _focusNode,
|
||||
onFocusChange: _handleFrameworkFocusChanged,
|
||||
child: _surface,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_focusNode = FocusNode(debugLabel: 'PlatformView(id: $_id)',);
|
||||
_initialize();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void _initialize() {
|
||||
_id = platformViewsRegistry.getNextPlatformViewId();
|
||||
_controller = widget._onCreatePlatformView(PlatformViewCreationParams._(id:_id, onPlatformViewCreated:_onPlatformViewCreated));
|
||||
_controller = widget._onCreatePlatformView(
|
||||
PlatformViewCreationParams._(
|
||||
id:_id,
|
||||
onPlatformViewCreated:_onPlatformViewCreated,
|
||||
onFocusChanged:_handlePlatformFocusChanged
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onPlatformViewCreated(int id) {
|
||||
setState(() => _platformViewCreated = true);
|
||||
}
|
||||
|
||||
void _handleFrameworkFocusChanged(bool isFocused) {
|
||||
if (!isFocused) {
|
||||
_controller?.clearFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void _handlePlatformFocusChanged(bool isFocused){
|
||||
if (isFocused) {
|
||||
_focusNode.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller?.dispose();
|
||||
_controller = null;
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ class FakePlatformViewController extends PlatformViewController {
|
||||
}
|
||||
|
||||
bool disposed = false;
|
||||
bool focusCleared = false;
|
||||
|
||||
/// Events that are dispatched;
|
||||
List<PointerEvent> dispatchedPointerEvents = <PointerEvent>[];
|
||||
@ -35,12 +36,18 @@ class FakePlatformViewController extends PlatformViewController {
|
||||
void clearTestingVariables() {
|
||||
dispatchedPointerEvents.clear();
|
||||
disposed = false;
|
||||
focusCleared = false;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
@override
|
||||
void clearFocus() {
|
||||
focusCleared = true;
|
||||
}
|
||||
}
|
||||
|
||||
class FakeAndroidPlatformViewsController {
|
||||
|
@ -2056,5 +2056,69 @@ void main() {
|
||||
});
|
||||
expect(container, isNotNull);
|
||||
});
|
||||
|
||||
testWidgets('PlatformViewLink manages the focus properly', (WidgetTester tester) async {
|
||||
final GlobalKey containerKey = GlobalKey();
|
||||
FakePlatformViewController controller;
|
||||
ValueChanged<bool> focusChanged;
|
||||
final PlatformViewLink platformViewLink = PlatformViewLink(
|
||||
onCreatePlatformView: (PlatformViewCreationParams params){
|
||||
params.onPlatformViewCreated(params.id);
|
||||
focusChanged = params.onFocusChanged;
|
||||
controller = FakePlatformViewController(params.id);
|
||||
return controller;
|
||||
},
|
||||
surfaceFactory: (BuildContext context, PlatformViewController controller) {
|
||||
return PlatformViewSurface(
|
||||
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
|
||||
controller: controller,
|
||||
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
|
||||
);
|
||||
});
|
||||
await tester.pumpWidget(
|
||||
Center(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
SizedBox(child: platformViewLink, width: 300, height: 300,),
|
||||
Focus(
|
||||
debugLabel: 'container',
|
||||
child: Container(key: containerKey),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
final Focus platformViewFocusWidget =
|
||||
tester.widget(
|
||||
find.descendant(
|
||||
of: find.byType(PlatformViewLink),
|
||||
matching: find.byType(Focus)
|
||||
)
|
||||
);
|
||||
final FocusNode platformViewFocusNode = platformViewFocusWidget.focusNode;
|
||||
final Element containerElement = tester.element(find.byKey(containerKey));
|
||||
final FocusNode containerFocusNode = Focus.of(containerElement);
|
||||
|
||||
containerFocusNode.requestFocus();
|
||||
await tester.pump();
|
||||
|
||||
expect(containerFocusNode.hasFocus, true);
|
||||
expect(platformViewFocusNode.hasFocus, false);
|
||||
|
||||
// ask the platform view to gain focus
|
||||
focusChanged(true);
|
||||
await tester.pump();
|
||||
|
||||
expect(containerFocusNode.hasFocus, false);
|
||||
expect(platformViewFocusNode.hasFocus, true);
|
||||
expect(controller.focusCleared, false);
|
||||
// ask the container to gain focus, and the platform view should clear focus.
|
||||
containerFocusNode.requestFocus();
|
||||
await tester.pump();
|
||||
|
||||
expect(containerFocusNode.hasFocus, true);
|
||||
expect(platformViewFocusNode.hasFocus, false);
|
||||
expect(controller.focusCleared, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user