Implementing Custom Text Overflow with Highlight Support in ExtendedText for Flutter and HarmonyOS
This article explains how the ExtendedText component adds custom text‑overflow effects, including start, middle, end and auto modes, supports highlighted keywords with keep‑visible spans, and improves performance by replacing binary search with range estimation across Flutter, Android, iOS, Web and HarmonyOS platforms.
The ExtendedText widget, originally released for Flutter five years ago, now also supports native HarmonyOS Next platforms and provides customizable text‑overflow effects beyond the default "..." ellipsis.
Custom Overflow Modes
Four overflow positions are defined: start , middle , end and auto . The auto mode automatically selects the appropriate position based on the location of a highlighted keyword.
Implementation Steps
Clip the original text.
Calculate the range where the text does not overflow.
Render the overflow widget and mask the underlying text.
Detecting Visual Overflow (Flutter)
bool _didVisualOverflow({TextPainter? textPainter}) {
final Size textSize = (textPainter ?? _textPainter).size;
final bool textDidExceedMaxLines = (textPainter ?? _textPainter).didExceedMaxLines;
final bool didOverflowHeight = size.height < textSize.height || textDidExceedMaxLines;
final bool didOverflowWidth = size.width < textSize.width;
if (size.height < textSize.height) {
size = constraints.constrain(textSize);
}
return didOverflowWidth || didOverflowHeight;
}Detecting Visual Overflow (HarmonyOS)
_didVisualOverflow(paragraph: text.Paragraph, constraint: ConstraintSizeOptions): boolean {
let textSize: SizeResult = {
width: px2vp(paragraph.getMaxWidth()),
height: px2vp(paragraph.getHeight()),
};
let size: SizeResult = {
width: constraint.maxWidth! as number,
height: constraint.maxHeight! as number,
};
let textDidExceedMaxLines = paragraph.didExceedMaxLines();
let didOverflowHeight = size.height < textSize.height || textDidExceedMaxLines;
let didOverflowWidth = size.width < textSize.width;
let hasVisualOverflow = didOverflowWidth || didOverflowHeight;
return hasVisualOverflow;
}Keeping Highlighted Span Visible
Mark the span that should stay visible with keepVisible: true . The framework then locates this span and ensures its range is preserved during clipping.
// Flutter
SpecialInlineSpanBase? keepVisibleSpan;
text.visitChildren((InlineSpan span) {
if (span is SpecialInlineSpanBase && (span as SpecialInlineSpanBase).keepVisible == true) {
keepVisibleSpan = span as SpecialInlineSpanBase;
return false;
}
return true;
}); // HarmonyOS
let keepVisibleSpan: InlineSpan | null = null;
this.text.visitChildren((span) => {
if (span.keepVisible === true) {
keepVisibleSpan = span;
return false;
}
return true;
});Rendering and Masking Overflow
On Flutter, the overflow widget is drawn using canvas.clipRect before painting the text; on HarmonyOS a similar clipping operation is performed.
// Flutter clipping example
if (_overflowRects != null) {
context.canvas.saveLayer(offset & size, Paint());
if (overflowWidget?.clearType == TextOverflowClearType.clipRect) {
if (_overflowClipTextRects != null) {
for (final Rect rect in _overflowClipTextRects!) {
context.canvas.clipRect(rect.shift(offset), clipOp: ui.ClipOp.difference);
}
}
for (final Rect rect in _overflowRects!) {
context.canvas.clipRect(rect.shift(offset), clipOp: ui.ClipOp.difference);
}
}
}
_textPainter.paint(context.canvas, offset);
paintInlineChildren(context, offset);
if (_overflowRects != null) {
context.canvas.restore();
}Performance Improvements
Instead of a binary‑search approach to find the overflow index, the new algorithm estimates a rough range using a single‑line TextPainter/Paragraph and then refines it, achieving more than 40% speedup for long texts.
Usage
Installation
Flutter: flutter pub add extended_text
HarmonyOS: ohpm install @candies/extended_text
Define Highlight Span
import 'package:extended_text/extended_text.dart';
import 'package:flutter/material.dart';
class HighlightText extends RegExpSpecialText {
@override
RegExp get regExp => RegExp("
(.*?)
");
static String getHighlightString(String content) =>
'
' + content + '
';
@override
InlineSpan finishText(int start, Match match,
{TextStyle? textStyle, SpecialTextGestureTapCallback? onTap}) {
final String hexColor = match[1]!;
return SpecialTextSpan(
text: match[2]!,
actualText: match[0],
start: start,
style: textStyle?.copyWith(color: Color(int.parse(hexColor.substring(1), radix: 16))),
keepVisible: true,
);
}
}Set Overflow Position
ExtendedText(
searchMessages[index],
specialTextSpanBuilder: HighlightTextSpanBuilder(),
maxLines: searchText.isEmpty ? 3 : 1,
overflowWidget: TextOverflowWidget(
child: const Text('\u2026 '),
position: TextOverflowPosition.auto,
),
);With these steps, the ExtendedText component now supports rich, customizable overflow effects on all major platforms, including Web, Android, iOS, Windows, macOS, Linux, HarmonyOS, HyperOS, ColorOS, OriginOS, MagicOS, Chrome OS and FuchsiaOS.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.