Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX: scrollview swipe effect

I would like to know, how to make scrollview swipe effect when mouse is dragged up and down. I'm developing JavaFX application which has ListView & I need to scroll the list when user drags the mouse up and down (like iOS contact list).

Also how to add the user's dragging velocity and speed to the scrolling value?

UPDATED : I got some code here but It doesn't work properly...

import java.util.ArrayList;
import java.util.Arrays;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.animation.TimelineBuilder;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.Event;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.FlowPane;
import javafx.util.Duration;

public class Swipe extends ScrollPane {

private static final int INERTIA_DURATION = 2400;
private static final double CLICK_THRESHOLD = 20;
private static final double CLICK_TIME_THRESHOLD = Integer.parseInt(System.getProperty("click", "400"));

private final double width;
private final double height;
private long startDrag;
private long lastDrag;
private long lastDragDelta;
private int startDragX;
private int startDragY;
private int lastDragX;
private int lastDragY;
private int lastDragStepX;
private int lastDragStepY;
private double dragVelocityX;
private double dragVelocityY;
private boolean clickThresholdBroken;
private Timeline inertiaTimeline = null;
private long lastClickTime = -1;
private final boolean isFiredByMe = false;

public Swipe(double width, double height) {
    this.width = width;
    this.height = height;
    init();
}

private void init() {
    setPrefSize(width, height);
    setPannable(true);
    setHbarPolicy(ScrollBarPolicy.NEVER);
    setVbarPolicy(ScrollBarPolicy.NEVER);
    setEventHandlers();
    ContentPane cp = new ContentPane();
    setContent(cp);
}



private void setEventHandlers() {
    setOnMousePressed((MouseEvent event) -> {

        lastDragX = startDragX = (int) event.getX();
        lastDragY = startDragY = (int) event.getY();
        lastDrag = startDrag = System.currentTimeMillis();
        lastDragDelta = 0;
        if (inertiaTimeline != null) {
            inertiaTimeline.stop();
        }
        clickThresholdBroken = false;
    });

    setOnDragDetected((MouseEvent event) -> {

        // Delta of this drag vs. last drag location (or start)
        lastDragStepX = (int) event.getX() - lastDragX;
        lastDragStepY = (int) event.getY() - lastDragY;

        // Duration of this drag step.
        lastDragDelta = System.currentTimeMillis() - lastDrag;

        // Velocity of last drag increment.
        dragVelocityX = (double) lastDragStepX / (double) lastDragDelta;
        dragVelocityY = (double) lastDragStepY / (double) lastDragDelta;

        // Snapshot of this drag event.
        lastDragX = (int) event.getX();
        lastDragY = (int) event.getY();
        lastDrag = System.currentTimeMillis();

        // Calculate distance so far -- have we dragged enough to scroll?
        final int dragX = (int) event.getX() - startDragX;
        final int dragY = (int) event.getY() - startDragY;
        double distance = Math.abs(Math.sqrt((dragX * dragX) + (dragY * dragY)));

        int scrollDistX = lastDragStepX;
        int scrollDistY = lastDragStepY;
        if (!clickThresholdBroken && distance > CLICK_THRESHOLD) {
            clickThresholdBroken = true;
            scrollDistX = dragX;
            scrollDistY = dragY;
        }

        if (clickThresholdBroken) {
            Event.fireEvent(event.getTarget(), new ScrollEvent(
                    ScrollEvent.SCROLL,
                    scrollDistX, scrollDistY,
                    scrollDistX, scrollDistY,
                    event.isShiftDown(), event.isControlDown(), event.isAltDown(), event.isMetaDown(),
                    true, false,
                    event.getX(), event.getY(),
                    event.getSceneX(), event.getSceneY(),
                    ScrollEvent.HorizontalTextScrollUnits.NONE, 0,
                    ScrollEvent.VerticalTextScrollUnits.NONE, 0, 0, null));
        }
    });

    setOnMouseReleased((MouseEvent event) -> {
        handleRelease(event);
    });

    setOnMouseClicked((MouseEvent event) -> {
        final long time = System.currentTimeMillis();
        if (clickThresholdBroken || (lastClickTime != -1 && (time - lastClickTime) < CLICK_TIME_THRESHOLD)) {
            event.consume();
        }
        lastClickTime = time;
    });
}

private void handleRelease(final MouseEvent me) {
    if (clickThresholdBroken) {
    // Calculate last instantaneous velocity. User may have stopped moving
    // before they let go of the mouse.
    final long time = System.currentTimeMillis() - lastDrag;
    dragVelocityX = (double) lastDragStepX / (time + lastDragDelta);
    dragVelocityY = (double) lastDragStepY / (time + lastDragDelta);

    // determin if click or drag/flick
    final int dragX = (int) me.getX() - startDragX;
    final int dragY = (int) me.getY() - startDragY;

    // calculate complete time from start to end of drag
    final long totalTime = System.currentTimeMillis() - startDrag;

    // if time is less than 300ms then considered a quick flick and whole time is used
    final boolean quick = totalTime < 300;

    // calculate velocity
    double velocityX = quick ? (double) dragX / (double) totalTime : dragVelocityX; // pixels/ms
    double velocityY = quick ? (double) dragY / (double) totalTime : dragVelocityY; // pixels/ms

    final int distanceX = (int) (velocityX * INERTIA_DURATION); // distance
    final int distanceY = (int) (velocityY * INERTIA_DURATION); // distance
    //
    DoubleProperty animatePosition = new SimpleDoubleProperty() {
        double lastMouseX = me.getX();
        double lastMouseY = me.getY();

        @Override
        protected void invalidated() {
            final double mouseX = me.getX() + (distanceX * get());
            final double mouseY = me.getY() + (distanceY * get());
            final double dragStepX = mouseX - lastMouseX;
            final double dragStepY = mouseY - lastMouseY;

            if (Math.abs(dragStepX) >= 1.0 || Math.abs(dragStepY) >= 1.0) {
                Event.fireEvent(me.getTarget(), new ScrollEvent(
                        ScrollEvent.SCROLL,
                        dragStepX, dragStepY,
                        (distanceX * get()), (distanceY * get()),
                        me.isShiftDown(), me.isControlDown(), me.isAltDown(), me.isMetaDown(),
                        true, true,
                        me.getX(), me.getY(),
                        me.getSceneX(), me.getSceneY(),
                        ScrollEvent.HorizontalTextScrollUnits.NONE, 0,
                        ScrollEvent.VerticalTextScrollUnits.NONE, 0,
                        0, null));
            }
            lastMouseX = mouseX;
            lastMouseY = mouseY;
        }
    };

    // animate a slow down from current velocity to zero
    inertiaTimeline = TimelineBuilder.create()
            .keyFrames(
                    new KeyFrame(Duration.ZERO, new KeyValue(animatePosition, 0)),
                    new KeyFrame(Duration.millis(INERTIA_DURATION), new KeyValue(animatePosition, 1d, Interpolator.SPLINE(0.0513, 0.1131, 0.1368, 1.0000)))
            ).build();
    inertiaTimeline.play();
    }
}

private class ContentPane extends FlowPane {

