Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Рисование на 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... и текст. По умолчанию обводка чёрная, заливка тоже.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.