JavaFX如何沿给定的SVG路径拖动对象 - 像滑块一样

时间:2015-11-27 16:37:15

标签: svg javafx slider javafx-2 javafx-8

我想在JavaFX中沿给定的SVG路径拖动一个圆圈或窗格。它应该像滑块一样工作。因此,如果圆圈位于路径的开头,那么"滑块"应该返回0,如果用户拖动圆圈到最后,"滑块"应该返回100。

参见附图: Link to image

路径应该是具有开头和结尾的任何SVG路径。

我找到了这个例子,但我无法转换"它是JavaFX。

Link to example

var D = document.createElement('div');
TweenMax.set('svg',{overflow:"visible"})
TweenMax.set('.knob',{x:10,y:80})

var tl = new TimelineMax({paused:true})
.from("#path2",1,{drawSVG:"0%",stroke:'orange',ease:Linear.easeNone})
.to('.knob',1,{bezier:{type:"quadratic",values:[{x:10,y:80},{x:150,y:0},{x:300,y:80}]},ease:Linear.easeNone},0);

Draggable.create(D,{trigger:".knob",
type:'x',
throwProps:true,
bounds:{minX:0,maxX:300},
onDrag:Update,
onThrowUpdate:Update});   
function Update(){tl.progress(Math.abs(this.x/300))};

TweenMax.to('#path1',0.5,{strokeDashoffset:-10,repeat:-1,ease:Linear.easeNone})

我感谢任何帮助。 THX。

2 个答案:

答案 0 :(得分:2)

我从JörnHameister那里找到了这个非常有用的例子,他使用Apache Batik Library来解析SVG路径。 JavaFX - Alien Rallye with SVGPath and PathTransition

通过他的JavaFXPathElementHandler,我能够将SVG路径转换为JavaFX路径。

我创建了一个长达500秒的路径动画。圆的所有位置(x,y) - 沿着这条路径移动 - 都保存在一个数组中。

拖动圆时,函数会搜索与阵列中鼠标距离最短的点。此位置的索引是动画中的时间(以秒为单位),然后显示。

此解决方案适用于我。也许有很多优化潜力,但就我而言,这已经足够了。

您需要以下图书馆: 蜡染parser.jar是 蜡染util.jar

他们可以在这里找到: Apache Batik Library

<强> SVGPathSlider.java

package svgpathslider;

import javafx.scene.paint.Color;
import java.util.ArrayList;
import java.util.List;
import javafx.animation.PathTransition;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
import javafx.util.Duration;

import org.apache.batik.parser.PathParser;


public class SVGPathSlider extends Application {

    private final static double WIDTH = 1488;
    private final static double HEIGHT = 840;

    private final static int SLIDER_MIN = 0;
    private final static int SLIDER_MAX = 100;

    private final static double ANIMATION_DURATION = 500.0d;

    private double _initX;
    private double _initY;
    private Point2D _dragAnchor;

    private PathTransition _pathTransition;
    private Circle _circle;

    private List<Point2D> _pathPointList;

    private int _actIndex;

    private int _sliderIndex = 0;