    private ArrayList getList() {
        String[] list = {
            "Kerrie Batts", "Raina Huffstutler", "Kip Kukowski", "Trish Sullivan", "Kyla Hollingsworth", "Gearldine Leavy", "Major Langdon", "Avery Rusin", "Hedy Messina", "Audry Felps", "Tianna Robbins", "Marian Tranmer", "Lashaunda Bivona", "Leighann Schwab", "Emanuel Volpe", "Neida Geist", "Edda Placencia", "Olevia Hippe", "Fernando Cohen", "Danette Dorsett"};
        ArrayList<String> nameList = new ArrayList();
        nameList.addAll(Arrays.asList(list));
        return nameList;
    }

    public ContentPane() {
        setPrefSize(215, 271);
        ArrayList<String> nameList = getList();
        Element[] element = new Element[nameList.size()];

        for (int i = 0; i < nameList.size(); i++) {
            String name = nameList.get(i);
            Element el = element[i] = new Element(210, 25, name);
            getChildren().add(el);
        }
    }

}

private class Element extends AnchorPane {

    private double width;
    private double height;
    private String name;

    public Element(double width, double height, String name) {
        this.width = width;
        this.height = height;
        this.name = name;
        init();
    }

    private Label createName() {
        Label label = new Label(name);
        label.setPrefSize(width, height);
        label.setAlignment(Pos.CENTER_LEFT);
        AnchorPane.setLeftAnchor(label, 5.0);
        label.setStyle("-fx-font-family: Calibri; -fx-font-size: 14;");
        return label;
    }

    private void init() {
        setPrefSize(width, height);
        getChildren().add(createName());
    }

