DEV Community

HarmonyOS
HarmonyOS

Posted on

Creating a drawable area with canvas

Read the original article:Creating a drawable area with canvas

Creating a drawable area with canvas

Requirement Description

Develop a touch-based drawing application with the following features:

  1. Color Selection: Users can choose between black, red, or blue for drawing.
  2. Drawing Canvas: A touch-responsive canvas to draw freehand paths.
  3. Clear Functionality: A button to erase all drawings.
  4. Path Tracking: Store drawing paths to enable persistent rendering.

Background Knowledge

  • Canvas API: Leverages CanvasRenderingContext2D for drawing operations (e.g., lineTo, stroke).
  • Touch Events:
    • TouchType.Down: Start a new path.
    • TouchType.Move: Update the current path and render segments.
  • State Management: @State variables track paths and color to trigger UI updates.

Implementation Steps

  1. Initialize State Variables:
    • paths: 2D array storing all drawn paths.
    • currentColor: Tracks the selected drawing color.
    • ctx: Canvas rendering context.
  2. Build UI Components:
    • Color Picker Row: Buttons for colors (black, red, blue) and a clear button (C).
    • Canvas: Configured with onReady (setup context) and onTouch (handle drawing).
  3. Handle Touch Events:
    • Touch Down: Start a new path at the touch coordinates.
    • Touch Move: Add points to the latest path and render segments incrementally.
  4. Drawing Logic:
    • drawPath(): Connects points in a path with lines and strokes using the current color.
    • Clear: Reset paths and call clearRect() on the canvas.
  5. Persistent Rendering:
    • Paths are preserved in paths array, enabling re-rendering on UI updates (e.g., color change).

Code Snippet

// DrawingPage.ets
@Entry
@Component
struct DrawingPage {
  @State paths: Array<Array<Point>> = []; // Store all paths
  @State currentColor: string = 'black';
  private settings: RenderingContextSettings = new RenderingContextSettings(true)
  private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)

  build() {
    Column() {
      // Color Picker
      Row() {
        Button().onClick(() => this.currentColor = 'black').backgroundColor(Color.Black).borderRadius(0)
        Button().onClick(() => this.currentColor = 'red').backgroundColor(Color.Red).borderRadius(0)
        Button().onClick(() => this.currentColor = 'blue').backgroundColor(Color.Blue).borderRadius(0)
        Button('C').onClick(() => {
          this.ctx?.clearRect(0, 0, 1000, 1000);
          this.paths = [];
        }).borderRadius(0)
      }
      .padding(15)


      // Drawing Canvas
      Canvas(this.ctx)
        .width('100%')
        .height(500)
        .backgroundColor('#f0f0f0')
        .onReady(() => {
          this.ctx = this.ctx as CanvasRenderingContext2D;
          this.ctx.lineWidth = 3;
          this.ctx.lineCap = 'round';
        })
        .onTouch((event: TouchEvent) => {
          const touch = event.touches[0];
          if (!this.ctx) return;

          switch (event.type) {
            case TouchType.Down:
              this.paths.push([{ x: touch.x, y: touch.y }]);
              break;
            case TouchType.Move:
              if (this.paths.length > 0) {
                const currentPath = this.paths[this.paths.length - 1];
                currentPath.push({ x: touch.x, y: touch.y });
                this.drawPath(currentPath);
              }
              break;
          }
        })

    }
  }

  // Draw a single path segment
  private drawPath(path: Array<Point>) {
    if (!this.ctx || path.length < 2) return;
    this.ctx.strokeStyle = this.currentColor;
    this.ctx.beginPath();
    this.ctx.moveTo(path[0].x, path[0].y);
    for (let i = 1; i < path.length; i++) {
      this.ctx.lineTo(path[i].x, path[i].y);
    }
    this.ctx.stroke();
  }
}

interface Point {
  x: number;
  y: number;
}
Enter fullscreen mode Exit fullscreen mode

Test Results

input900.gif

Related Resources

Written by Emrecan Karakas

Top comments (0)