DEV Community

HarmonyOS
HarmonyOS

Posted on

Enhancing Shape Rendering to Show Only Borders

Read the original article:Enhancing Shape Rendering to Show Only Borders

Context

During the app's progression, the shapes were originally rendered as fully filled elements.

While this correctly represented their structure, it did not align with the intended minimalist visual design.

The desired effect was for the shapes to appear empty inside while keeping their borders visible — creating a cleaner and more modern look that clearly distinguishes different states such as active and completed.

Description

Each shape was drawn with a single path that defined both its border and fill area.

This approach made it difficult to control the inner appearance without affecting the outline.

As a result, the shapes appeared solidly filled, reducing visual clarity and contrast between different states.

To resolve this, the rendering process was modified to use two separate paths for each shape.

By drawing two overlapping paths with slightly different stroke thicknesses, the inner area between them could be visually controlled to simulate an “empty center.”

his made it possible to preserve the shape’s outline while adjusting the perceived fill dynamically depending on the app’s state (e.g., active, completed).

Solution / Approach

The solution involves rendering two closely overlapping paths during the build() stage.

Each path shares the same geometric commands, but their stroke widths differ slightly.

This subtle difference creates the illusion of a hollow shape with a visible border.

  • The first path defines the outer border using the main stroke color.
  • The second path, drawn inside it with a slightly thinner stroke, is used to represent the fill color dynamically.
  • When the shape is in progress (uncompleted), a dark blue or default color is applied.
  • When the shape is completed or the stage is passed, a turquoise (cyan) tone is used to indicate progress.

This layered rendering method allows the shapes to visually appear empty but still retain their clear, defined edges.

  build() {
    Stack({ alignContent: CENTER_ALIGNMENT }) {
      ForEach(
        **your shapes' arc method**,
        (arc: string, index: number) => {
          Stack() {
            this.drawPath(arc, NAVY_BLUE, STROKE_WIDTH_MAIN);
            this.drawPath(arc, **your condition** ? CYAN : this.pieceColor, STROKE_WIDTH_SECONDARY);
          }
        },
        (arc: string, index: number) => `${index}`
      );
    }
    .width(this.cellSize)
    .height(this.cellSize)
    .backgroundColor(Color.Transparent)
    .clip(false)
  }

  @Builder
  drawPath(arc: string, strokeColor: string, strokeWidth: number) {
    Path()
      .commands(arc)
      .stroke(strokeColor)
      .strokeWidth(strokeWidth)
      .fill('none')
      .width(this.cellSize)
      .height(this.cellSize)
      .antiAlias(true)
  }
Enter fullscreen mode Exit fullscreen mode

Result

This enhancement provides a clear visual distinction between shape states without introducing heavy color fills.

The shapes now appear border-only, with subtle inner highlights that change depending on progression.

This not only improves aesthetic quality but also enhances visual clarity, ensuring users can easily identify active versus completed shapes.

Key Takeaways

  • Shapes now appear visually “empty” while retaining well-defined borders.
  • Implemented by rendering two nearly overlapping paths with different stroke widths.
  • Visual state is dynamically controlled based on app progression.
  • The design achieves a clean, minimal, and modern visual effect consistent with the overall app style.

Written by Aycanur Ucar

Top comments (0)