    public void setPrefSize(double width, double height) {
        this.width = width;
        this.height = height;
    }

    public void setName(String name) {
        this.name = name;
    }
}

}

like image 404
Hiran Avatar asked Mar 27 '14 07:03

Hiran


1 Answers

I figured out myself. There is two classes. 1st one is for calculate the velocity of the movement.

VelocityTracker.java

import javafx.scene.input.MouseEvent;

public final class VelocityTracker {

    private static final boolean DEBUG = false;
    private static final boolean localLOGV = DEBUG;

    private static final int NUM_PAST = 10;
    private static final int MAX_AGE_MILLISECONDS = 200;

    private static final int POINTER_POOL_CAPACITY = 20;

    private static Pointer sRecycledPointerListHead;
    private static int sRecycledPointerCount;

    private static final class Pointer {

        public Pointer next;

        public int id;
        public float xVelocity;
        public float yVelocity;

        public final float[] pastX = new float[NUM_PAST];
        public final float[] pastY = new float[NUM_PAST];
        public final long[] pastTime = new long[NUM_PAST]; // uses Long.MIN_VALUE as a sentinel

    }

    private Pointer mPointerListHead; // sorted by id in increasing order
    private int mLastTouchIndex;

    public VelocityTracker() {
        clear();
    }

    public void clear() {
        releasePointerList(mPointerListHead);

        mPointerListHead = null;
        mLastTouchIndex = 0;
    }

    public void addMovement(MouseEvent ev) {
        final int historySize = 0;
        final int lastTouchIndex = mLastTouchIndex;
        final int nextTouchIndex = (lastTouchIndex + 1) % NUM_PAST;
        final int finalTouchIndex = (nextTouchIndex + historySize) % NUM_PAST;

        mLastTouchIndex = finalTouchIndex;

        if (mPointerListHead == null) {
            mPointerListHead = obtainPointer();
        }

        final float[] pastX = mPointerListHead.pastX;
        final float[] pastY = mPointerListHead.pastY;
        final long[] pastTime = mPointerListHead.pastTime;

        pastX[finalTouchIndex] = (float) ev.getX();
        pastY[finalTouchIndex] = (float) ev.getY();
        pastTime[finalTouchIndex] = System.currentTimeMillis();

    }

    public void computeCurrentVelocity(int units) {
        computeCurrentVelocity(units, Float.MAX_VALUE);
    }

    public void computeCurrentVelocity(int units, float maxVelocity) {
        final int lastTouchIndex = mLastTouchIndex;

        for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) {
            final long[] pastTime = pointer.pastTime;
            int oldestTouchIndex = lastTouchIndex;
            int numTouches = 1;
            final long minTime = pastTime[lastTouchIndex] - MAX_AGE_MILLISECONDS;
            while (numTouches < NUM_PAST) {
                final int nextOldestTouchIndex = (oldestTouchIndex + NUM_PAST - 1) % NUM_PAST;
                final long nextOldestTime = pastTime[nextOldestTouchIndex];
                if (nextOldestTime < minTime) { // also handles end of trace sentinel
                    break;
                }
                oldestTouchIndex = nextOldestTouchIndex;
                numTouches += 1;
            }
            if (numTouches > 3) {
                numTouches -= 1;
            }
            final float[] pastX = pointer.pastX;
            final float[] pastY = pointer.pastY;

            final float oldestX = pastX[oldestTouchIndex];
            final float oldestY = pastY[oldestTouchIndex];
            final long oldestTime = pastTime[oldestTouchIndex];

            float accumX = 0;
            float accumY = 0;

            for (int i = 1; i < numTouches; i++) {
                final int touchIndex = (oldestTouchIndex + i) % NUM_PAST;
                final int duration = (int) (pastTime[touchIndex] - oldestTime);

                if (duration == 0) {
                    continue;
                }

                float delta = pastX[touchIndex] - oldestX;
                float velocity = (delta / duration) * units; // pixels/frame.
                accumX = (accumX == 0) ? velocity : (accumX + velocity) * .5f;

                delta = pastY[touchIndex] - oldestY;
                velocity = (delta / duration) * units; // pixels/frame.
                accumY = (accumY == 0) ? velocity : (accumY + velocity) * .5f;
            }

            if (accumX < -maxVelocity) {
                accumX = -maxVelocity;
            } else if (accumX > maxVelocity) {
                accumX = maxVelocity;
            }

            if (accumY < -maxVelocity) {
                accumY = -maxVelocity;
            } else if (accumY > maxVelocity) {
                accumY = maxVelocity;
            }

            pointer.xVelocity = accumX;
            pointer.yVelocity = accumY;
        }
    }

    public float getXVelocity() {
        Pointer pointer = getPointer(0);
        return pointer != null ? pointer.xVelocity : 0;
    }

    public float getYVelocity() {
        Pointer pointer = getPointer(0);
        return pointer != null ? pointer.yVelocity : 0;
    }

    public float getXVelocity(int id) {
        Pointer pointer = getPointer(id);
        return pointer != null ? pointer.xVelocity : 0;
    }

    public float getYVelocity(int id) {
        Pointer pointer = getPointer(id);
        return pointer != null ? pointer.yVelocity : 0;
    }

    private Pointer getPointer(int id) {
        for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) {
            if (pointer.id == id) {
                return pointer;
            }
        }
        return null;
    }

    private static Pointer obtainPointer() {
        if (sRecycledPointerCount != 0) {
            Pointer element = sRecycledPointerListHead;
            sRecycledPointerCount -= 1;
            sRecycledPointerListHead = element.next;
            element.next = null;
            return element;
        }
        return new Pointer();
    }

    private static void releasePointer(Pointer pointer) {
        if (sRecycledPointerCount < POINTER_POOL_CAPACITY) {
            pointer.next = sRecycledPointerListHead;
            sRecycledPointerCount += 1;
            sRecycledPointerListHead = pointer;
        }
    }

    private static void releasePointerList(Pointer pointer) {
        if (pointer != null) {
            int count = sRecycledPointerCount;
            if (count >= POINTER_POOL_CAPACITY) {
                return;
            }

            Pointer tail = pointer;
            for (;;) {
                count += 1;
                if (count >= POINTER_POOL_CAPACITY) {
                    break;
                }

                Pointer next = tail.next;
                if (next == null) {
                    break;
                }
                tail = next;
            }

            tail.next = sRecycledPointerListHead;
            sRecycledPointerCount = count;
            sRecycledPointerListHead = pointer;
        }
    }
}

And this is second class

import java.util.concurrent.atomic.AtomicReference;
import javafx.animation.FadeTransition;
import javafx.animation.Interpolator;
import javafx.animation.Timeline;
import javafx.animation.TranslateTransition;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Bounds;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;

public class VerticlePane extends Region {

    private ObjectProperty<Node> content;
    private final VelocityTracker tracker = new VelocityTracker();
    private final Rectangle clipRect = new Rectangle();
    private Rectangle scrollNode;
    private boolean magnet, autoTranslate = true;
    private DoubleProperty targetY = new SimpleDoubleProperty(), controlHeight = new SimpleDoubleProperty(), viewHeight = new SimpleDoubleProperty();
    private double startValue, endValue;
    private HBox indicator;
    private StringProperty indicatorValue = new SimpleStringProperty("Pull Up to Update...");

