DEV Community

HarmonyOS
HarmonyOS

Posted on

Creating a Touch-Responsive Angle Measurement Tool Using Canvas

Read the original article:Creating a Touch-Responsive Angle Measurement Tool Using Canvas

Creating a Touch-Responsive Angle Measurement Tool Using Canvas

Requirement Description

How to implement an interactive protractor (angle measurement tool) using the Canvas component, allowing users to measure angles through touch interaction with real-time visual feedback.

Background Knowledge

Implementation Steps

Use the Canvas component to create a custom semicircular gauge with scale markings. The specific implementation steps are as follows:

  1. Draw the protractor panel: In the draw() method, clear the canvas. Use the arc() method to draw a semicircle with a radius of 120 to form the protractor panel.
  2. Draw scale lines: Loop from 90 degrees to 270 degrees, incrementing by 5 degrees each time. Use moveTo() and lineTo() methods to draw each scale line, with lines extending inward 3 units from radius 120.
  3. Draw the pointer: Use the arc() method to draw a sector area starting from 180 degrees (directly left), drawing to the corresponding angle based on the angel variable value. Use stroke() and fill() methods to complete the pointer drawing.
  4. Handle touch events: In the onTouch event, calculate the touch point coordinates relative to the center point. Use Math.atan() to calculate the angle, adjusting the angle range to 0-180 degrees as needed. Update the angel variable and call the draw() method to redraw the protractor, displaying the current angle.
  5. Display current angle: Use a Text component to display the current angle value, formatted to two decimal places.

Code Snippet / Configuration

Complete implementation:

@Entry
@Component
struct Index {
  @State angel: number = 0
  private settings: RenderingContextSettings = new RenderingContextSettings(true)
  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
  @State canvasH: number = 150
  private radius: number = 0
  private centerX: number = 0
  private centerY: number = 0

  private draw() {
    this.context.clearRect(0, 0, this.context.width, this.context.height)

    this.context.beginPath()
    this.context.arc(this.centerX, this.centerY, this.radius, Math.PI, Math.PI * 2)
    this.context.lineWidth = 1.5
    this.context.strokeStyle = '#222222'
    this.context.stroke()

    for (let deg: number = 90; deg <= 270; deg += 5) {
      const rad: number = deg * Math.PI / 180
      const inner: number = this.radius - (deg % 10 === 0 ? 6 : 3)
      const x1: number = this.centerX + this.radius * Math.sin(rad)
      const y1: number = this.centerY + this.radius * Math.cos(rad)
      const x2: number = this.centerX + inner * Math.sin(rad)
      const y2: number = this.centerY + inner * Math.cos(rad)
      this.context.beginPath()
      this.context.lineWidth = deg % 10 === 0 ? 1.5 : 1
      this.context.strokeStyle = deg % 10 === 0 ? '#FF4D4F' : '#D46B08'
      this.context.moveTo(x1, y1)
      this.context.lineTo(x2, y2)
      this.context.stroke()
    }

    const endRad: number = Math.PI + (this.angel * Math.PI / 180)
    this.context.beginPath()
    this.context.strokeStyle = '#1677FF'
    this.context.fillStyle = 'rgba(22,119,255,0.15)'
    this.context.moveTo(this.centerX, this.centerY)
    this.context.lineTo(this.centerX - this.radius, this.centerY)
    this.context.arc(this.centerX, this.centerY, this.radius, Math.PI, endRad)
    this.context.lineTo(this.centerX, this.centerY)
    this.context.stroke()
    this.context.fill()
  }

  build() {
    Column() {
      Canvas(this.context)
        .width('100%')
        .height(this.canvasH)
        .backgroundColor(Color.Transparent)
        .onReady(() => {
          this.centerX = this.context.width / 2
          this.centerY = this.context.height
          this.radius = Math.min(this.centerX, this.centerY) - 8
          this.draw()
        })
        .onTouch((event: TouchEvent) => {
          if (!event.touches || event.touches.length === 0) {
            return
          }
          const tx: number = event.touches[0].x
          const ty: number = event.touches[0].y
          const vx: number = this.centerX - tx
          const vy: number = this.centerY - ty
          let deg: number = Math.atan2(vy, vx) * 180 / Math.PI
          if (deg < 0) {
            deg += 180
          }
          if (deg > 180) {
            deg = 180
          }
          if (deg < 0) {
            deg = 0
          }
          this.angel = deg
          this.draw()
        })

      Text('Angle: ' + this.angel.toFixed(1) + '°')
        .fontSize(14)
        .margin({ top: 6 })
    }
    .width('100%')
    .height('100%')
    .padding(8)
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
    .backgroundColor('#F7F8FA')
    .onAreaChange((_old, cur) => {
      const pad: number = 12
      const w: number = Math.floor(Number(cur.width) - pad * 2)
      const h: number = Math.floor(w / 2)
      if (Math.abs(h - this.canvasH) >= 2) {
        this.canvasH = h > 80 ? h : 80
        this.centerX = Math.floor(w / 2)
        this.centerY = this.canvasH
        this.radius = Math.min(this.centerX, this.centerY) - 8
        if (this.context.width && this.context.height) {
          this.draw()
        }
      }
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

Test Results

The implementation successfully demonstrates:

  • Interactive protractor with real-time angle measurement (0-180 degrees)
  • Touch-responsive pointer that follows user interaction
  • Visual feedback with colored scale markings (red) and pointer area (green)
  • Accurate angle calculation using arctangent mathematics
  • Dynamic text display showing angle to two decimal precision
  • Smooth canvas redrawing on each touch event

kbs--95a55f755a9d4e9ebe1ba110018be06e-64a19.gif

Limitations or Considerations

  • This example supports API Version 19 Release and above
  • Compatible with HarmonyOS 5.1.1 Release SDK and above
  • Requires DevEco Studio 5.1.1 Release or later for compilation and execution
  • The protractor measures angles from 0 to 180 degrees (semicircle only)
  • Touch interaction requires proper handling of coordinate system transformations

Related Documents or Links

Written by Bunyamin Eymen Alagoz

Top comments (0)