jns
jns

Reputation: 6952

Keyboard covers TextField

As the center node of a gluon view I have a scrollpane which contains several textfields in a vbox. When one of these textfields becomes the focusowner and the keyboard shows up, the textfield doesn't get repositioned according to the layout of the keyboard, so it is left covered by the keyboard. I tried putting

 android:windowSoftInputMode="adjustResize"

in the AndroidManifest, but without any success.

As a workaround I translate the y-coordinates of the covered textfield to the visible area. When you press the android back button to hide the keyboard, the textfields position will be reset to its original state. The issue I'm getting here is that I don't get an event for the android back button, no matter where I add the listener:

 view.addEventFilter(MobileEvent.BACK_BUTTON_PRESSED, evt -> eventFilter); 

 MobileApplication.getInstance().getGlassPane().addEventFilter(MobileEvent.BACK_BUTTON_PRESSED, evt -> eventFilter); 

Is there any possibility to handle the positioning of a node under the keyboard, or to get a reference to the keyboard itself?

Upvotes: 1

Views: 599

Answers (2)

jns
jns

Reputation: 6952

This is the solution I could come up with so far:

public class PositionAdjuster {

    public static void main(String[] args) { launch(args); }

    private static final float SCALE = FXActivity.getInstance().getResources().getDisplayMetrics().density;

    private Node                   nodeToAdjust;
    private ObservableValue<Node>  focusOwner;

    private ViewGroup              viewGroup;
    private Rect                   currentBounds;
    private DoubleProperty height;

    private OnGlobalLayoutListener layoutListener;

    public PositionAdjuster(Node nodeToAdjust, ObservableValue<Node> focusOwner) {
        this.nodeToAdjust = nodeToAdjust;
        this.focusOwner = focusOwner;

        initLayoutListener();
    }

    private void initLayoutListener() {
        double screenHeight = MobileApplication.getInstance().getScreenHeight();
        height = new SimpleDoubleProperty(screenHeight);
        height.addListener((ov, n, n1) -> onHeightChanged(n, n1));

        layoutListener = () -> height.set(getCurrentHeigt());

        viewGroup = FXActivity.getViewGroup();
        viewGroup.getViewTreeObserver().addOnGlobalLayoutListener(layoutListener);
        currentBounds = new Rect();
    }

    private float getCurrentHeigt() {
        viewGroup.getRootView().getWindowVisibleDisplayFrame(currentBounds);
        return currentBounds.height() / SCALE;
    }

    private void onHeightChanged(Number oldValue, Number newValue) {
        double heightDelta = newValue.doubleValue() - oldValue.doubleValue();

        if (heightDelta < 0) {
            double maxY = getBoundsInScene(nodeToAdjust)).getMaxY();
            double currentMaxY = heightDelta + maxY;

            double result = currentMaxY- getBoundsInScene(focuseOwner.getValue()).getMaxY();

            if (result < 0) {
                nodeToAdjust.setTranslateY(result);
            }

        } else if (heightDelta > 0) {
            nodeToAdjust.setTranslateY(0);
        }
    }

    private Bounds getBoundsInScene(Node node) {
        return node.localToScene(node.getBoundsInLocal());
    }

    public void removeListener() {
        viewGroup.getViewTreeObserver().removeOnGlobalLayoutListener(layoutListener);
    }
}

EDIT:

I think this is a more straightforward approach. The previous version was dependent on the maxY of noteToAdjust to be equal to the height of the screen, not taking into account e.g. the presence of a bottomBar. Now the maxY position of the focusedNode is validated against the visible screen height, and the difference is used to reposition its parent.

public AndroidPositionAdjuster(Node parent, ObservableValue<Node> focusOwner) {
    this.parent = parent;
    this.focusOwner = focusOwner;

    initLayoutListener();
}

private void onHeightChanged(Number oldHeight, Number newHeight) {
    double heightDelta = newHeight.doubleValue() - oldHeight.doubleValue();

    if (heightDelta < 0) {
        double maxY = newHeight.doubleValue();
        double focusedNodeY = getBoundsInScene(focusOwner.getValue()).getMaxY();
        double result = maxY - focusedNodeY;

        if (result < 0) {
            parent.setTranslateY(result);
        }

    } else if (heightDelta > 0) {
         parent.setTranslateY(0);
    }
}

Upvotes: 1

Pedro
Pedro

Reputation: 313

Only layers get the MobileEvent.BACK_BUTTON_PRESSED event. One solution is to go native and use the Android API.

Upvotes: 1

Related Questions