    public VerticlePane() {
        setFocusTraversable(false);
        setClip(clipRect);
        contentProperty().addListener((ObservableValue<? extends Node> observable, Node oldValue, Node newValue) -> {
            getChildren().clear();
            if (newValue != null) {
                getChildren().addAll(newValue, createRectangle());
            }
            layout();
        });

        final AtomicReference<MouseEvent> deltaEvent = new AtomicReference<>();
        final AtomicReference<TranslateTransition> currentTransition1 = new AtomicReference<>();
        final AtomicReference<TranslateTransition> currentTransition2 = new AtomicReference<>();
        final AtomicReference<Timeline> timeline = new AtomicReference<>();

        setOnMousePressed((me) -> {
            tracker.addMovement(me);
            deltaEvent.set(me);
            startValue = me.getY();

            if (currentTransition1.get() != null) {
                currentTransition1.get().stop();
            }
            if (currentTransition2.get() != null) {
                currentTransition2.get().stop();
            }
            if (timeline.get() != null) {
                timeline.get().stop();
            }
        });

        setOnMouseDragged((event) -> {
            tracker.addMovement(event);
            double delta = event.getY() - deltaEvent.get().getY();
            targetY.set(content.get().getTranslateY() + delta);
            content.get().setTranslateY(targetY.get());

            controlHeight.set(content.get().getLayoutBounds().getHeight());
            viewHeight.set(getHeight());
            double scrollTargetY = targetY.divide(controlHeight.divide(viewHeight.get()).get()).get();
            double lastInstanceHeight = controlHeight.subtract(viewHeight.get()).multiply(-1).get();

            scrollNode.relocate(1.0, (getHeight() - 5.0));
            scrollNode.setArcHeight(5.0);
            scrollNode.setArcWidth(5.0);
            scrollNode.setHeight(viewHeight.divide(controlHeight.divide(viewHeight.get()).get()).get());
            scrollNode.setVisible(true);
            scrollNode.setOpacity(0.25);

            if (targetY.lessThan(0).and(targetY.greaterThan(lastInstanceHeight)).get()) {
                scrollNode.setTranslateY(Math.abs(scrollTargetY));
            } else if (targetY.greaterThanOrEqualTo(0).get()) {
                scrollNode.setHeight(scrollNode.getHeight() - targetY.get());
            }
            if (targetY.get() < lastInstanceHeight) {
                double scrollNodeHeight = scrollNode.getHeight() - (lastInstanceHeight - targetY.get());
                double scrollNodeTranslateY = viewHeight.subtract(viewHeight.divide(controlHeight.divide(viewHeight.get()).get()).get()).add(lastInstanceHeight - targetY.get()).get();
                scrollNode.setHeight(scrollNodeHeight);
                scrollNode.setTranslateY(scrollNodeTranslateY);
            }
            deltaEvent.set(event);
        });

        setOnMouseReleased((me) -> {

            FadeTransition ft = new FadeTransition(Duration.millis(300), scrollNode);
            ft.setFromValue(0.25);
            ft.setToValue(0.00);

            tracker.addMovement(me);
            tracker.computeCurrentVelocity(500);
            endValue = me.getY();

            controlHeight.set(content.get().getLayoutBounds().getHeight());
            viewHeight.set(getHeight());
            float velocityY = tracker.getYVelocity();

            targetY.set(scrollNode(velocityY, currentTransition1, content.get()));
            TranslateTransition tt = bb(currentTransition2, scrollNode, Math.abs((targetY.divide(controlHeight.divide(viewHeight.get()).get()).get())));
            tt.setOnFinished((ae) -> {
                ft.play();
            });
        });
    }

    public double getStartValue() {
        return startValue;
    }

    public double getEndValue() {
        return endValue;
    }

    public double getControlHeight() {
        return controlHeight.get();
    }

    public double getViewHeight() {
        return viewHeight.get();
    }

    public VerticlePane(double prefWidth, double prefHeight) {
        this();
        setPrefSize(prefWidth, prefHeight);
    }

    public VerticlePane(double prefWidth, double prefHeight, boolean magnet) {
        this(prefWidth, prefHeight);
        setMagnet(magnet);
    }

    public final void setMagnet(boolean magnet) {
        this.magnet = magnet;
    }

    private TranslateTransition bb(AtomicReference<TranslateTransition> currentTransition, Node node, Double targetY) {

        TranslateTransition translate = new TranslateTransition(new Duration(1000), node);
        if (node != null) {
            translate.setInterpolator(Interpolator.SPLINE(0.0513, 0.1131, 0.1368, 1.0000));

            if (targetY != null) {
                translate.setFromY(node.getTranslateY());
                translate.setToY(targetY);
            }

            translate.play();
            currentTransition.set(translate);

        }
        return translate;

    }

