DEV Community

HarmonyOS
HarmonyOS

Posted on

Inline Gradient Chips in HarmonyOS: CustomSpan, Text Wrapper, and ImageSpan Approaches

Read the original article:Inline Gradient Chips in HarmonyOS: CustomSpan, Text Wrapper, and ImageSpan Approaches

Inline Gradient Chips in HarmonyOS: CustomSpan, Text Wrapper, and ImageSpan Approaches

Requirement Description

Render inline text where a specific “span” segment has a gradient background. Direct linearGradient on Span doesn’t work, so we provide three working approaches.

Background Knowledge

  • Span: child of Text/ContainerSpan; does not support universal attributes, so linearGradient is ignored. Use Text for universal styling or custom-draw with CustomSpan.

Docs: Span, Text, ContainerSpan

  • Universal gradient attribute: linearGradient works on components that support universal attributes (e.g., Text).

Docs: linearGradient

  • Styled strings & CustomSpan: draw your own background/text for inline segments.

Docs: CustomSpan (Styled String), Example – Span

  • Graphics API for gradients: set a shader on the brush.

Docs (search please): Brush.setShaderEffect, ShaderEffect.createLinearGradient

Implementation Steps

1.Choose an approach

A) CustomSpan (recommended): Draw rounded-rect gradient + text via drawing APIs.

B) Text wrapper gradient: Wrap the small label as a separate Text (supports linearGradient), keep the rest as normal spans.

C) Image-based: Draw gradient + text to a PixelMap and embed inline via ImageSpan/ImageAttachment.

2.Mind inline layout: baseline alignment and width/height so the “chip” sits naturally within the line.

3.Wearable tips: maintain contrast; avoid tiny corners that clip on round screens; ≥ 44 vp if tappable.

Code Snippet / Configuration

A) CustomSpan — gradient background (recommended)

// In your CustomSpan onDraw(...)
onDraw(context: DrawContext, options: CustomSpanDrawInfo) {
  const canvas = context.canvas;

  // Gradient brush
  const bg = new drawing.Brush();
  const w = this.getUIContext().vp2px(this.width);
  const h = this.getUIContext().vp2px(this.height);
  const start: common2D.Point = { x: 0, y: 0 };
  const end: common2D.Point = { x: w, y: h };
  const shader = drawing.ShaderEffect.createLinearGradient(
    start, end, [0xFFE574FF, 0xFFC906FF], drawing.TileMode.CLAMP
  );
  bg.setShaderEffect(shader);

  // Rounded background behind the span segment
  const rect: common2D.Rect = { left: options.x, top: options.lineTop, right: options.x + w, bottom: options.lineBottom };
  const path = new drawing.Path();
  path.addRoundRect(new drawing.RoundRect(rect, 4, 4), drawing.PathDirection.CLOCKWISE);
  canvas.attachBrush(bg);
  canvas.drawPath(path);
  canvas.detachBrush();

  // Foreground text
  const fg = new drawing.Brush();
  fg.setColor({ alpha: 255, red: 255, green: 255, blue: 255 });
  const font = new drawing.Font();
  font.setSize(25);
  const blob = drawing.TextBlob.makeFromString(this.word, font, drawing.TextEncoding.TEXT_ENCODING_UTF8);
  canvas.attachBrush(fg);
  canvas.drawTextBlob(blob, options.x + this.getUIContext().vp2px(8), options.lineBottom - this.getUIContext().vp2px(6));
  canvas.detachBrush();
}
Enter fullscreen mode Exit fullscreen mode

B) Text wrapper gradient — use Text for the chip

@Entry
@Component
export struct RichEditorStatus {
  build() {
    Row() {
      Text() {
        Span('Hello').fontSize(30)
      }
      .linearGradient({ direction: GradientDirection.Right, colors: [['#ff46f6f6', 0], ['#ff46a4f6', 1]] })
      .padding({ left: 6, right: 6 })
      .borderRadius(6)

      Text() { Span(' World!').fontSize(30) }
    }.height('100%')
  }
}
Enter fullscreen mode Exit fullscreen mode

C) Image-based — draw to PixelMap and embed inline

// After drawing gradient+text to PixelMap:
Text() {
  ImageSpan(this.pixelMap).width(40).height(16)
  Span(' World!').baselineOffset(new LengthMetrics(0)).fontSize(16)
}
Enter fullscreen mode Exit fullscreen mode

Test Results

  • CustomSpan correctly renders a gradient chip inline.
  • Text wrapper approach works when the chip can be a separate node.
  • Image-based matches complex visuals; cache the PixelMap for performance.

12.png

Limitations or Considerations

  • Span can’t take linearGradient; universal attributes are ignored.
  • Wrapping: long text may reveal seams with A/B/C approaches—plan layout accordingly.
  • Performance: image rendering is heavier; cache and redraw only on change.

Related Documents or Links

Written by Bunyamin Eymen Alagoz

Top comments (0)