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.
|
/// The [PlatformViewController] is unusable after calling dispose.
|
||||||
void dispose();
|
void dispose();
|
||||||
|
|
||||||
|
/// Clears the view's focus on the platform side.
|
||||||
|
void clearFocus();
|
||||||
}
|
}
|
||||||
|
@ -588,7 +588,8 @@ class PlatformViewCreationParams {
|
|||||||
|
|
||||||
const PlatformViewCreationParams._({
|
const PlatformViewCreationParams._({
|
||||||
@required this.id,
|
@required this.id,
|
||||||
@required this.onPlatformViewCreated
|
@required this.onPlatformViewCreated,
|
||||||
|
@required this.onFocusChanged,
|
||||||
}) : assert(id != null),
|
}) : assert(id != null),
|
||||||
assert(onPlatformViewCreated != null);
|
assert(onPlatformViewCreated != null);
|
||||||
|
|
||||||
@ -599,6 +600,11 @@ class PlatformViewCreationParams {
|
|||||||
|
|
||||||
/// Callback invoked after the platform view has been created.
|
/// Callback invoked after the platform view has been created.
|
||||||
final PlatformViewCreatedCallback onPlatformViewCreated;
|
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.
|
/// 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;
|
PlatformViewController _controller;
|
||||||
bool _platformViewCreated = false;
|
bool _platformViewCreated = false;
|
||||||
Widget _surface;
|
Widget _surface;
|
||||||
|
FocusNode _focusNode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -686,27 +693,51 @@ class _PlatformViewLinkState extends State<PlatformViewLink> {
|
|||||||
return const SizedBox.expand();
|
return const SizedBox.expand();
|
||||||
}
|
}
|
||||||
_surface ??= widget._surfaceFactory(context, _controller);
|
_surface ??= widget._surfaceFactory(context, _controller);
|
||||||
return _surface;
|
return Focus(
|
||||||
|
focusNode: _focusNode,
|
||||||
|
onFocusChange: _handleFrameworkFocusChanged,
|
||||||
|
child: _surface,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
_focusNode = FocusNode(debugLabel: 'PlatformView(id: $_id)',);
|
||||||
_initialize();
|
_initialize();
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _initialize() {
|
void _initialize() {
|
||||||
_id = platformViewsRegistry.getNextPlatformViewId();
|
_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) {
|
void _onPlatformViewCreated(int id) {
|
||||||
setState(() => _platformViewCreated = true);
|
setState(() => _platformViewCreated = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleFrameworkFocusChanged(bool isFocused) {
|
||||||
|
if (!isFocused) {
|
||||||
|
_controller?.clearFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handlePlatformFocusChanged(bool isFocused){
|
||||||
|
if (isFocused) {
|
||||||
|
_focusNode.requestFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_controller?.dispose();
|
_controller?.dispose();
|
||||||
|
_controller = null;
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ class FakePlatformViewController extends PlatformViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool disposed = false;
|
bool disposed = false;
|
||||||
|
bool focusCleared = false;
|
||||||
|
|
||||||
/// Events that are dispatched;
|
/// Events that are dispatched;
|
||||||
List<PointerEvent> dispatchedPointerEvents = <PointerEvent>[];
|
List<PointerEvent> dispatchedPointerEvents = <PointerEvent>[];
|
||||||
@ -35,12 +36,18 @@ class FakePlatformViewController extends PlatformViewController {
|
|||||||
void clearTestingVariables() {
|
void clearTestingVariables() {
|
||||||
dispatchedPointerEvents.clear();
|
dispatchedPointerEvents.clear();
|
||||||
disposed = false;
|
disposed = false;
|
||||||
|
focusCleared = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
disposed = true;
|
disposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void clearFocus() {
|
||||||
|
focusCleared = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakeAndroidPlatformViewsController {
|
class FakeAndroidPlatformViewsController {
|
||||||
|
@ -2056,5 +2056,69 @@ void main() {
|
|||||||
});
|
});
|
||||||
expect(container, isNotNull);
|
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