Skip to content

Instantly share code, notes, and snippets.

@CaiJingLong
Last active December 20, 2024 02:26
Show Gist options
  • Save CaiJingLong/9cbd4a968512446400f4fb2a3e2ef999 to your computer and use it in GitHub Desktop.
Save CaiJingLong/9cbd4a968512446400f4fb2a3e2ef999 to your computer and use it in GitHub Desktop.
拼音模糊匹配的方法,需要导入 pinyin 库 flutter pub add pinyin
import 'package:flutter/material.dart';
import 'package:pinyin/pinyin.dart';
/// 拼音比较结果类
///
/// 用于存储拼音匹配的结果信息,包括是否匹配、拼音字符串和匹配位置信息
///
/// 主要功能:
/// 1. 存储匹配结果
/// 2. 提供构建富文本的方法,可以高亮显示匹配的字符
///
/// 示例:
/// ```dart
/// // 1. 基本使用
/// final result = PinyinUtils.checkPinyin(
/// text: '小明同学',
/// inputPinyin: 'xm',
/// );
///
/// print(result.isMatch); // true
/// print(result.pinyin); // xiaomingtongxue
/// print(result.matchIndex); // {0: true, 1: true}
///
/// // 2. 在 UI 中显示高亮效果
/// RichText(
/// text: TextSpan(
/// children: result.buildRich(
/// matchColor: Colors.red, // 匹配字符的颜色
/// normalColor: Colors.grey, // 普通字符的颜色
/// fontSize: 16, // 字体大小
/// ),
/// ),
/// )
///
/// // 3. 自定义样式
/// final spans = result.buildRich(
/// matchColor: Colors.blue,
/// normalColor: Colors.black87,
/// fontSize: 14,
/// );
/// ```
class PinyinCompareResult {
/// 是否匹配成功
final bool isMatch;
/// 原文本转换后的拼音字符串
final String pinyin;
/// 匹配位置信息
///
/// key: 字符在拼音中的位置
/// value: true 表示该位置的字符匹配成功
final Map<int, bool> matchIndex;
PinyinCompareResult({
required this.isMatch,
required this.pinyin,
this.matchIndex = const {},
});
/// 构建富文本,用于显示匹配高亮效果
///
/// 参数:
/// * [matchColor] - 匹配字符的颜色,默认为红色
/// * [normalColor] - 普通字符的颜色,默认为灰色
/// * [fontSize] - 字体大小,默认为 13
///
/// 返回值:
/// * 返回 [TextSpan] 列表,可以直接用于 [RichText] 或 [Text.rich]
List<TextSpan> buildRich({
Color matchColor = Colors.red,
Color normalColor = Colors.grey,
double fontSize = 13,
}) {
final normalStyle = TextStyle(color: normalColor, fontSize: fontSize);
final matchStyle = TextStyle(color: matchColor, fontSize: fontSize);
final List<TextSpan> textSpans = [
TextSpan(
text: ' ',
style: normalStyle,
),
];
var start = 0;
matchIndex.forEach((key, value) {
if (start < key) {
textSpans.add(
TextSpan(
text: pinyin.substring(start, key),
style: normalStyle,
),
);
}
textSpans.add(
TextSpan(
text: pinyin[key],
style: value ? matchStyle : normalStyle,
),
);
start = key + 1;
});
if (start < pinyin.length) {
textSpans.add(
TextSpan(
text: pinyin.substring(start),
style: normalStyle,
),
);
}
return textSpans;
}
}
class PinyinUtils {
PinyinUtils._();
/// 批量检查拼音匹配,支持任意类型的数据列表
///
/// 示例:
/// ```dart
/// // 1. 简单的字符串列表匹配
/// final names = ['小明', '小红', '小张'];
/// final matches = PinyinUtils.batchCheckPinyin(
/// items: names,
/// inputPinyin: 'xm',
/// textExtractor: (name) => name,
/// );
///
/// // 2. 对象列表匹配
/// class User {
/// final String name;
/// final String phone;
/// User(this.name, this.phone);
/// }
///
/// final users = [
/// User('张三', '13800138000'),
/// User('李四', '13900139000'),
/// ];
///
/// // 按名字搜索
/// final nameMatches = PinyinUtils.batchCheckPinyin(
/// items: users,
/// inputPinyin: 'zs',
/// textExtractor: (user) => user.name,
/// );
///
/// // 按电话搜索
/// final phoneMatches = PinyinUtils.batchCheckPinyin(
/// items: users,
/// inputPinyin: '138',
/// textExtractor: (user) => user.phone,
/// );
///
/// // 3. 在UI中使用匹配结果
/// ListView.builder(
/// itemCount: matches.length,
/// itemBuilder: (context, index) {
/// final match = matches[index];
/// return ListTile(
/// title: RichText(
/// text: TextSpan(
/// children: match.result.buildRich(),
/// ),
/// ),
/// );
/// },
/// )
/// ```
///
/// 参数说明:
/// * [items] - 要搜索的数据列表,可以是任意类型
/// * [inputPinyin] - 输入的拼音字符串,支持全拼和首字母,如 'xiaoming' 或 'xm'
/// * [textExtractor] - 从数据项中提取要匹配的文本的函数
///
/// 返回值:
/// * 返回 [PinyinMatchItem] 列表,包含匹配的原始数据和匹配结果
/// * 只返回匹配成功的项目
static List<PinyinMatchItem<T>> batchCheckPinyin<T>({
required List<T> items,
required String inputPinyin,
required String Function(T item) textExtractor,
}) {
final results = <PinyinMatchItem<T>>[];
for (final item in items) {
final text = textExtractor(item);
final matchResult = checkPinyin(
text: text,
inputPinyin: inputPinyin,
);
if (matchResult.isMatch) {
results.add(PinyinMatchItem(
item: item,
result: matchResult,
));
}
}
return results;
}
/// 检查单个文本是否匹配输入的拼音
///
/// 支持的匹配方式:
/// 1. 连续匹配: 'xiaoming' 匹配 'xm'
/// 2. 顺序匹配: 'xiaoming' 匹配 'xming'
/// 3. 完整匹配: 'xiaoming' 匹配 'xiaoming'
///
/// 示例:
/// ```dart
/// final result = PinyinUtils.checkPinyin(
/// text: '小明',
/// inputPinyin: 'xm',
/// );
///
/// if (result.isMatch) {
/// print('匹配成功');
/// // 使用 buildRich() 显示高亮的匹配结果
/// RichText(
/// text: TextSpan(
/// children: result.buildRich(),
/// ),
/// );
/// }
/// ```
///
/// 参数:
/// * [text] - 要匹配的原始文本
/// * [inputPinyin] - 输入的拼音
///
/// 返回值:
/// * 返回 [PinyinCompareResult],包含匹配结果和匹配位置信息
static PinyinCompareResult checkPinyin({
required String text,
required String inputPinyin,
}) {
if (text.trim().isEmpty) {
return PinyinCompareResult(isMatch: false, pinyin: '');
}
final textPinyin = PinyinHelper.getPinyin(text, separator: '');
if (inputPinyin.isEmpty) {
return PinyinCompareResult(isMatch: false, pinyin: textPinyin);
}
inputPinyin = inputPinyin.toLowerCase();
final matchIndex = <int, bool>{};
if (textPinyin.contains(inputPinyin)) {
final index = textPinyin.indexOf(inputPinyin);
for (var i = 0; i < inputPinyin.length; i++) {
matchIndex[index + i] = true;
}
return PinyinCompareResult(
isMatch: true, pinyin: textPinyin, matchIndex: matchIndex);
}
// 双指针查找法,查找是否包含输入的拼音
// 比如 xiaomishouji 包含 xmsj、也包含 xmiouji ,只要顺序不错,即可匹配成功
// 被查找的拼音指针
var tPosition = 0;
// 输入的拼音指针
var iPosition = 0;
for (; tPosition < textPinyin.length; tPosition++) {
if (textPinyin[tPosition] == inputPinyin[iPosition]) {
iPosition++;
matchIndex[tPosition] = true;
}
if (iPosition == inputPinyin.length) {
return PinyinCompareResult(
isMatch: true,
pinyin: textPinyin,
matchIndex: matchIndex,
);
}
}
return PinyinCompareResult(isMatch: false, pinyin: textPinyin);
}
}
/// 拼音匹配结果项
///
/// 包含原始数据项和对应的匹配结果,用于批量匹配场景
///
/// 泛型 [T] 可以是任意类型,方便处理不同类型的数据
///
/// 示例:
/// ```dart
/// final match = PinyinMatchItem(
/// item: user, // 原始数据
/// result: compareResult, // 匹配结果
/// );
///
/// // 访问原始数据
/// print(match.item.name);
///
/// // 使用匹配结果
/// RichText(
/// text: TextSpan(
/// children: match.result.buildRich(),
/// ),
/// );
/// ```
class PinyinMatchItem<T> {
/// 原始数据项
final T item;
/// 拼音匹配结果
final PinyinCompareResult result;
PinyinMatchItem({
required this.item,
required this.result,
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment