package canvastest; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; import javafx.application.Application; import javafx.beans.InvalidationListener; import javafx.beans.Observable; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.shape.StrokeLineCap; import javafx.scene.shape.StrokeLineJoin; import javafx.stage.Stage; import javafx.util.Duration; public class CanvasDashTest extends Application { public static void main(String argv[]) { launch(); } static double[] d(double... d) { return d; } DoubleProperty dashOffsetProp = new SimpleDoubleProperty(); @Override public void start(Stage stage) { VBox vb = new VBox(10); addDashTest(vb, null, 5, StrokeLineCap.BUTT, StrokeLineJoin.ROUND); addDashTest(vb, d(10, 10), 5, StrokeLineCap.BUTT, StrokeLineJoin.ROUND); addDashTest(vb, d(10, 10), 5, StrokeLineCap.ROUND, StrokeLineJoin.ROUND); addDashTest(vb, d(10, 10), 5, StrokeLineCap.SQUARE, StrokeLineJoin.ROUND); stage.setScene(new Scene(vb)); stage.show(); KeyValue kv = new KeyValue(dashOffsetProp, -1000); KeyFrame kf = new KeyFrame(Duration.seconds(10), kv); Timeline t = new Timeline(kf); t.setCycleCount(Timeline.INDEFINITE); t.play(); } void addDashTest(Pane parent, double dashes[], double linewidth, StrokeLineCap cap, StrokeLineJoin join) { HBox hb = new HBox(10); hb.getChildren().add(new DashRenderer(CanvasDashTest::renderLine) .init(dashes, linewidth, cap, join)); hb.getChildren().add(new DashRenderer(CanvasDashTest::renderCircle) .init(dashes, linewidth, cap, join)); hb.getChildren().add(new DashRenderer(CanvasDashTest::renderRect) .init(dashes, linewidth, cap, join)); hb.getChildren().add(new DashRenderer(CanvasDashTest::renderHourglass) .init(dashes, linewidth, cap, join)); hb.getChildren().add(new DashRenderer(CanvasDashTest::renderBeziers) .init(dashes, linewidth, cap, join)); parent.getChildren().add(hb); } static interface ShapeRenderer { void renderShape(GraphicsContext gc); } static void renderLine(GraphicsContext gc) { gc.strokeLine(10, 10, 90, 90); } static void renderRect(GraphicsContext gc) { gc.strokeRect(10, 10, 80, 80); } static void renderCircle(GraphicsContext gc) { gc.strokeOval(10, 10, 80, 80); } static void renderHourglass(GraphicsContext gc) { gc.beginPath(); gc.moveTo(10, 10); gc.lineTo(90, 10); gc.lineTo(10, 90); gc.lineTo(90, 90); gc.closePath(); gc.stroke(); } static void renderBeziers(GraphicsContext gc) { gc.beginPath(); gc.moveTo(10, 10); gc.quadraticCurveTo(10, 90, 90, 90); gc.bezierCurveTo(40, -60, 160, 60, 10, 10); gc.closePath(); gc.stroke(); } class DashRenderer { Canvas canvas; GraphicsContext gc; ShapeRenderer renderer; DashRenderer(ShapeRenderer renderer) { this.renderer = renderer; dashOffsetProp.addListener(new InvalidationListener() { @Override public void invalidated(Observable o) { render(); } }); } Node init(double dashes[], double linewidth, StrokeLineCap cap, StrokeLineJoin join) { this.canvas = new Canvas(100, 100); this.gc = canvas.getGraphicsContext2D(); gc.setLineWidth(linewidth); gc.setLineCap(cap); gc.setLineJoin(join); gc.setLineDashes(dashes); gc.setFill(Color.WHITE); gc.setStroke(Color.BLUE); render(); return canvas; } void render() { gc.fillRect(0, 0, canvas.getWidth(), canvas.getHeight()); gc.setLineDashOffset(dashOffsetProp.get()); renderer.renderShape(gc); } } }