high_light_search_text.dart 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import 'package:flutter/cupertino.dart';
  2. import 'package:flutter/material.dart';
  3. class HighlightSearchText extends StatefulWidget {
  4. final String text; // 原始文本
  5. final String searchKeyword; // 搜索关键字
  6. final int? defaultHighlightIndex; // 默认高亮的索引位置
  7. final TextStyle normalTextStyle; // 普通文本样式
  8. final TextStyle highlightTextStyle; // 高亮文本样式
  9. final TextStyle activeHighlightTextStyle; // 定位高亮文本样式
  10. final ValueChanged<Map<String, int>>? onHighlightChanged; // 高亮变化时的回调
  11. const HighlightSearchText({
  12. super.key,
  13. required this.text,
  14. required this.searchKeyword,
  15. this.defaultHighlightIndex,
  16. this.onHighlightChanged,
  17. this.normalTextStyle = const TextStyle(color: Colors.black, fontSize: 16),
  18. this.highlightTextStyle = const TextStyle(
  19. color: Colors.white, backgroundColor: Colors.orange, fontSize: 16),
  20. this.activeHighlightTextStyle = const TextStyle(
  21. color: Colors.white, backgroundColor: Colors.red, fontSize: 16),
  22. });
  23. @override
  24. State<HighlightSearchText> createState() => _HighlightSearchTextState();
  25. static int getHighlightTotal(String targetTxt, String searchKeyword) {
  26. if (searchKeyword.isEmpty || targetTxt.isEmpty) {
  27. return 0;
  28. }
  29. int count = 0;
  30. int start = 0;
  31. while (true) {
  32. int index = targetTxt.indexOf(searchKeyword, start);
  33. if (index < 0) break;
  34. count++;
  35. start = index + searchKeyword.length;
  36. }
  37. return count;
  38. }
  39. }
  40. class _HighlightSearchTextState extends State<HighlightSearchText> {
  41. List<int> matchIndices = []; // 记录所有匹配的位置
  42. int currentHighlightIndex = -1; // 当前高亮的索引
  43. @override
  44. void initState() {
  45. super.initState();
  46. currentHighlightIndex = widget.defaultHighlightIndex ?? -1;
  47. _updateMatchIndices();
  48. }
  49. @override
  50. Widget build(BuildContext context) {
  51. return RichText(
  52. text: _buildHighlightedText(),
  53. );
  54. }
  55. @override
  56. void didUpdateWidget(covariant HighlightSearchText oldWidget) {
  57. super.didUpdateWidget(oldWidget);
  58. if (widget.text != oldWidget.text ||
  59. widget.searchKeyword != oldWidget.searchKeyword ||
  60. widget.defaultHighlightIndex != oldWidget.defaultHighlightIndex) {
  61. currentHighlightIndex = widget.defaultHighlightIndex ?? -1;
  62. // debugPrint(
  63. // 'HighlightSearchText currentHighlightIndex:$currentHighlightIndex '
  64. // 'defaultHighlightIndex:${widget.defaultHighlightIndex} '
  65. // 'matchIndices:${matchIndices.length} '
  66. // 'text:${widget.text} '
  67. // ' searchKeyword:${widget.searchKeyword}');
  68. _updateMatchIndices();
  69. _notifyHighlightChanged();
  70. }
  71. }
  72. void _notifyHighlightChanged() {
  73. if (widget.onHighlightChanged != null) {
  74. widget.onHighlightChanged!({
  75. 'current': currentHighlightIndex + 1, // 当前高亮位置,1-based
  76. 'total': matchIndices.length, // 总匹配数量
  77. });
  78. }
  79. }
  80. void _updateMatchIndices() {
  81. matchIndices.clear();
  82. if (widget.searchKeyword.isEmpty) return;
  83. String textLower = widget.text.toLowerCase(); // 将原文本转换为小写
  84. String keywordLower = widget.searchKeyword.toLowerCase(); // 将搜索关键字转换为小写
  85. int start = 0;
  86. while (true) {
  87. int index = textLower.indexOf(keywordLower, start); // 在小写文本中进行匹配
  88. if (index < 0) break;
  89. matchIndices.add(index);
  90. start = index + widget.searchKeyword.length;
  91. }
  92. }
  93. TextSpan _buildHighlightedText() {
  94. if (widget.searchKeyword.isEmpty) {
  95. // 如果没有搜索关键词,返回原始文本
  96. return TextSpan(
  97. text: widget.text,
  98. style: widget.normalTextStyle,
  99. );
  100. }
  101. List<TextSpan> spans = [];
  102. int start = 0;
  103. for (int i = 0; i < matchIndices.length; i++) {
  104. int index = matchIndices[i];
  105. // 添加普通文本部分
  106. if (index > start) {
  107. spans.add(TextSpan(
  108. text: widget.text.substring(start, index),
  109. style: widget.normalTextStyle,
  110. ));
  111. }
  112. // 添加高亮文本部分
  113. spans.add(TextSpan(
  114. text: widget.text.substring(index, index + widget.searchKeyword.length),
  115. style: i == currentHighlightIndex
  116. ? widget.activeHighlightTextStyle // 当前定位的高亮颜色
  117. : widget.highlightTextStyle, // 普通高亮颜色
  118. ));
  119. start = index + widget.searchKeyword.length;
  120. }
  121. // 添加剩余普通文本部分
  122. if (start < widget.text.length) {
  123. spans.add(TextSpan(
  124. text: widget.text.substring(start),
  125. style: widget.normalTextStyle,
  126. ));
  127. }
  128. return TextSpan(children: spans);
  129. }
  130. }