【问题标题】:How to make the SoftKeyboard show up again如何让软键盘再次出现
【发布时间】:2017-01-07 16:37:59
【问题描述】:

当您通过Android Back Button 隐藏SoftKeyboard 时,触摸(仍然集中的)inputNode 不会使键盘再次显示。 为了解决这个问题,我使用了以下类:

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 内部类(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);
        }
    }
}

【问题讨论】:

    标签: gluon-mobile


    【解决方案1】:

    如您所知,Android 的 JavaFX 层管理软键盘,它实际上仅由获得/失去焦点 event 触发。

    所以你的方法是正确的,但如果你想避免私有 API,我看到了两种可能的解决方案:

    • 进入JavaFX层,修改并构建... 可以,但是工作量很大,下个JavaFXPorts版本会中断。

    • 创建自定义插件并提供 API 以方便您管理软键盘。

    对于第二个选项,使用新的 Down 插件 API 很容易做到这一点。在你的主包com.gluonhq.charm.down.plugins下创建这两个类:

    键盘服务

    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下添加这个类:

    AndroidKeyboardService

    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。

    额外提示:按照这种方法,您只需一步即可在长按时提供触觉反馈...

    【讨论】:

    • 很好的答案!非常感谢。
    • 这里还有一个问题:当键盘已经显示imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0)时会隐藏键盘。你怎么能防止这种情况,或者有没有办法找出键盘是否显示?
    • 没有Android API,但基于answer,您可以添加一些侦听器来确定键盘是显示还是隐藏。我将编辑我的答案。
    【解决方案2】:

    我们改进了您的初始版本,为 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();
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-02-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多