Gluon Mobile ScrollPane Оптимизация

Я пытаюсь реализовать JavaFX ScrollPane с вложенным VBox, и у меня возникает странная проблема при слишком быстрой прокрутке на мобильных устройствах. Проблема в том, что если я прокручиваю более мелкие, но быстрые жесты вверх, панель прокрутки сначала останавливается на полсекунды, а затем продолжается. Это становится медленным в случайных жестах салфетки. В любом случае я могу оптимизировать это?

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

public class StackUserView extends View {

    private Label label;

    public StackUserView(String name) {
        super(name);
        initDisplay();
    }

    private void initDisplay() {
        this.label = new Label("10");
        label.setFont(Font.font(40d));
        VBox box = new VBox();
        box.setSpacing(10);
        box.setAlignment(Pos.CENTER_RIGHT);

        for(int i = 0; i < 100; i++){
            Label label = new Label("HELLO");
            label.setCache(true);
            label.setCacheHint(CacheHint.SPEED);
            label.setCacheShape(true);
            box.getChildren().add(label);
        }

        ScrollPane pane = new ScrollPane(box);
        pane.setCacheHint(CacheHint.QUALITY);
        pane.setFitToHeight(true);
        pane.setFitToHeight(true);

        this.setCenter(pane);
    }

    @Override
    protected void updateAppBar(AppBar appBar) {
        appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> MobileApplication.getInstance().showLayer(SpeedSelect.MENU_LAYER)));
        appBar.setTitleText(this.getName());
        appBar.getActionItems().add(MaterialDesignIcon.SEARCH.button(e -> System.out.println("Search")));
    }

}

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

Мне удалось оптимизировать ситуации, когда требуется прокрутка, создав собственный CharmListView с ячейками HBox, но тогда я не могу использовать SceneBuilder для своих представлений, что влияет на удобство сопровождения.

Примечание. Для тестирования я использую LG G5.

Вот короткое видео о проблеме. Вы можете увидеть после салфетки. Прокрутка останавливается на секунду, а затем продолжается:

Отображение задержки прокрутки.


person A.Sharma    schedule 28.01.2018    source источник
comment
Я еще не тестировал ваш код, но почему вы говорите, что не можете использовать CharmListView с Scene Builder? Пока что элементы управления ListView/CharmListView как раз и предназначены для виртуализации и повторного использования всего нескольких ячеек. И в случае с мобильным устройством CharmListView на самом деле является оптимизированным ListView.   -  person José Pereda    schedule 28.01.2018
comment
Я, конечно, могу добавить CharmListView в SceneBuilder, но как мне добавить к нему HBox в SceneBuilder? Для других ситуаций в этом приложении я фактически прибегал к жесткому кодированию представления списка HBox, которое расширяет CharmListView. Однако большинство представлений в этом приложении создаются с помощью SceneBuilder с внедрением FXML, поэтому я хочу ограничить количество жесткого кода, необходимого для создания представления. Это не длинный список. Достаточно долго, чтобы не уместиться в поле зрения. Приведенный выше фрагмент кода — это всего лишь пример кода для воссоздания проблемы. Не настоящий код.   -  person A.Sharma    schedule 28.01.2018
comment
Это тоже не совсем список. Это просто длинная форма, которая может переполниться в зависимости от размера устройства, которое ее просматривает.   -  person A.Sharma    schedule 28.01.2018
comment
Как и обычный ListView, CharmListView использует реализацию ListCell. Ни один из них не поддерживает ячейки в Scene Builder. Так что это не может быть достигнуто там. Но если он тоже не подходит для списка, тогда вам действительно нужно другое решение.   -  person José Pereda    schedule 28.01.2018
comment
@sillyfly было бы прискорбно, если бы это было правдой. Это означало бы, что эта ошибка будет сохраняться в любой ситуации, когда требуется прокрутка на мобильном устройстве без использования представления списка. Я бы подумал, что это будет серьезным ограничением.   -  person A.Sharma    schedule 29.01.2018
comment
Ты не понимаешь. Эта ошибка влияет на любой рендеринг текста в порте JavaFX для Android, независимо от контейнера. ListView в этом смысле не намного лучше, чем ScrollPane. Если вы можете скомпилировать собственную версию порта JFX, обходной путь довольно прост. В противном случае требуется относительно более сложный обходной путь для изменения FontFactory посредством отражения.   -  person Itai    schedule 29.01.2018
comment
Ах я вижу. я не знаю, что это полностью проблема в моем случае, так как она сохраняется как в Android, так и в iOS.   -  person A.Sharma    schedule 29.01.2018
comment
@sillyfly JavaFXPorts на Android использует FreeType, а не Pango. Я не думаю, что проблема, которую вы связали, имеет какое-либо отношение к этой.   -  person José Pereda    schedule 31.01.2018
comment
@sillyfly Вы имеете в виду это PrismFontFactory.java? Никаких следов Панго. Если вы проверите журнал приложения для Android, вы найдете что-то вроде Loading FontFactory com.sun.javafx.font.freetype.FTFactory и Freetype2 Loaded (version 2.6.5). Или, если вы проверите apk, вы найдете эту библиотеку libjavafx_font_freetype.so.   -  person José Pereda    schedule 31.01.2018
comment
@sillyfly В любом случае, давайте не будем обсуждать здесь проблему Pango, так как этот вопрос связан с чем-то совершенно другим.   -  person José Pereda    schedule 31.01.2018
comment
@JoséPeda Я смог еще больше оптимизировать прокрутку, так как с последним кодом, который я опубликовал, были некоторые сбои пользовательского интерфейса. Дайте мне знать о любых областях, которые вы можете найти улучшение.   -  person A.Sharma    schedule 31.01.2018
comment
@ А.Шарма Да, я видел твой ответ. Только что добавил мой. Можете ли вы проверить, работает ли это и для вас?   -  person José Pereda    schedule 01.02.2018
comment
Хосе, это прекрасно работает. Спасибо!   -  person A.Sharma    schedule 02.02.2018