    @Override
    public void start(Stage primaryStage) {
        Pane root = new Pane();

        // Parse the SVG Path with Apache Batik and create a Path
        PathParser parser = new PathParser();
        JavaFXPathElementHandler handler = new JavaFXPathElementHandler("track");
        parser.setPathHandler(handler);

        // SVG Path
        parser.parse("M1411.789,59.381c-32.315,6.982-60.416,21.861-85.014,43.752c-19.922,17.731-35.061,38.869-45.943,63.058\n" +
"       c-5.333,11.853-9.46,24.204-12.018,36.946c-1.175,5.859-2.027,11.836-2.615,17.849c-0.784,8.039-1.604,16.047-1.405,24.125\n" +
"       c0.544,22.257,4.783,43.825,12.943,64.524c3.956,10.034,8.61,19.806,13.214,29.573c10.041,21.306,20.268,42.525,30.387,63.795\n" +
"       c5.109,10.741,10.271,21.46,15.19,32.288c7.075,15.575,14.958,30.839,19.51,47.438c3.979,14.508,6.568,29.219,7.169,44.322\n" +
"       c0.331,8.324,0.435,16.646-0.246,24.884c-1.532,18.565-5.443,36.725-12.43,54.038c-8.359,20.721-19.532,39.866-34.275,56.79\n" +
"       c-11.257,12.923-24.016,24.286-38.271,33.754c-15.338,10.186-31.729,18.501-49.484,23.784c-9.592,2.853-19.303,5.104-29.216,6.816\n" +
"       c-15.327,2.646-30.695,2.349-46.054,2.007c-9.38-0.208-18.705-2.263-28.096-2.838c-6.393-0.393-12.754-1.123-19.09-1.821\n" +
"       c-8.197-0.902-16.423-1.712-24.613-2.781c-5.534-0.722-11.162-1.225-16.739-1.823c-5.861-0.631-11.759-1.022-17.578-2.167\n" +
"       c-2.655-0.522-5.449-0.437-8.188-0.75c-8.846-1.013-17.698-1.924-26.519-3.171c-5.554-0.786-11.189-0.973-16.764-1.633\n" +
"       c-7.814-0.928-15.595-2.138-23.412-3.036c-2.847-0.327-5.725-0.423-8.588-0.699c-8.938-0.864-17.943-1.748-26.899-1.554\n" +
"       c-15.911,0.347-31.625,2.248-47.251,5.876c-15.535,3.606-31.159,6.96-46.725,10.469c-14.081,3.174-28.128,6.604-42.203,9.777\n" +
"       c-16.611,3.743-33.229,7.521-49.856,11.208c-9.739,2.16-19.507,4.166-29.301,5.996c-5.747,1.073-11.674,1.388-17.537,1.594\n" +
"       c-7.402,0.261-14.82,0.113-22.231,0.052c-15.293-0.127-30.188-2.85-44.857-6.989c-21.598-6.095-40.763-16.693-58.57-30.253\n" +
"       c-27.463-20.914-55.199-41.478-83.072-61.846c-15.905-11.621-34.119-18.604-53.016-23.727c-9.574-2.596-19.33-4.402-29.226-5.603\n" +
"       c-11.292-1.37-22.576-2.289-33.947-2.248c-11.527,0.041-22.741,2.374-33.923,4.73c-11.105,2.342-22.015,5.769-33.198,7.525\n" +
"       c-9.26,1.455-18.411,3.436-27.664,4.864");

        Path path = handler.getPath();
        root.getChildren().add(path);

        // Moving image
        _circle = new Circle(20);
        _circle.setFill(Color.RED);

        root.getChildren().add(_circle);

        // Path Transition
        _pathTransition = new PathTransition();
        _pathTransition.setDuration(Duration.seconds(ANIMATION_DURATION));
        _pathTransition.setPath(path);
        _pathTransition.setNode(_circle);
        _pathTransition.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT);
        _pathTransition.setCycleCount(1);
        _pathTransition.playFromStart();
        _pathTransition.pause();
        _pathTransition.jumpTo("end");

        // Save the circle positions on the path
        _pathPointList = new ArrayList<>();
        savePositions();


        // Mouse presssed handler
        _circle.setOnMousePressed(new EventHandler<MouseEvent>() {
            public void handle(MouseEvent me) {
                 // Store initial position
                _initX = _circle.getTranslateX();
                _initY = _circle.getTranslateY();
                _dragAnchor = new Point2D(me.getSceneX(), me.getSceneY());                
            }
        });


        // Mouse dragged handler
        _circle.setOnMouseDragged(new EventHandler<MouseEvent>() {
            public void handle(MouseEvent me) {
                double dragX = me.getSceneX() - _dragAnchor.getX();
                double dragY = me.getSceneY() - _dragAnchor.getY();

                // Calculate new position of the circle
                double newXPosition = _initX + dragX;
                double newYPosition = _initY + dragY;

                // Get the nearest index (= second) of the animation
                _actIndex = getAnimationIndex(newXPosition, newYPosition);

                // Show animation at the given second
                _pathTransition.jumpTo(Duration.seconds(_actIndex));                                

                // Get slider index
                _sliderIndex = remap(_actIndex, ANIMATION_DURATION, 0, SLIDER_MIN, SLIDER_MAX);
                System.out.println(_sliderIndex);
            }
        });

