Я хочу нарисовать вектор по частям, перерисовывая вектор каждый раз, когда добавляется новая часть. У меня есть строка, содержащая каждую часть пути для финального svg, который зацикливается (обозначается знаком «;» в строке). Кроме того, я добавил strokeLine в качестве своего рода индикатора выполнения, чтобы отслеживать, какая часть вектора нарисована.
public void renderObject(GraphicsContext playGraphics, Canvas toUpdate)
{
playGraphics.beginPath();
String toAppend = "M 215 256; L 215 256; L 215 256; L 215 256; L 225 241; L 234 231; L 246 223; L 266 214; L 284 208; L 309 204; L 340 200; L 378 199; L 416 199; L 444 199; L 473 203; L 485 206; L 496 211; L 506 218; L 510 224; L 513 233; L 515 243; L 516 257; L 512 270; L 502 285; L 493 298; L 483 308; L 476 315; L 472 318; L 469 320; L 468 320; L 468 320; L 468 320; L 468 317; L 472 309; L 480 300; L 492 293; L 510 287; L 535 283; L 557 282; L 580 283; L 593 287; L 607 295; L 623 311; L 634 333; L 640 355; L 642 396; L 639 430; L 624 467; L 602 508; L 582 536; L 557 563; L 524 585; L 490 602; L 464 611; L 432 619; L 420 621; L 404 622; L 393 622; L 383 621; L 376 620; L 372 618; L 365 610; L 360 598; L 358 578; L 357 554; L 361 514; L 371 493; L 386 463; L 412 422; L 432 395; L 456 362; L 480 329; L 506 299; L 533 271; L 560 247; L 600 213; L 620 194; L 626 189; L 629 184; L 630 182; L 632 178";
for (int i=0; i < toAppend.split(";").length; i++)
{
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
playGraphics.clearRect(0, 0, toUpdate.getWidth(), toUpdate.getHeight());
playGraphics.strokeLine(50, 50, 50+10*i, 50);
playGraphics.appendSVGPath(toAppend.split(";")[i]);
playGraphics.stroke();
}
}
Однако после реализации на живом холсте вектор не рисуется, и отображается только индикатор выполнения. Это довольно странно, учитывая, что после завершения рендеринга векторное изображение может быть полностью отображено на холсте, если объект graphicsContext используется снова и вызывается другой playGraphics.stroke();.
Почему playGraphics.stroke(); не запускается, а playGraphics.strokeLine работает нормально?
Индикатор выполнения, показывающий, что рендеринг происходит, но вектор не отображается
Индикатор выполнения, показывающий, что рендеринг происходит, но вектор не отображается
Вектор, наконец, отображается, но только после того, как точка была нарисована на холсте, повторно вызывая playGraphics.stroke();




Похоже, пути SVG каким-то образом удаляются из текущего пути, когда вы вызываете stroke() (после чего они не могут быть добавлены к текущему пути). Если бы вы вызывали stroke() только после завершения цикла (что, как я понимаю, подорвало бы эффект анимации и, следовательно, не является решением), ваш рисунок выглядел бы так, как ожидалось.
Это осложняется тем фактом, что ваши спящие вызовы игнорируют правила многопоточности JavaFX. Ваш метод renderObject вызывается в потоке приложения JavaFX?
sleep препятствуют обработке событий, в том числе рисованию.… Once a
Canvasnode is attached to a scene, it must be modified on the JavaFX Application Thread.Calling any method on the
GraphicsContextis considered modifying its correspondingCanvasand is subject to the same threading rules.
Как только ваш Canvas отображается, вы должны сделать эти вызовы GraphicsContext в потоке приложения. Но вы не можете спать в потоке приложения; это приводит к зависанию всех событий графического интерфейса, включая все визуальные обновления и все ответы на ввод пользователя.
Есть несколько способов правильно реализовать многопоточность. Проще всего создать один новый поток, в котором разрешены вызовы sleep, при этом убедитесь, что ваши вызовы Canvas GraphicsContext происходят в потоке приложения JavaFX с использованием Platform.runLater.
Некоторые другие примечания:
toAppend.split(";") это не переменная, это дорогой операция. И вы повторяете эту операцию дважды в каждой итерации цикла! Вы должны вызвать split(";")однажды до начала цикла и сохранить возвращаемый массив в переменной. (Возможно, JVM сможет выполнить эту оптимизацию во время выполнения, но это не гарантируется.)split; вы можете поместить все команды SVG в массив, который имеет дополнительное преимущество, заключающееся в том, что его легче читать.Итак, имея в виду все вышеперечисленное, вы хотите что-то вроде этого:
public void renderObject(GraphicsContext playGraphics, Canvas toUpdate)
{
if (!Platform.isFxApplicationThread())
{
throw new IllegalStateException(
"Must be called in JavaFX application thread");
}
String[] toAppend = {
"M 215 256", "L 215 256", "L 215 256", "L 215 256",
"L 225 241", "L 234 231", "L 246 223", "L 266 214",
"L 284 208", "L 309 204", "L 340 200", "L 378 199",
"L 416 199", "L 444 199", "L 473 203", "L 485 206",
"L 496 211", "L 506 218", "L 510 224", "L 513 233",
"L 515 243", "L 516 257", "L 512 270", "L 502 285",
"L 493 298", "L 483 308", "L 476 315", "L 472 318",
"L 469 320", "L 468 320", "L 468 320", "L 468 320",
"L 468 317", "L 472 309", "L 480 300", "L 492 293",
"L 510 287", "L 535 283", "L 557 282", "L 580 283",
"L 593 287", "L 607 295", "L 623 311", "L 634 333",
"L 640 355", "L 642 396", "L 639 430", "L 624 467",
"L 602 508", "L 582 536", "L 557 563", "L 524 585",
"L 490 602", "L 464 611", "L 432 619", "L 420 621",
"L 404 622", "L 393 622", "L 383 621", "L 376 620",
"L 372 618", "L 365 610", "L 360 598", "L 358 578",
"L 357 554", "L 361 514", "L 371 493", "L 386 463",
"L 412 422", "L 432 395", "L 456 362", "L 480 329",
"L 506 299", "L 533 271", "L 560 247", "L 600 213",
"L 620 194", "L 626 189", "L 629 184", "L 630 182",
"L 632 178"
};
// If the SVG string is not hard-coded, create the array here:
// String[] toAppend = svgString.split(";");
Runnable pathBuilder = () -> {
try
{
StringBuilder path = new StringBuilder();
for (String segment : toAppend)
{
Thread.sleep(100);
path.append(" ").append(segment);
String pathToDraw = path.toString();
Platform.runLater(() -> {
playGraphics.beginPath();
playGraphics.appendSVGPath(pathToDraw);
playGraphics.stroke();
});
}
}
catch (InterruptedException e)
{
// Someone wants us to exit, so fall through and return.
e.printStackTrace();
}
};
Thread pathBuildingThread = new Thread(pathBuilder);
pathBuildingThread.setDaemon(true);
pathBuildingThread.start();
}
Но решение, которое мне нужно, основано на перерисовке всего объекта по 1 пути.
Я согласен, что это похоже на ошибку. На мой взгляд, gc.appendSVGPath("M 100 100 L 200 200"); должен функционировать идентично gc.moveTo(100, 100); gc.lineTo(200, 200);.
Вы правы в том, что проблема заключается в вызове beginStroke один раз, а не в каждой итерации. Я считаю, что это ошибка JavaFx, потому что она отлично работает, если вы используете формальные методы moveTo и lineTo.