import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; class HighlightSearchText extends StatefulWidget { final String text; // 原始文本 final String searchKeyword; // 搜索关键字 final int? defaultHighlightIndex; // 默认高亮的索引位置 final TextStyle normalTextStyle; // 普通文本样式 final TextStyle highlightTextStyle; // 高亮文本样式 final TextStyle activeHighlightTextStyle; // 定位高亮文本样式 final ValueChanged>? onHighlightChanged; // 高亮变化时的回调 const HighlightSearchText({ super.key, required this.text, required this.searchKeyword, this.defaultHighlightIndex, this.onHighlightChanged, this.normalTextStyle = const TextStyle(color: Colors.black, fontSize: 16), this.highlightTextStyle = const TextStyle( color: Colors.white, backgroundColor: Colors.orange, fontSize: 16), this.activeHighlightTextStyle = const TextStyle( color: Colors.white, backgroundColor: Colors.red, fontSize: 16), }); @override State createState() => _HighlightSearchTextState(); static int getHighlightTotal(String targetTxt, String searchKeyword) { if (searchKeyword.isEmpty || targetTxt.isEmpty) { return 0; } int count = 0; int start = 0; while (true) { int index = targetTxt.indexOf(searchKeyword, start); if (index < 0) break; count++; start = index + searchKeyword.length; } return count; } } class _HighlightSearchTextState extends State { List matchIndices = []; // 记录所有匹配的位置 int currentHighlightIndex = -1; // 当前高亮的索引 @override void initState() { super.initState(); currentHighlightIndex = widget.defaultHighlightIndex ?? -1; _updateMatchIndices(); } @override Widget build(BuildContext context) { return RichText( text: _buildHighlightedText(), ); } @override void didUpdateWidget(covariant HighlightSearchText oldWidget) { super.didUpdateWidget(oldWidget); if (widget.text != oldWidget.text || widget.searchKeyword != oldWidget.searchKeyword || widget.defaultHighlightIndex != oldWidget.defaultHighlightIndex) { currentHighlightIndex = widget.defaultHighlightIndex ?? -1; // debugPrint( // 'HighlightSearchText currentHighlightIndex:$currentHighlightIndex ' // 'defaultHighlightIndex:${widget.defaultHighlightIndex} ' // 'matchIndices:${matchIndices.length} ' // 'text:${widget.text} ' // ' searchKeyword:${widget.searchKeyword}'); _updateMatchIndices(); _notifyHighlightChanged(); } } void _notifyHighlightChanged() { if (widget.onHighlightChanged != null) { widget.onHighlightChanged!({ 'current': currentHighlightIndex + 1, // 当前高亮位置,1-based 'total': matchIndices.length, // 总匹配数量 }); } } void _updateMatchIndices() { matchIndices.clear(); if (widget.searchKeyword.isEmpty) return; String textLower = widget.text.toLowerCase(); // 将原文本转换为小写 String keywordLower = widget.searchKeyword.toLowerCase(); // 将搜索关键字转换为小写 int start = 0; while (true) { int index = textLower.indexOf(keywordLower, start); // 在小写文本中进行匹配 if (index < 0) break; matchIndices.add(index); start = index + widget.searchKeyword.length; } } TextSpan _buildHighlightedText() { if (widget.searchKeyword.isEmpty) { // 如果没有搜索关键词,返回原始文本 return TextSpan( text: widget.text, style: widget.normalTextStyle, ); } List spans = []; int start = 0; for (int i = 0; i < matchIndices.length; i++) { int index = matchIndices[i]; // 添加普通文本部分 if (index > start) { spans.add(TextSpan( text: widget.text.substring(start, index), style: widget.normalTextStyle, )); } // 添加高亮文本部分 spans.add(TextSpan( text: widget.text.substring(index, index + widget.searchKeyword.length), style: i == currentHighlightIndex ? widget.activeHighlightTextStyle // 当前定位的高亮颜色 : widget.highlightTextStyle, // 普通高亮颜色 )); start = index + widget.searchKeyword.length; } // 添加剩余普通文本部分 if (start < widget.text.length) { spans.add(TextSpan( text: widget.text.substring(start), style: widget.normalTextStyle, )); } return TextSpan(children: spans); } }