Прокрутка VBox с большим количеством текстовых полей

У меня много текстовых полей в VBox в области прокрутки. При прокрутке и касании текстовых полей он просто захватывает фокус. Поэтому плавная прокрутка невозможна. Как я могу получить хорошую прокрутку без нежелательного фокуса на любом текстовом поле. Нужно ли мне использовать события в текстовом поле во время прокрутки?


person tonimaroni    schedule 06.06.2017    source источник


Ответы (2)


Это возможное решение для случая, когда вы хотите прокручивать длинный список текстовых полей, не позволяя им взять фокус, пока вы вообще не прекратите прокрутку и явно не захотите выбрать одно из текстовых полей.

Он основан на пользовательском событии «нажмите и удерживайте», вдохновленном этим вопрос.

Идея состоит в том, чтобы связать элемент управления TextField в HBox и отключить доступ к элементу управления с помощью свойства прозрачности мыши контейнера.

Затем, всякий раз, когда вы нажимаете на контейнер, если вы нажмете достаточно долго, контейнер предоставит доступ к элементу управления, и появится клавиатура. В противном случае вы продолжите прокрутку, но не показывая клавиатуру.

Я буду использовать KeyboardService, указанный в этом вопросе только на iOS.

public class BasicView extends View {

    public BasicView(String name) {
        super(name);

        setTop(new Button("Button"));

        VBox controls = new VBox(15.0);

        controls.setAlignment(Pos.CENTER);

        ScrollPane pane = new ScrollPane(controls);
        controls.prefWidthProperty().bind(pane.widthProperty().subtract(20));

        for (int i = 0; i < 100; i++) {
            final Label label = new Label("TextField " + (i + 1));
            final TextField textField1 = new TextField();
            HBox.setHgrow(textField1, Priority.ALWAYS);
            HBox box = new HBox(10, label, textField1);
            box.setMouseTransparent(true);

            box.setAlignment(Pos.CENTER_LEFT);
            box.setPadding(new Insets(5));
            controls.getChildren().add(box);
        }

        addPressAndHoldHandler(controls, Duration.millis(300), eStart -> {
                for (Node box : controls.getChildren()) {
                    box.setMouseTransparent(true);
                }
            }, eEnd -> {
                for (Node box : controls.getChildren()) {
                    if (box.localToScene(box.getBoundsInLocal()).contains(eEnd.getSceneX(), eEnd.getSceneY())) {
                        box.setMouseTransparent(false);
                        ((HBox) box).getChildren().get(1).requestFocus();
                        break;
                    }
                }
            });
        setCenter(pane);

        // iOS only
        Services.get(KeyboardService.class).ifPresent(keyboard -> {
            keyboard.visibleHeightProperty().addListener((obs, ov, nv) -> {
                if (nv.doubleValue() > 0) {
                    for (Node box : controls.getChildren()) {
                        Node n1 = ((HBox) box).getChildren().get(1);
                        if (n1.isFocused()) {
                            double h = getScene().getHeight() - n1.localToScene(n1.getBoundsInLocal()).getMaxY();
                            setTranslateY(-nv.doubleValue() + h);
                            break;
                        }
                    }
                } else {
                    setTranslateY(0);
                }
            });
        });
    }

    @Override
    protected void updateAppBar(AppBar appBar) {
        appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> System.out.println("Menu")));
        appBar.setTitleText("Scrolling over TextFields");
    }

    private void addPressAndHoldHandler(Node node, Duration holdTime, 
            EventHandler<MouseEvent> handlerStart, EventHandler<MouseEvent> handlerEnd) {
        class Wrapper<T> { 
            T content; 
        }

        Wrapper<MouseEvent> eventWrapper = new Wrapper<>();

        PauseTransition holdTimer = new PauseTransition(holdTime);
        holdTimer.setOnFinished(event -> handlerEnd.handle(eventWrapper.content));

        node.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
            handlerStart.handle(event);
            eventWrapper.content = event;
            holdTimer.playFromStart();
        });
        node.addEventHandler(MouseEvent.MOUSE_RELEASED, event -> holdTimer.stop());
        node.addEventHandler(MouseEvent.DRAG_DETECTED, event -> holdTimer.stop());
    }
}

Обратите внимание, что я добавил кнопку вверху, чтобы сфокусироваться на первом месте, когда вы показываете вид.

Всякий раз, когда вы нажимаете/нажимаете на controls VBox, он делает все поля прозрачными для мыши: box.setMouseTransparent(true); и запускает PauseTransition.

Если отпустить мышь или перетащить мышь до 300 мс (это можно изменить по вашему усмотрению), переход остановится. В противном случае через 300 мс он установит поле в box.setMouseTransparent(false); и установит фокус на TextField, и в этот момент появится клавиатура.

person José Pereda    schedule 10.06.2017

Вот класс, который я использую для цели, которую вы описываете:

public class MouseClickedFilter{

    private final Node                       observableNode;

    private BooleanProperty                  scrolling          = new ReadOnlyBooleanWrapper(false);

    private EventHandler<? super MouseEvent> dragDetectedFilter = e -> scrolling.set(true);
    private EventHandler<? super MouseEvent> mouseExitedHandler = e -> scrolling.set(false);

    private EventHandler<? super MouseEvent> mouseClickedFilter = evt ->
                                                                    {
                                                                        if (scrolling.get()) {
                                                                            evt.consume();
                                                                            scrolling.set(false);
                                                                        }
                                                                    };

    private boolean                          listenersEnabled;

    public MouseClickedFilter(Node observableNode) {
        this.observableNode = observableNode;
    }

    public void activate() {
        if (!listenersEnabled) {
            observableNode.addEventFilter(MouseEvent.DRAG_DETECTED, dragDetectedFilter);
            observableNode.addEventHandler(MouseEvent.MOUSE_EXITED, mouseExitedHandler);
            observableNode.addEventFilter(MouseEvent.MOUSE_CLICKED, mouseClickedFilter);
        }
    }

    public void deactivate() {
        if (listenersEnabled) {
            observableNode.removeEventFilter(MouseEvent.DRAG_DETECTED, dragDetectedFilter);
            observableNode.removeEventHandler(MouseEvent.MOUSE_EXITED, mouseExitedHandler);
            observableNode.removeEventFilter(MouseEvent.MOUSE_CLICKED, mouseClickedFilter);
        }
    }

    public final ReadOnlyBooleanProperty scrollingProperty() {
        return scrolling;
    }

    public final boolean isScrolling() {
        return scrolling.get();
    }

}

ObservableNode - это ваш ScrollPane, содержащий текстовые поля

person jns    schedule 06.06.2017
comment
Спасибо за код, но он не помог. Прокрутка все еще застревает и показывает TextFields. - person tonimaroni; 08.06.2017
comment
Да, прокрутка застревает. Похоже, это основная проблема ScrollPane на Android в сочетании с javafxport, независимо от узлов, используемых в ScrollPane. - person jns; 08.06.2017