Unverified Commit 7f5540fa authored by Chris Yang's avatar Chris Yang Committed by GitHub

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]
parent 27a08065
......@@ -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);
});
});
}
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