項目中的android 車機系統 搜索聯繫人算法一直有問題 , 這裏就把整個的流程寫一遍
一 . 搜索算法實現的功能
1.支持中文,英文搜索
2.支持電話號碼搜索
3.支持漢語拼音搜索, 首字母搜索也能支持, 同時對檢索到的文字顯示高亮
前面的第一, 第二點, 都是比較簡單的, 通過遍歷字符串, 查看字符串是否包含用戶輸入的字符, 就能達到檢索的功能
重點是第三點,拼音的搜索,首字母搜索
二. 拼音搜索的流程
1.先介紹幾個類
ContactData.java , 裏面保存有手機端同步過來的數據
public class ContactData implements Serializable, Parcelable {
public static final int CONTACT_NAME_MAX_LENGTH = 16;
public static final int CONTACT_PHONE_MAX_LENGTH = 32;
private String name = "";
private String letter = "";
private List<Integer> phoneNumTypeList = new ArrayList<>();
private List<String> phoneNumList = new ArrayList<>();
public List<NumberData> numbers = new ArrayList<>();
private List<String> addressList = new ArrayList<>();
public int photoType;
public byte[] photoBytes;
.... //一些可能用到的方法
}
SearchResult.java , 搜索結果實體類 .
public class SearchResult implements Serializable, Cloneable, Parcelable {
private static final long serialVersionUID = -8624630249543035384L;
public long contactID = -1;
public String name = "";
public String phone;
public String time;
public int count = 1;
// 加入優先級 排序用的
int priority = -1;
public String pinyin;
public String number;
public String initials;
public String pinyinInitials;
// 高亮
public String[] highlight;
// 分解
ArrayList<PyNode> nodes;
public int photoType;
public byte[] photoBytes;
...// 一些可能使用到的方法
}
Pinyin.java 這個類來自於開源的項目 com.github.promeg.pinyinhelper , 通過下面的語句,可以將中文轉化爲拼音, 具體的原理, 不詳,有時間再看看
// 漢字
String pinyin = Pinyin.toPinyin(c);
2.實現流程,
a.先將同步到的聯繫人數據contacts 轉爲searchResult 數據, 這個過程就是將名字數據轉換爲拼音, 生成首字母數據pinyinInitials
private List<SearchResult> convertFormatSearchResult(List<ContactData> contactsList) {
Lg.i(TAG, " convertFormatSearchResult ");
if (contactsList == null || contactsList.isEmpty()) {
return Lists.newArrayList();
}
List<SearchResult> searchResults = new ArrayList<>();
String phoneNumber;
String name;
String tempKey = null;
for (ContactData phones : contactsList) {
int size = phones.numbers.size();
for (int i = 0; i < size; i++) {
name = phones.getName();
phoneNumber = phones.numbers.get(i).number;
SearchResult phoneContact = new SearchResult();
phoneContact.name = name;
phoneContact.phone = phoneNumber;
// 分字
phoneContact.formatPinYin();
String pinyinInitials = PinyinUtils.cn2FirstSpell(name, false);
String pinyin = PinyinUtils.cn2Spell(name, false, true, true, "");
String initialsNumber = PinyinUtils.pinyinConvertToNumber(pinyinInitials);
String pinyinNumber = PinyinUtils.pinyinConvertToNumber(pinyin);
// 取得名字所有字母轉化爲拼音
phoneContact.pinyin = pinyin;
// 取首字母 並 轉換爲 數字
phoneContact.initials = initialsNumber;
phoneContact.pinyinInitials = pinyinInitials;
// 取所有字母 並 轉化爲 數字
phoneContact.number = pinyinNumber;
phoneContact.photoType = phones.photoType;
phoneContact.photoBytes = phones.photoBytes;
searchResults.add(phoneContact);
}
}
return searchResults;
}
b. 建立循環, 遍歷所有的已知searchResult , 查看與檢索項匹配的聯繫人, 添加到 result 中
public static List<SearchResult> searchContactByAll(final List<SearchResult> source, final String query) {
List<SearchResult> result = new ArrayList<>();
// 漢語轉拼音
String pyInput = PinyinUtils.cn2Spell(query, false, true, true, "");
String pyInitialInput = PinyinUtils.cn2FirstSpell(query, false);
for (SearchResult user : source) {
// 重置高亮
user.resetAllHighlight();
// 標識是否包含搜索內容
boolean flag = false;
// 搜索內容 不能大於 聯繫人拼音的長度
// 搜索中文的時候, 我們直接遍歷名字
if (pyInput.length() <= user.pinyin.length()) {
if(StringUtils.checkChinese(query)){
if (user.name.contains(query)) {
user.priority = 1;
if(user.name.startsWith(query)) {
// 優先級爲最大
user.priority = 0;
}
//由於名字中可能含有多音字,所以使用name.indexof
int j = user.name.indexOf(query);
//避免這裏出現數組越界
for (int i = 0; i < user.nodes.size() - j; i++) {
// 設置高亮
updateHighlight(user.nodes.get(j + i), -1);
}
result.add(user);
}
continue;
}
if (StringUtils.checkChinese(user.name)) {
if (user.name.contains(query)) {
user.priority = 1;
if(user.name.startsWith(query)) {
// 優先級爲最大
user.priority = 0;
}
//由於名字中可能含有多音字,所以使用name.indexof
int j = user.name.indexOf(query);
//避免這裏出現數組越界
for (int i = 0; i < user.nodes.size() - j; i++) {
// 設置高亮
updateHighlight(user.nodes.get(j + i), -1);
}
flag = true;
} else if (user.pinyin.toLowerCase().startsWith(pyInput.toLowerCase())) {// 直接拼音匹配
// 優先級爲1
user.priority = 2;
//避免這裏出現數組越界
for (int i = 0; i < user.nodes.size(); i++) {
updateHighlight(user.nodes.get(i), -1);
}
flag = true;
} else {
// 深層拷貝ArrayList
ArrayList<PyNode> nodes = Lists.newArrayList();
for (PyNode pyNode : user.nodes) {
try {
PyNode node = (PyNode) pyNode.clone();
node.number = node.pinyin; // 注意這裏換掉T9算法中的number,使用拼音代替!
nodes.add(node);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
// 全部轉換爲小寫
pyInput = pyInput.toLowerCase(Locale.getDefault());
// 開啓遞歸搜索
flag = pinyinRecursion(pyInput, 0, 1, 0, nodes, false);
// 優先級
user.priority = user.getNodeHighlightString().indexOf("1") + 400;
}
} else {
// 英文
String name = user.pinyin.toLowerCase(Locale.getDefault());
String key = query.toLowerCase(Locale.getDefault());
if (name.contains(key)) {
int length = key.length(), j = name.indexOf(key);
// 優先級
user.priority = j + 1200;
if(name.startsWith(key)) {
user.priority = j + 800;
}
// 設置高亮
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
sb.append(1);
}
StringBuilder highlight = new StringBuilder(user.highlight[0]);
highlight.replace(j, length + j, sb.toString());
user.highlight[0] = highlight.toString();
flag = true;
}
}
}
// 搜索電話
if (!flag && user.phone.contains(query)) {
if(user.phone.equals(query)) {
user.priority = 9997;
} else if(user.phone.startsWith(query)) {
user.priority = 9998;
} else {
user.priority = 9999 + user.phone.indexOf(query);
}
// 重置高亮
user.resetAllHighlight();
flag = true;
}
// 判斷是否匹配成功
if (flag) {
result.add(user);
}
}
Collections.sort(result, new ListComparator());
return result;
}
3.總結 :
主要工作就是兩點, 一是把名字轉化爲拼音, 二是找到匹配的聯繫人項, 並標註匹配的字串
之前一直有個問題,
1. 不支持中文全拼檢索 , 發現是已經檢索到,只是數組越界了,
2. 中文會被先轉爲拼音,然後檢索. 這個也是不對的, 正確的做法是中文檢索優先級最高