Ответы (2)


Ответ, предоставленный А. Шармой, решает некоторые проблемы, поскольку он полностью удаляет базовую реализацию встроенной обработки событий ScrollPane.

Конечно, для окончательного решения проблемы требуется правильное решение в JavaFXPorts.

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

Этот класс можно найти здесь.

Хорошей новостью является то, что этот класс можно клонировать в ваш проект (например, MyScrollPaneSkin), изменять и добавлять в качестве нового скина для существующего элемента управления ScrollPane.

Возможное исправление

После некоторых тестов виновник проблемы может быть обнаружен в анимации contentsToViewTimeline. Он выполняет 1,35-секундную паузу, чтобы:

блокировать «афтершоки»

Затем есть эта строка внутри обработчика события прокрутки, которая проверяет, закончилась ли анимация, и только после этого обновляет положение полосы прокрутки:

if (!(((ScrollEvent)event).isInertia()) || (((ScrollEvent)event).isInertia()) && 
    (contentsToViewTimeline == null || 
     contentsToViewTimeline.getStatus() == Status.STOPPED)) {
    vsb.setValue(newValue);
    ...
}   

Хотя эту анимацию не следует удалять, полоса прокрутки должна обновляться, даже когда анимация находится в режиме ожидания.

Я сделал следующее изменение, заменив строки 517-527 на:

    /*
    ** if there is a repositioning in progress then we only
    ** set the value for 'real' events
    */
    if ((newValue <= vsb.getMax() && newValue >= vsb.getMin())) {
        vsb.setValue(newValue);
    }
    boolean stop = ! ((ScrollEvent) event).isInertia() || ((ScrollEvent) event).isInertia() && (contentsToViewTimeline == null || contentsToViewTimeline.getStatus() == Status.STOPPED);
    if ((newValue > vsb.getMax() || newValue < vsb.getMin()) && (!mouseDown && !touchDetected) && stop) {
        startContentsToViewport();
    }
    if (stop) {
        event.consume();
    }

В вашем проекте теперь замените встроенный скин:

 ScrollPane pane = new ScrollPane(box);
 pane.setSkin(new MyScrollPaneSkin(pane));

