如何让软键盘再次出现
How to make the SoftKeyboard show up again
当您通过 Android Back Button
隐藏 SoftKeyboard
时,触摸(仍然聚焦的)inputNode 不会再次显示键盘。
为了解决这个问题,我使用了以下 class:
public class RefocusableTextField extends TextField {
private Region fakeFocusTarget;
public RefocusableTextField(String text) {
this();
setText(text);
}
public RefocusableTextField() {
fakeFocusTarget = new Region();
fakeFocusTarget.setManaged(false);
getChildren().add(fakeFocusTarget);
addEventFilter(MouseEvent.MOUSE_PRESSED, MouseEvent::consume);
addEventHandler(MouseEvent.MOUSE_CLICKED, e ->
{
if (!isFocused()) {
requestFocus();
} else {
fakeFocusTarget.requestFocus();
requestFocus();
HitInfo hitInfo = ((TextFieldSkin) getSkin()).getIndex(e.getX(), e.getY());
((TextFieldSkin) getSkin()).positionCaret(hitInfo, false);
}
});
}
}
虽然这可行,但它似乎是一个丑陋的解决方法。如果不使用 JDK 内部 classes (TextFieldSkin
, HitInfo
) 怎么能做到这一点?
编辑: 这是另一个解决方案,基于 José Pereda 的回答:
public class RefocusableTextField extends TextField {
private Optional<KeyboardService> service;
public RefocusableTextField(String text) {
this();
setText(text);
}
public RefocusableTextField() {
service = Services.get(KeyboardService.class);
addEventFilter(MouseEvent.MOUSE_PRESSED, event ->
{
if (!isFocused()) {
event.consume();
}
});
addEventHandler(MouseEvent.MOUSE_CLICKED, e ->
{
if (!isFocused()) {
requestFocus();
end();
} else {
service.ifPresent(KeyboardService::show);
}
});
}
}
public class AndroidKeyboardService implements KeyboardService {
private static final float SCALE = FXActivity.getInstance().getResources().getDisplayMetrics().density;
private final InputMethodManager imm;
private Rect currentBounds;
private DoubleProperty visibleHeight;
private OnGlobalLayoutListener layoutListener;
private boolean keyboardVisible;
public AndroidKeyboardService() {
imm = (InputMethodManager) FXActivity.getInstance().getSystemService(FXActivity.INPUT_METHOD_SERVICE);
initLayoutListener();
}
private void initLayoutListener() {
double screenHeight = MobileApplication.getInstance().getScreenHeight();
currentBounds = new Rect();
visibleHeight = new SimpleDoubleProperty(screenHeight);
visibleHeight.addListener((ov, n, n1) -> onHeightChanged(n, n1));
layoutListener = layoutListener(visibleHeight);
FXActivity.getViewGroup().getViewTreeObserver().addOnGlobalLayoutListener(layoutListener);
Services.get(LifecycleService.class).ifPresent(l ->
{
l.addListener(LifecycleEvent.RESUME, () -> FXActivity.getViewGroup().getViewTreeObserver().addOnGlobalLayoutListener(layoutListener));
l.addListener(LifecycleEvent.PAUSE, () -> FXActivity.getViewGroup().getViewTreeObserver().removeOnGlobalLayoutListener(layoutListener));
});
}
private OnGlobalLayoutListener layoutListener(DoubleProperty height) {
return () -> height.set(getCurrentHeigt());
}
private float getCurrentHeigt() {
FXActivity.getViewGroup().getRootView().getWindowVisibleDisplayFrame(currentBounds);
return currentBounds.height() / SCALE;
}
private void onHeightChanged(Number oldHeight, Number newHeight) {
double heightDelta = newHeight.doubleValue() - oldHeight.doubleValue();
keyboardVisible = heightDelta < 0;
}
@Override
public boolean isKeyboardVisible() {
return keyboardVisible;
}
@Override
public void show() {
if (!keyboardVisible) {
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
}
}
@Override
public void hide() {
if (keyboardVisible) {
imm.toggleSoftInput(0, InputMethodManager.HIDE_IMPLICIT_ONLY);
}
}
}
如您所知,Android 的 JavaFX 层管理软键盘,它实际上仅由焦点 gained/lost event.
触发
所以你的做法是正确的,但是如果你想避免私有 API,我看到两种可能的解决方案:
进入JavaFX层,修改并构建... 可以做到,但是工作量大,下一次发布JavaFXPorts版本时会中断。
创建自定义插件并提供API方便管理软键盘。
对于第二个选项,使用新的 Down 插件很容易做到 API。在你的主包下创建包 com.gluonhq.charm.down.plugins
这两个 classes:
键盘服务
package com.gluonhq.charm.down.plugins;
public interface KeyboardService {
public void show();
public void hide();
}
KeyboardServiceFactory
package com.gluonhq.charm.down.plugins;
import com.gluonhq.charm.down.DefaultServiceFactory;
public class KeyboardServiceFactory extends DefaultServiceFactory<KeyboardService> {
public KeyboardServiceFactory() {
super(KeyboardService.class);
}
}
现在在Android包下,在com.gluonhq.charm.down.plugins.android
包下添加这个class:
Android键盘服务
package com.gluonhq.charm.down.plugins.android;
import android.view.inputmethod.InputMethodManager;
import com.gluonhq.charm.down.plugins.KeyboardService;
import javafxports.android.FXActivity;
public class AndroidKeyboardService implements KeyboardService {
private final InputMethodManager imm;
private boolean visible = false;
public AndroidKeyboardService() {
imm = (InputMethodManager) FXActivity.getInstance().getSystemService(FXActivity.INPUT_METHOD_SERVICE);
final ViewTreeObserver.OnGlobalLayoutListener listener = () -> {
Rect rect = new Rect();
FXActivity.getViewGroup().getWindowVisibleDisplayFrame(rect);
int heightDiff = FXActivity.getViewGroup().getRootView().getHeight() - rect.height();
visible = (heightDiff > FXActivity.getViewGroup().getRootView().getHeight() / 4);
};
Services.get(LifecycleService.class).ifPresent(l -> {
l.addListener(LifecycleEvent.RESUME, () ->
FXActivity.getViewGroup().getViewTreeObserver().addOnGlobalLayoutListener(listener));
l.addListener(LifecycleEvent.PAUSE, () ->
FXActivity.getViewGroup().getViewTreeObserver().removeOnGlobalLayoutListener(listener));
});
FXActivity.getViewGroup().getViewTreeObserver().addOnGlobalLayoutListener(listener))
}
@Override
public void show() {
if (!visible) {
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
}
}
@Override
public void hide() {
if (visible) {
imm.toggleSoftInput(0, InputMethodManager.HIDE_IMPLICIT_ONLY);
}
}
}
现在,通过您的代码,您可以轻松地从文本字段调用键盘。
我添加了 long-press
类型的事件,基于此 implementation:
private void addPressAndHoldHandler(Node node, Duration holdTime, EventHandler<MouseEvent> handler) {
class Wrapper<T> {
T content;
}
Wrapper<MouseEvent> eventWrapper = new Wrapper<>();
PauseTransition holdTimer = new PauseTransition(holdTime);
holdTimer.setOnFinished(event -> handler.handle(eventWrapper.content));
node.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
eventWrapper.content = event;
holdTimer.playFromStart();
});
node.addEventHandler(MouseEvent.MOUSE_RELEASED, event -> holdTimer.stop());
node.addEventHandler(MouseEvent.DRAG_DETECTED, event -> holdTimer.stop());
}
因此,如果您有一个 TextField 并且想要在用户按住它一段时间时调用键盘,您只需要:
TextField textField = new TextField();
addPressAndHoldHandler(textField, Duration.seconds(1), event ->
Services.get(KeyboardService.class)
.ifPresent(KeyboardService::show));
编辑
注意:我添加了键盘的可见性控制,基于此 answer.
额外提示:按照这种方法,您只需一步就可以在长按时提供触觉反馈...
我们通过添加 KeyEvents 侦听器改进了您的初始版本,并在 Android 和 iOS 上成功测试了它。
defocus-method 可以很容易地在视图开始时调用,这样它就不会自动聚焦第一个 "Platform.runlater(() -> textField.defocus());" 的文本字段。
public class RefocusableTextField extends TextField {
private Region fakeFocusTarget;
public RefocusableTextField(String text) {
this();
setText(text);
}
public RefocusableTextField() {
fakeFocusTarget = new Region();
fakeFocusTarget.setManaged(false);
getChildren().add(fakeFocusTarget);
addEventFilter(MouseEvent.MOUSE_PRESSED, MouseEvent::consume);
addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
if (!isFocused()) {
requestFocus();
} else {
defocus();
}
});
addEventHandler(KeyEvent.KEY_PRESSED, e -> {
System.out.println(e.getCode());
if (e.getCode().equals(KeyCode.ENTER)) {
defocus();
}
});
}
public void defocus() {
fakeFocusTarget.requestFocus();
}
}
当您通过 Android Back Button
隐藏 SoftKeyboard
时,触摸(仍然聚焦的)inputNode 不会再次显示键盘。
为了解决这个问题,我使用了以下 class:
public class RefocusableTextField extends TextField {
private Region fakeFocusTarget;
public RefocusableTextField(String text) {
this();
setText(text);
}
public RefocusableTextField() {
fakeFocusTarget = new Region();
fakeFocusTarget.setManaged(false);
getChildren().add(fakeFocusTarget);
addEventFilter(MouseEvent.MOUSE_PRESSED, MouseEvent::consume);
addEventHandler(MouseEvent.MOUSE_CLICKED, e ->
{
if (!isFocused()) {
requestFocus();
} else {
fakeFocusTarget.requestFocus();
requestFocus();
HitInfo hitInfo = ((TextFieldSkin) getSkin()).getIndex(e.getX(), e.getY());
((TextFieldSkin) getSkin()).positionCaret(hitInfo, false);
}
});
}
}
虽然这可行,但它似乎是一个丑陋的解决方法。如果不使用 JDK 内部 classes (TextFieldSkin
, HitInfo
) 怎么能做到这一点?
编辑: 这是另一个解决方案,基于 José Pereda 的回答:
public class RefocusableTextField extends TextField {
private Optional<KeyboardService> service;
public RefocusableTextField(String text) {
this();
setText(text);
}
public RefocusableTextField() {
service = Services.get(KeyboardService.class);
addEventFilter(MouseEvent.MOUSE_PRESSED, event ->
{
if (!isFocused()) {
event.consume();
}
});
addEventHandler(MouseEvent.MOUSE_CLICKED, e ->
{
if (!isFocused()) {
requestFocus();
end();
} else {
service.ifPresent(KeyboardService::show);
}
});
}
}
public class AndroidKeyboardService implements KeyboardService {
private static final float SCALE = FXActivity.getInstance().getResources().getDisplayMetrics().density;
private final InputMethodManager imm;
private Rect currentBounds;
private DoubleProperty visibleHeight;
private OnGlobalLayoutListener layoutListener;
private boolean keyboardVisible;
public AndroidKeyboardService() {
imm = (InputMethodManager) FXActivity.getInstance().getSystemService(FXActivity.INPUT_METHOD_SERVICE);
initLayoutListener();
}
private void initLayoutListener() {
double screenHeight = MobileApplication.getInstance().getScreenHeight();
currentBounds = new Rect();
visibleHeight = new SimpleDoubleProperty(screenHeight);
visibleHeight.addListener((ov, n, n1) -> onHeightChanged(n, n1));
layoutListener = layoutListener(visibleHeight);
FXActivity.getViewGroup().getViewTreeObserver().addOnGlobalLayoutListener(layoutListener);
Services.get(LifecycleService.class).ifPresent(l ->
{
l.addListener(LifecycleEvent.RESUME, () -> FXActivity.getViewGroup().getViewTreeObserver().addOnGlobalLayoutListener(layoutListener));
l.addListener(LifecycleEvent.PAUSE, () -> FXActivity.getViewGroup().getViewTreeObserver().removeOnGlobalLayoutListener(layoutListener));
});
}
private OnGlobalLayoutListener layoutListener(DoubleProperty height) {
return () -> height.set(getCurrentHeigt());
}
private float getCurrentHeigt() {
FXActivity.getViewGroup().getRootView().getWindowVisibleDisplayFrame(currentBounds);
return currentBounds.height() / SCALE;
}
private void onHeightChanged(Number oldHeight, Number newHeight) {
double heightDelta = newHeight.doubleValue() - oldHeight.doubleValue();
keyboardVisible = heightDelta < 0;
}
@Override
public boolean isKeyboardVisible() {
return keyboardVisible;
}
@Override
public void show() {
if (!keyboardVisible) {
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
}
}
@Override
public void hide() {
if (keyboardVisible) {
imm.toggleSoftInput(0, InputMethodManager.HIDE_IMPLICIT_ONLY);
}
}
}
如您所知,Android 的 JavaFX 层管理软键盘,它实际上仅由焦点 gained/lost event.
触发所以你的做法是正确的,但是如果你想避免私有 API,我看到两种可能的解决方案:
进入JavaFX层,修改并构建... 可以做到,但是工作量大,下一次发布JavaFXPorts版本时会中断。
创建自定义插件并提供API方便管理软键盘。
对于第二个选项,使用新的 Down 插件很容易做到 API。在你的主包下创建包 com.gluonhq.charm.down.plugins
这两个 classes:
键盘服务
package com.gluonhq.charm.down.plugins;
public interface KeyboardService {
public void show();
public void hide();
}
KeyboardServiceFactory
package com.gluonhq.charm.down.plugins;
import com.gluonhq.charm.down.DefaultServiceFactory;
public class KeyboardServiceFactory extends DefaultServiceFactory<KeyboardService> {
public KeyboardServiceFactory() {
super(KeyboardService.class);
}
}
现在在Android包下,在com.gluonhq.charm.down.plugins.android
包下添加这个class:
Android键盘服务
package com.gluonhq.charm.down.plugins.android;
import android.view.inputmethod.InputMethodManager;
import com.gluonhq.charm.down.plugins.KeyboardService;
import javafxports.android.FXActivity;
public class AndroidKeyboardService implements KeyboardService {
private final InputMethodManager imm;
private boolean visible = false;
public AndroidKeyboardService() {
imm = (InputMethodManager) FXActivity.getInstance().getSystemService(FXActivity.INPUT_METHOD_SERVICE);
final ViewTreeObserver.OnGlobalLayoutListener listener = () -> {
Rect rect = new Rect();
FXActivity.getViewGroup().getWindowVisibleDisplayFrame(rect);
int heightDiff = FXActivity.getViewGroup().getRootView().getHeight() - rect.height();
visible = (heightDiff > FXActivity.getViewGroup().getRootView().getHeight() / 4);
};
Services.get(LifecycleService.class).ifPresent(l -> {
l.addListener(LifecycleEvent.RESUME, () ->
FXActivity.getViewGroup().getViewTreeObserver().addOnGlobalLayoutListener(listener));
l.addListener(LifecycleEvent.PAUSE, () ->
FXActivity.getViewGroup().getViewTreeObserver().removeOnGlobalLayoutListener(listener));
});
FXActivity.getViewGroup().getViewTreeObserver().addOnGlobalLayoutListener(listener))
}
@Override
public void show() {
if (!visible) {
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
}
}
@Override
public void hide() {
if (visible) {
imm.toggleSoftInput(0, InputMethodManager.HIDE_IMPLICIT_ONLY);
}
}
}
现在,通过您的代码,您可以轻松地从文本字段调用键盘。
我添加了 long-press
类型的事件,基于此 implementation:
private void addPressAndHoldHandler(Node node, Duration holdTime, EventHandler<MouseEvent> handler) {
class Wrapper<T> {
T content;
}
Wrapper<MouseEvent> eventWrapper = new Wrapper<>();
PauseTransition holdTimer = new PauseTransition(holdTime);
holdTimer.setOnFinished(event -> handler.handle(eventWrapper.content));
node.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
eventWrapper.content = event;
holdTimer.playFromStart();
});
node.addEventHandler(MouseEvent.MOUSE_RELEASED, event -> holdTimer.stop());
node.addEventHandler(MouseEvent.DRAG_DETECTED, event -> holdTimer.stop());
}
因此,如果您有一个 TextField 并且想要在用户按住它一段时间时调用键盘,您只需要:
TextField textField = new TextField();
addPressAndHoldHandler(textField, Duration.seconds(1), event ->
Services.get(KeyboardService.class)
.ifPresent(KeyboardService::show));
编辑 注意:我添加了键盘的可见性控制,基于此 answer.
额外提示:按照这种方法,您只需一步就可以在长按时提供触觉反馈...
我们通过添加 KeyEvents 侦听器改进了您的初始版本,并在 Android 和 iOS 上成功测试了它。
defocus-method 可以很容易地在视图开始时调用,这样它就不会自动聚焦第一个 "Platform.runlater(() -> textField.defocus());" 的文本字段。
public class RefocusableTextField extends TextField {
private Region fakeFocusTarget;
public RefocusableTextField(String text) {
this();
setText(text);
}
public RefocusableTextField() {
fakeFocusTarget = new Region();
fakeFocusTarget.setManaged(false);
getChildren().add(fakeFocusTarget);
addEventFilter(MouseEvent.MOUSE_PRESSED, MouseEvent::consume);
addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
if (!isFocused()) {
requestFocus();
} else {
defocus();
}
});
addEventHandler(KeyEvent.KEY_PRESSED, e -> {
System.out.println(e.getCode());
if (e.getCode().equals(KeyCode.ENTER)) {
defocus();
}
});
}
public void defocus() {
fakeFocusTarget.requestFocus();
}
}