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)
}
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.
Top comments (0)