Я тестировал его на Android и iOS, и в обоих случаях анимация плавная, и никаких следов проблемы не осталось.

Кроме того, эти свойства могут помочь (используйте java.custom.properties, добавленный к /src/main/resources):

gluon.experimental.performance=true
com.sun.javafx.gestures.scroll.inertia.velocity=2000

Если это работает, его можно отправить в выпуск JavaFXPorts или в качестве PR.

person José Pereda    schedule 01.02.2018
comment
Хосе для файла пользовательских свойств. Единственное, что требуется для этого, это просто ввести его в папку ресурсов? Или мне также нужно ссылаться на него где-нибудь в коде Java? - person A.Sharma; 01.02.2018
comment
Или я могу просто установить его с помощью метода System.setProperties()? - person A.Sharma; 01.02.2018
comment
Просто добавьте файл свойств с этим именем. - person José Pereda; 01.02.2018

Поэтому я как бы взломал исправление для этого, используя собственное событие прокрутки и настроив его. По сути, я использовал метод проб и ошибок, чтобы выяснить, какие жесткие (факторы ниже) значения мне нужны, чтобы прокрутка прошла нормально.

private SimpleDoubleProperty location = new SimpleDoubleProperty();
private SimpleDoubleProperty vValue = new SimpleDoubleProperty(0);
private SimpleLongProperty startTime = new SimpleLongProperty();
private SimpleLongProperty timer = new SimpleLongProperty();
private double height = MobileApplication.getInstance().getGlassPane().getHeight();
private Animation animation;
private Animation callbackAnimation;

private void scrollOverride() {

    sp.addEventFilter(ScrollEvent.SCROLL, event -> {
        event.consume();
    });

    sp.setOnTouchPressed(e -> {

        if (callbackAnimation != null){
            callbackAnimation.stop();
        }

        if (animation != null) {
            animation.stop();
        }

        vValue.set(sp.vvalueProperty().get());
        location.set(e.getTouchPoint().getY());
        startTime.set((new Date()).getTime());

    });

    sp.setOnTouchMoved(e -> {
        timer.set((new Date()).getTime());
    });

    sp.setOnTouchReleased(e -> {

        Boolean dontOverride = false;

        double deltaTime = (new Date()).getTime() - startTime.get();
        double deltaY = sp.vvalueProperty().get() - vValue.get();

        if(Math.abs(deltaY) < 0.05){
            dontOverride = true;
        }

        double translation = deltaY / (deltaTime/300);

        double value = 0d;

        if (Math.abs(timer.get() - startTime.get()) < 500) {
            value = sp.vvalueProperty().get() + translation;
        } else {
            value = sp.vvalueProperty().get();
            dontOverride = true;
        }

        animation = new Timeline(
                new KeyFrame(Duration.millis(deltaTime + 100),
                        new KeyValue(sp.vvalueProperty(), value)));

        animation.play();

        if (!dontOverride) {
            animation.setOnFinished(evt -> {
                boolean innerUp = (sp.vvalueProperty().get() - vValue.get()) < 0;

                int innerSign = 1;

                if (innerUp) {
                    innerSign = -1;
                }

                callbackAnimation = new Timeline(
                        new KeyFrame(Duration.millis(deltaTime + 150),
                                new KeyValue(sp.vvalueProperty(), sp.vvalueProperty().get() + (0.1 * innerSign), Interpolator.EASE_OUT)));
                callbackAnimation.play();
            });
        }

    });
}

Вы можете ограничить это переопределение только на мобильных устройствах, выполнив следующие действия:

if(!com.gluonhq.charm.down.Platform.isDesktop()){
   scrollOverride();
}      

Если кто-то может оптимизировать это или придумать собственное решение, это было бы здорово. Опять же, моя цель состояла в том, чтобы предотвратить эту задержку при прокрутке.

В приложении на моем iPhone 7+ это работает очень хорошо. Это работает так же хорошо, как когда я внедряю браузер в приложение с веб-представлением.

person A.Sharma    schedule 28.01.2018