        primaryStage.setTitle("JavaFX SVG Path Slider");
        primaryStage.setScene(new Scene(root, WIDTH, HEIGHT));
        primaryStage.show();
    }

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


    /**
     * Save the position of the circle for every second of the animation in
     * a list.
     */
    private void savePositions() {

        if (_pathPointList == null)
            return;

        for (int i=0; i<=(int)ANIMATION_DURATION; i++) {
            _pathTransition.jumpTo(Duration.seconds(i));

            _pathPointList.add(new Point2D(_circle.getTranslateX(), _circle.getTranslateY()));
        }

    }

    /**
     * Returns the index 
     * @param mousePosX
     * @param mousePosY
     * @return 
     */
    private int getAnimationIndex(double mousePosX, double mousePosY) {

        int nearestIndex = 0;

        int i = 0;
        double dx;
        double dy;
        double old_dist = Double.MAX_VALUE;
        double act_dist;

        for (Point2D pathPos : _pathPointList) {

            // Get distance between mouse position and saved position on path
            // with pythagoras
            dx = mousePosX - pathPos.getX();
            dy = mousePosY - pathPos.getY();
            act_dist = Math.sqrt(dx * dx + dy * dy);

            if (act_dist < old_dist) {
                old_dist = act_dist;
                nearestIndex = i;
            }

            i++;            
        }

        return nearestIndex;
    }

    /**
     * Remaps the given value from one to anoter range
     * 
     * @param value
     * @param from1
     * @param to1
     * @param from2
     * @param to2
     * @return 
     */
    private int remap (int value, double from1, double to1, double from2, double to2) {
        double tmp = (value - from1) / (to1 - from1) * (to2 - from2) + from2;        
        return (int)tmp;
    }
}

JavaFXPathElementHandler.java (作者:j。Hameister)

package svgpathslider;

import javafx.scene.shape.ArcTo;
import javafx.scene.shape.ClosePath;
import javafx.scene.shape.CubicCurveTo;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;

import org.apache.batik.parser.ParseException;
import org.apache.batik.parser.PathHandler;

/**
 * 
 * @see PathHandler Interface
 * 
 * http://www.w3.org/TR/SVG/paths.html
 * 
 * @author j.hameister
 *
 */
public class JavaFXPathElementHandler implements PathHandler {

    private Path path = new Path();

    private float lastX = 0;
    private float lastY = 0;

    public JavaFXPathElementHandler(String pathStyling) {
        path.getStyleClass().add(pathStyling);
    }

    @Override
    public void arcAbs(float rx, float ry, float xAxisRotation, boolean largeArcFlag, boolean sweepFlag, float x, float y) throws ParseException {
        lastX = x;
        lastY = y;
        path.getElements().add(new ArcTo(rx, ry, xAxisRotation, x, y, largeArcFlag, sweepFlag));
    }

    @Override
    public void arcRel(float rx, float ry, float xAxisRotation, boolean largeArcFlag, boolean sweepFlag, float x, float y) throws ParseException {
        lastX = x;
        lastY = y;

        path.getElements().add(new ArcTo(rx, ry, xAxisRotation, x, y, largeArcFlag, sweepFlag));
    }

    @Override
    public void closePath() throws ParseException {
        path.getElements().add(new ClosePath());
    }

    @Override
    public void curvetoCubicAbs(float x1, float y1, float x2, float y2, float x, float y) throws ParseException {
        lastX = x;
        lastY = y;

        CubicCurveTo c = new CubicCurveTo(x1,y1,x2,y2,x,y);
        c.setAbsolute(true);
        path.getElements().add(c);
    }

