Respond to AndroidView focus events. (#33901)
When an AndroidView gains focus we invoke the(newly introduced) 'TextInput.setPlatformViewClient' text_input system channel method which sets the platform view as the text input target. When the AndroidView loses focus we send a clearFocus message to platform views system channel(so the engine can clear the focus from the platform view). This PR is going to land before the engine implementation is rolled to the framework, we swallow MissingPluginException for the newly introduced method channel methods so this is a no-op before the engine is ready(after the engine is rolled with the corresponding change I'll remove the logic to swallow the exceptions). The engine counterpart is in: flutter/engine#9203
This commit is contained in:
parent
fb8df82c06
commit
7d27550f6f
@ -583,6 +583,14 @@ class AndroidViewController {
|
||||
});
|
||||
}
|
||||
|
||||
/// Clears the focus from the Android View if it is focused.
|
||||
Future<void> clearFocus() {
|
||||
if (_state != _AndroidViewState.created) {
|
||||
return null;
|
||||
}
|
||||
return SystemChannels.platform_views.invokeMethod<void>('clearFocus', id);
|
||||
}
|
||||
|
||||
static int _getAndroidDirection(TextDirection direction) {
|
||||
assert(direction != null);
|
||||
switch (direction) {
|
||||
|
@ -306,6 +306,7 @@ class _AndroidViewState extends State<AndroidView> {
|
||||
Widget build(BuildContext context) {
|
||||
return Focus(
|
||||
focusNode: _focusNode,
|
||||
onFocusChange: _onFocusChange,
|
||||
child: _AndroidPlatformView(
|
||||
controller: _controller,
|
||||
hitTestBehavior: widget.hitTestBehavior,
|
||||
@ -384,6 +385,40 @@ class _AndroidViewState extends State<AndroidView> {
|
||||
_controller.addOnPlatformViewCreatedListener(widget.onPlatformViewCreated);
|
||||
}
|
||||
}
|
||||
|
||||
void _onFocusChange(bool isFocused) {
|
||||
if (!_controller.isCreated) {
|
||||
return;
|
||||
}
|
||||
if (!isFocused) {
|
||||
_controller.clearFocus().catchError((dynamic e) {
|
||||
if (e is MissingPluginException) {
|
||||
// We land the framework part of Android platform views keyboard
|
||||
// support before the engine part. There will be a commit range where
|
||||
// clearFocus isn't implemented in the engine. When that happens we
|
||||
// just swallow the error here. Once the engine part is rolled to the
|
||||
// framework I'll remove this.
|
||||
// TODO(amirh): remove this once the engine's clearFocus is rolled.
|
||||
return;
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
SystemChannels.textInput.invokeMethod<void>(
|
||||
'TextInput.setPlatformViewClient',
|
||||
_id,
|
||||
).catchError((dynamic e) {
|
||||
if (e is MissingPluginException) {
|
||||
// We land the framework part of Android platform views keyboard
|
||||
// support before the engine part. There will be a commit range where
|
||||
// setPlatformViewClient isn't implemented in the engine. When that
|
||||
// happens we just swallow the error here. Once the engine part is
|
||||
// rolled to the framework I'll remove this.
|
||||
// TODO(amirh): remove this once the engine's clearFocus is rolled.
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _UiKitViewState extends State<UiKitView> {
|
||||
|
@ -28,6 +28,8 @@ class FakeAndroidPlatformViewsController {
|
||||
|
||||
Completer<void> createCompleter;
|
||||
|
||||
int lastClearedFocusViewId;
|
||||
|
||||
void registerViewType(String viewType) {
|
||||
_registeredViewTypes.add(viewType);
|
||||
}
|
||||
@ -50,6 +52,8 @@ class FakeAndroidPlatformViewsController {
|
||||
return _touch(call);
|
||||
case 'setDirection':
|
||||
return _setDirection(call);
|
||||
case 'clearFocus':
|
||||
return _clearFocus(call);
|
||||
}
|
||||
return Future<dynamic>.sync(() => null);
|
||||
}
|
||||
@ -154,6 +158,19 @@ class FakeAndroidPlatformViewsController {
|
||||
|
||||
return Future<dynamic>.sync(() => null);
|
||||
}
|
||||
|
||||
Future<dynamic> _clearFocus(MethodCall call) {
|
||||
final int id = call.arguments;
|
||||
|
||||
if (!_views.containsKey(id))
|
||||
throw PlatformException(
|
||||
code: 'error',
|
||||
message: 'Trying to clear the focus on a platform view with unknown id: $id',
|
||||
);
|
||||
|
||||
lastClearedFocusViewId = id;
|
||||
return Future<dynamic>.sync(() => null);
|
||||
}
|
||||
}
|
||||
|
||||
class FakeIosPlatformViewsController {
|
||||
|
@ -908,6 +908,100 @@ void main() {
|
||||
expect(containerFocusNode.hasFocus, isFalse);
|
||||
expect(androidViewFocusNode.hasFocus, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('AndroidView sets a platform view text input client when focused', (WidgetTester tester) async {
|
||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||
viewsController.registerViewType('webview');
|
||||
|
||||
viewsController.createCompleter = Completer<void>();
|
||||
|
||||
final GlobalKey containerKey = GlobalKey();
|
||||
await tester.pumpWidget(
|
||||
Center(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
const SizedBox(
|
||||
width: 200.0,
|
||||
height: 100.0,
|
||||
child: AndroidView(viewType: 'webview', layoutDirection: TextDirection.ltr),
|
||||
),
|
||||
Focus(
|
||||
debugLabel: 'container',
|
||||
child: Container(key: containerKey),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
viewsController.createCompleter.complete();
|
||||
|
||||
|
||||
final Element containerElement = tester.element(find.byKey(containerKey));
|
||||
final FocusNode containerFocusNode = Focus.of(containerElement);
|
||||
|
||||
containerFocusNode.requestFocus();
|
||||
await tester.pump();
|
||||
|
||||
int lastPlatformViewTextClient;
|
||||
SystemChannels.textInput.setMockMethodCallHandler((MethodCall call) {
|
||||
if (call.method == 'TextInput.setPlatformViewClient') {
|
||||
lastPlatformViewTextClient = call.arguments;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
viewsController.invokeViewFocused(currentViewId + 1);
|
||||
await tester.pump();
|
||||
|
||||
expect(lastPlatformViewTextClient, currentViewId + 1);
|
||||
});
|
||||
|
||||
testWidgets('AndroidView clears platform focus when unfocused', (WidgetTester tester) async {
|
||||
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
|
||||
final FakeAndroidPlatformViewsController viewsController = FakeAndroidPlatformViewsController();
|
||||
viewsController.registerViewType('webview');
|
||||
|
||||
viewsController.createCompleter = Completer<void>();
|
||||
|
||||
final GlobalKey containerKey = GlobalKey();
|
||||
await tester.pumpWidget(
|
||||
Center(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
const SizedBox(
|
||||
width: 200.0,
|
||||
height: 100.0,
|
||||
child: AndroidView(viewType: 'webview', layoutDirection: TextDirection.ltr),
|
||||
),
|
||||
Focus(
|
||||
debugLabel: 'container',
|
||||
child: Container(key: containerKey),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
viewsController.createCompleter.complete();
|
||||
|
||||
final Element containerElement = tester.element(find.byKey(containerKey));
|
||||
final FocusNode containerFocusNode = Focus.of(containerElement);
|
||||
|
||||
containerFocusNode.requestFocus();
|
||||
await tester.pump();
|
||||
|
||||
viewsController.invokeViewFocused(currentViewId + 1);
|
||||
await tester.pump();
|
||||
|
||||
viewsController.lastClearedFocusViewId = null;
|
||||
|
||||
containerFocusNode.requestFocus();
|
||||
await tester.pump();
|
||||
|
||||
expect(viewsController.lastClearedFocusViewId, currentViewId + 1);
|
||||
});
|
||||
});
|
||||
|
||||
group('UiKitView', () {
|
||||
|
Loading…
x
Reference in New Issue
Block a user