JavaFXPorts - Problems with ScrollBar on Mobile Devices

338 views Asked by At

I'm currently developing a mobile application with JavaFX, using GluonHQ and JavaFXPorts. One of my screens contains a listview as you can see from the screenshot below, which was taken from my iPhone 6.

I have noticed the following problems with the scrollbar in mobile devices:

  1. The first time i touch the screen the scroll bar appears a bit off place and then moves to the correct right position. This just happens quickly only the first time. (Screenshot)
  2. I noticed that the scrollbar appears every time i touch the screen and not only when I touch and drag. On native iOS applications the scrollbar appears only when you touch and drag. If you keep your finger on screen and then remove it the scrollbar does not appear.
  3. The scrollbar always takes some time to disappear when I remove my finger from the screen, whilst in native apps it disappears instantly.

Can anyone help me on fixing these issues. How can you define the time the scrollbar appears before it hides again?

You can experience this situation by just creating a ListView and load it with some items.

enter image description here

UPDATE

Thanks to the answer of Jose Pereda below, I have managed to overcome all three problems described above. Here is the code I used to reach the desired results. Watch this short video to get a quick idea of how the new scrolling bar appears and behaves. Again, Jose, you are the boss! Please go ahead with any comments for improvement.

public class ScrollBarView {

   public static void changeView(ListView<?> listView) {

       listView.skinProperty().addListener(new ChangeListener<Object>() {
           private StackPane thumb;
           private ScrollBar scrollBar;
           boolean touchReleased = true, inertia = false;

           @Override
           public void changed(ObservableValue<? extends Object> observable, Object oldValue, Object newValue) {
               scrollBar = (ScrollBar) listView.lookup(".scroll-bar");

               // "hide" thumb as soon as the scroll ends
               listView.setOnScrollFinished(e -> {
                  if (thumb != null) {
                     touchReleased = true;
                     playAnimation();
                  } // if
               });

               // Fix for 1. When user touches first time, the bar is set invisible so that user cannot see it is
               // placed in the wrong position.
               listView.setOnTouchPressed(e -> {
                  if (thumb == null) {
                     thumb = (StackPane) scrollBar.lookup(".thumb");
                     thumb.setOpacity(0);
                     initHideBarAnimation();
                  } // if
               });

               // Try to play animation whenever an inertia scroll takes place
               listView.addEventFilter(ScrollEvent.SCROLL, e -> {
                   inertia = e.isInertia();
                   playAnimation();
               });

               // As soon as the scrolling starts the thumb become visible.
               listView.setOnScrollStarted(e -> {
                   sbTouchTimeline.stop();
                   thumb.setOpacity(1);
                   touchReleased = false;
               });
           } // changed

           private Timeline sbTouchTimeline;
           private KeyFrame sbTouchKF1, sbTouchKF2;

           // Initialize the animation that hides the thumb when no scrolling takes place.
           private void initHideBarAnimation() {
              if (sbTouchTimeline == null) {
                 sbTouchTimeline = new Timeline();
                 sbTouchKF1 = new KeyFrame(Duration.millis(50), new KeyValue(thumb.opacityProperty(), 1));
                 sbTouchKF2 = new KeyFrame(Duration.millis(200), (e) -> inertia = false, new KeyValue(thumb.opacityProperty(), 0));
                 sbTouchTimeline.getKeyFrames().addAll(sbTouchKF1, sbTouchKF2);
              } // if
           } // initHideBarAnimation

           // Play animation whenever touch is released, and when an inertia scroll is running but thumb reached its bounds.
           private void playAnimation() {
              if(touchReleased)
                 if(!inertia || (scrollBar.getValue() != 0.0 && scrollBar.getValue() != 1))
                    sbTouchTimeline.playFromStart();
           } // playAnimation()
       });
   } // changeView
} // ScrollBarView
1

There are 1 answers

2
José Pereda On BEST ANSWER

As mentioned in the comments, the first issue is known, and for now it hasn't been fixed. The problem seems to be related to the initial width of the scrollbar (20 pixels as in desktop), and then is set to 8 pixels (as in touch enabled devices), and moved to its final position with this visible shift of 12 pixels to the right.

As for the second and third issues, if you don't want to patch and build the JDK yourself, it is possible to override the default behavior, as the ScrollBar control is part of the VirtualFlow control of a ListView, and both can be found on runtime via lookups.

Once you have the control, you can play with its visibility according to your needs. The only problem with this property is that it is already bound and constantly called from the layoutChildren method.

This is quite a hacky solution, but it works for both 2) and 3):

public class BasicView extends View {

    private final ListView<String> listView;
    private ScrollBar scrollbar;
    private StackPane thumb;

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

        listView = new ListView<>();
        // add your items

        final InvalidationListener skinListener = new InvalidationListener() {
            @Override
            public void invalidated(Observable observable) {
                if (listView.getSkin() != null) {
                    listView.skinProperty().removeListener(this);
                    scrollbar = (ScrollBar) listView.lookup(".scroll-bar");
                    listView.setOnScrollFinished(e -> {
                        if (thumb != null) {
                            // "hide" thumb as soon as scroll/drag ends
                            thumb.setStyle("-fx-background-color: transparent;");
                        }
                    });
                    listView.setOnScrollStarted(e -> {
                        if (thumb == null) {
                            thumb = (StackPane) scrollbar.lookup(".thumb");
                        }
                        if (thumb != null) {
                            // "show" thumb again only when scroll/drag starts
                            thumb.setStyle("-fx-background-color: #898989;");
                        }
                    });

                }
            }
        };
        listView.skinProperty().addListener(skinListener);
        setCenter(listView);
    }

}