    private Double scrollNode(float velocityY, AtomicReference<TranslateTransition> currentTransition, Node node) {

        final Bounds b = node.getLayoutBounds();
        Double backBouncingY = null;
        Double targetY = null;

        if (node != null) {
            TranslateTransition translate = new TranslateTransition(new Duration(1000), node);
            translate.setInterpolator(Interpolator.SPLINE(0.0513, 0.1131, 0.1368, 1.0000));
            if (Math.abs(velocityY) < 10) {
                velocityY = 0;
            }

            targetY = node.getTranslateY();

            double controlHeight = b.getHeight() - indicator.heightProperty().add(10).get();
            double viewHeight = getHeight();

            if (controlHeight < viewHeight && targetY < controlHeight) {
                targetY = 0.0;
            } else if (targetY > 0) {
                targetY = 0.0;
            } else if ((targetY < (controlHeight - viewHeight) * -1)) {
                targetY = (controlHeight - viewHeight) * -1;
            } else {
                targetY += velocityY;

                if (controlHeight < viewHeight && targetY < controlHeight) {
                    targetY = -25.0;
                    backBouncingY = 0.0;
                } else if (targetY > 0) {
                    targetY = 25.0;
                    backBouncingY = 0.0;
                } else if (targetY < (controlHeight - viewHeight) * -1) {
                    targetY = (controlHeight - viewHeight) * -1 - 25;
                    backBouncingY = (controlHeight - viewHeight) * -1;
                }

            }

//Magnet
            if (magnet) {
                double dragRegion = (viewHeight / 2) * -1;
                double instances = controlHeight / viewHeight;
                double lastInstance = ((controlHeight - viewHeight) * -1);
                double newInstanceValue = dragRegion;

                if (targetY > dragRegion) {
                    targetY = 0.0;
                }

                if (instances > 1) {
                    for (int i = 1; i < instances - 1; i++) {

                        double instanceValue = (viewHeight * i) * -1;
                        if (targetY < newInstanceValue && targetY > instanceValue || targetY < instanceValue && targetY > (instanceValue + dragRegion)) {
                            targetY = instanceValue;
                        }
                        newInstanceValue += (viewHeight * -1);
                    }
                }

                if (targetY < (lastInstance - dragRegion) && targetY > lastInstance) {
                    targetY = lastInstance;
                }
            }
//
            if (targetY != null) {
                translate.setFromY(node.getTranslateY());
                translate.setToY(targetY);
            }

            if (backBouncingY != null) {
                final Double fbackFlingY = backBouncingY;
                translate.setOnFinished((ae) -> {
                    currentTransition.set(null);
                    TranslateTransition translate1 = new TranslateTransition(new Duration(300), node);
                    if (fbackFlingY != null) {
                        translate1.setFromY(node.getTranslateY());
                        translate1.setToY(fbackFlingY);
                    }
                    translate1.play();
                    currentTransition.set(translate1);
                });
            } else {
                translate.setOnFinished((ae) -> {
                    currentTransition.set(null);
                });
            }

            translate.play();
            currentTransition.set(translate);
        }
        return targetY;
    }

    private Rectangle createRectangle() {
        if (scrollNode == null) {
            scrollNode = new Rectangle(4.0, 4.0, Color.BLACK);
        }
        scrollNode.setVisible(false);
        return scrollNode;
    }

    public ObjectProperty<Node> contentProperty() {
        if (content == null) {
            content = new SimpleObjectProperty<>(this, "content");
        }
        return content;
    }

    public final void setContent(Node value) {

        contentProperty().set(
                new VBox(10) {
                    {
                        getChildren().addAll(value, indicator());
                    }
                }
        );
        contentProperty().get().layoutBoundsProperty().addListener((ObservableValue<? extends Bounds> obs, Bounds ov, Bounds nv) -> {
            Double containerHeight = nv.getHeight();
            Double flingHeight = prefHeightProperty().get();
            if (autoTranslate) {
                if (flingHeight <= containerHeight) {
                    contentProperty().get().setTranslateY((containerHeight - flingHeight - 34) * -1);
                }
            }
        });
    }

    private HBox indicator() {
        return indicator = new HBox(10) {
            {
                setVisible(false);
                setAlignment(Pos.CENTER);
                getChildren().addAll(
                        new ImageView(new Image(getClass().getResourceAsStream("Indicator.gif"))),
                        new Label() {
                            {
                                textProperty().bind(indicatorValue);
                                setStyle("-fx-font-family: Roboto; -fx-font-size: 14px;");
                            }
                        }
                );
            }
        };
    }

    public void setAutoTranslate(Boolean autoTranslate) {
        this.autoTranslate = autoTranslate;
    }

    public void setTranslateZero() {
        this.contentProperty().get().setTranslateY(0);
    }

    public HBox getIndicator() {
        return indicator;
    }

    public void showIndicator() {
        indicator.setVisible(true);
    }

    public void hideIndicator() {
        indicator.setVisible(false);
    }

    public void setIndicatorValue(String value) {
        indicatorValue.setValue(value);
    }

    public final Node getContent() {
        return content == null ? null : content.get();
    }

    @Override
    protected void layoutChildren() {
        super.layoutChildren();
        clipRect.setWidth(getWidth());
        clipRect.setHeight(getHeight());
    }
}
like image 103
Hiran Avatar answered Sep 24 '22 19:09

Hiran