    @Override
    public void curvetoCubicRel(float x1, float y1, float x2, float y2, float x, float y) throws ParseException {
        lastX = x;
        lastY = y;
        CubicCurveTo c = new CubicCurveTo(x1,y1,x2,y2,x,y);
        c.setAbsolute(false);
        path.getElements().add(c);
    }

    @Override
    public void curvetoCubicSmoothAbs(float x2, float y2, float x, float y) throws ParseException {
        throw new ParseException("curvetoCubicSmoothAbs is currently not supported.", null);
    }

    @Override
    public void curvetoCubicSmoothRel(float x2, float y2, float x, float y) throws ParseException {
        throw new ParseException("curvetoCubicSmoothRel is currently not supported.", null);
    }

    @Override
    public void curvetoQuadraticAbs(float x1, float y1, float x, float y) throws ParseException {
        throw new ParseException("curvetoQuadraticAbs is currently not supported.", null);
    }

    @Override
    public void curvetoQuadraticRel(float x1, float y1, float x, float y) throws ParseException {
        throw new ParseException("curvetoQuadraticRel is currently not supported.", null);
    }

    @Override
    public void curvetoQuadraticSmoothAbs(float x, float y) throws ParseException {
        throw new ParseException("curvetoQuadraticSmoothAbs is currently not supported.", null);
    }

    @Override
    public void curvetoQuadraticSmoothRel(float x, float y) throws ParseException {
        throw new ParseException("curvetoQuadraticSmoothRel is currently not supported.", null);
    }

    @Override
    public void endPath() throws ParseException {}

    @Override
    public void linetoAbs(float x, float y) throws ParseException {
        lastX = x;
        lastY = y;

        LineTo lineTo = new LineTo(x,y);
        lineTo.setAbsolute(true);
        path.getElements().add(lineTo);
    }

    @Override
    public void linetoHorizontalAbs(float x) throws ParseException {
        lastX = x;
        LineTo lineTo = new LineTo(x, lastX);
        lineTo.setAbsolute(true);
        path.getElements().add(lineTo);
    }

    @Override
    public void linetoHorizontalRel(float x) throws ParseException {
        lastX = x;
        LineTo lineTo = new LineTo(x, lastY);
        lineTo.setAbsolute(false);
        path.getElements().add(lineTo);
    }

    @Override
    public void linetoRel(float x, float y) throws ParseException {
        lastX = x;
        lastY = y;
        LineTo lineTo = new LineTo(x, y);
        lineTo.setAbsolute(false);
        path.getElements().add(lineTo);
    }

    @Override
    public void linetoVerticalAbs(float y) throws ParseException {
        lastY = y;
        LineTo lineTo = new LineTo(lastX, y);
        lineTo.setAbsolute(true);
        path.getElements().add(lineTo);
    }

    @Override
    public void linetoVerticalRel(float y) throws ParseException {
        lastY = y;
        LineTo lineTo = new LineTo(lastX, y);
        lineTo.setAbsolute(false);
        path.getElements().add(lineTo);
    }

    @Override
    public void movetoAbs(float x, float y) throws ParseException {
        lastX = x;
        lastY = y;
        MoveTo moveTo = new MoveTo(x, y);
        moveTo.setAbsolute(true);
        path.getElements().add(moveTo);
    }

    @Override
    public void movetoRel(float x, float y) throws ParseException {
        lastX = x;
        lastY = y;
        MoveTo moveTo = new MoveTo(x, y);
        moveTo.setAbsolute(false);
        path.getElements().add(moveTo);
    }

    @Override
    public void startPath() throws ParseException {
    }

    public Path getPath() {
        return path;
    }

}

答案 1 :(得分:0)

您可以计算拖动动作在圆上产生的“力”(作为矢量),而不用获取列表中的最近点,并查看此力是否使圆最接近点中的下一个点或上一个点名单。因为仅计算最近的点会导致圆上的传送,具体取决于路径。但是,如果您不介意的话,就可以了。

(我没有50个声誉,只能添加评论,这就是为什么它是一个答案...对不起)