Рисование на Canvas
Обычно интерфейс JavaFX собирают из узлов: каждая кнопка и надпись — отдельный объект, который сам себя рисует. Холст Canvas устроен иначе: это один узел-картинка, на которой вы рисуете командами — «залей прямоугольник», «проведи линию», «напиши текст». Команды отдаёт объект GraphicsContext. Холст подходит для графиков своего вида, игр и рисования мышью. Сделаем для записной книжки окно «Набросок», где можно быстро нарисовать схему.
Первый рисунок
Создайте класс окна. Первый вариант рисует несколько фигур:
// src/main/java/com/example/notes/SketchWindow.java — первый вариант
package com.example.notes;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.stage.Window;
public class SketchWindow {
public void show(Window owner) {
Canvas canvas = new Canvas(400, 200);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.web("#4f6bed"));
gc.fillRect(20, 20, 120, 80);
gc.setStroke(Color.web("#d9534f"));
gc.setLineWidth(4);
gc.strokeOval(170, 20, 80, 80);
gc.strokeLine(20, 150, 380, 150);
gc.setFill(Color.BLACK);
gc.setFont(Font.font(20));
gc.fillText("Набросок", 270, 70);
Stage stage = new Stage();
stage.setTitle("Набросок");
stage.initOwner(owner);
stage.setScene(new Scene(new StackPane(canvas)));
stage.show();
}
}В нижний ряд main-view.fxml добавьте <Button text="Набросок" onAction="#showSketch"/>, а в контроллер — метод:
// src/main/java/com/example/notes/MainController.java — новый метод showSketch()
@FXML
private void showSketch() {
new SketchWindow().show(table.getScene().getWindow());
}Нажмите «Набросок»: в окне синий прямоугольник, красная окружность, надпись «Набросок» справа от неё и красная линия внизу.
Координаты и цвета
Точка (0, 0) — левый верхний угол холста, X растёт вправо, Y — вниз. Методы fill... закрашивают фигуру цветом из setFill, методы stroke... рисуют контур цветом из setStroke и толщиной из setLineWidth. Цвет задают константой Color.BLACK, строкой Color.web("#4f6bed") или Color.rgb(79, 107, 237).
| Метод | Что рисует |
|---|---|
fillRect(x, y, w, h), strokeRect(...) | Прямоугольник |
fillOval(x, y, w, h), strokeOval(...) | Овал, вписанный в прямоугольник |
strokeLine(x1, y1, x2, y2) | Отрезок |
fillText(text, x, y) | Текст; y — линия, на которой стоят буквы |
clearRect(x, y, w, h) | Стирает область до прозрачной |
Рисование мышью
Холст не помнит, что на нём нарисовано: это просто пиксели. Чтобы отменять линии, будем хранить их в списке и перерисовывать всё заново. Полная версия класса:
// src/main/java/com/example/notes/SketchWindow.java
package com.example.notes;
import java.util.ArrayList;
import java.util.List;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.ColorPicker;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.StrokeLineCap;
import javafx.stage.Stage;
import javafx.stage.Window;
public class SketchWindow {
private record Line(Color color, List<Point2D> points) {
}
private final Canvas canvas = new Canvas(500, 320);
private final GraphicsContext gc = canvas.getGraphicsContext2D();
private final ColorPicker colorPicker = new ColorPicker(Color.web("#4f6bed"));
private final List<Line> lines = new ArrayList<>();
public void show(Window owner) {
canvas.setOnMousePressed(event -> {
Line line = new Line(colorPicker.getValue(), new ArrayList<>());
line.points().add(new Point2D(event.getX(), event.getY()));
lines.add(line);
});
canvas.setOnMouseDragged(event -> {
lines.getLast().points().add(new Point2D(event.getX(), event.getY()));
redraw();
});
Button undo = new Button("Отменить");
undo.setOnAction(event -> {
if (!lines.isEmpty()) {
lines.removeLast();
redraw();
}
});
Button clear = new Button("Очистить");
clear.setOnAction(event -> {
lines.clear();
redraw();
});
VBox root = new VBox(8, new HBox(8, colorPicker, undo, clear), canvas);
root.setPadding(new Insets(16));
redraw();
Stage stage = new Stage();
stage.setTitle("Набросок");
stage.initOwner(owner);
stage.setScene(new Scene(root));
stage.show();
}
private void redraw() {
gc.setFill(Color.WHITE);
gc.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
gc.setLineWidth(3);
gc.setLineCap(StrokeLineCap.ROUND);
for (Line line : lines) {
gc.setStroke(line.color());
gc.beginPath();
Point2D first = line.points().getFirst();
gc.moveTo(first.getX(), first.getY());
for (Point2D point : line.points()) {
gc.lineTo(point.getX(), point.getY());
}
gc.stroke();
}
gc.setFill(Color.GRAY);
gc.fillText("Линий: " + lines.size(), 10, canvas.getHeight() - 10);
}
}Нажатие кнопки мыши начинает новую линию, каждое движение с зажатой кнопкой добавляет точку и вызывает redraw(). Координаты event.getX() и event.getY() уже отсчитаны от угла холста. redraw() заливает холст белым и заново проводит все линии: beginPath начинает контур, moveTo ставит перо, lineTo ведёт его дальше, stroke рисует. ColorPicker — стандартный выбор цвета.
Нарисуйте синюю линию, выберите красный цвет и нарисуйте вторую — внизу появится «Линий: 2». «Отменить» убирает последнюю линию, «Очистить» — все.
Частая ошибка
Перед strokeLine задают цвет через setFill(Color.RED) и ждут красную линию, а она выходит чёрной. Ошибки нет: линии, контуры и stroke() берут цвет из setStroke, а setFill влияет только на методы fill... и текст. По умолчанию обводка чёрная, заливка тоже.