Android搜索匹配算法,智能拨号匹配算法(三)

有了匹配算法,下面是如何将搜索联系人并显示出来以及高亮显示匹配到的字符串。

1.搜索并显示联系人

显示列表当然是使用ListView,使用自定义的ContactAdapter,ContactAdapter继承BaseAdapter,并实现了Filterable接口,此接口中有一个getFilter方法,返回一个过滤用的类Filter,Filter需要自己实现,我们就是通过这个Filter实现搜索的。

Filter类有两个方法,publishResults和performFiltering方法,其中publishResults运行在UI线程,而performFiltering运行在其他线程,搜索的过程就在performFiltering中执行。

下面是自己实现的Filter类@Override

public Filter getFilter() {

return filter;

}

// 上一次搜索的字符串

private String preQueryString = "";

private Filter filter = new Filter() {

@Override

protected void publishResults(CharSequence constraint,

FilterResults results) {

if (results != null) {

if (results.count > 0) {

notifyDataSetChanged();

} else {

notifyDataSetInvalidated();

}

}

}

@Override

synchronized protected FilterResults performFiltering(CharSequence constraint) {

if (TextUtils.isEmpty(constraint)

|| preQueryString.equals(constraint)) {

return null;

}

String queryString = constraint.toString();

FilterResults results = new FilterResults();

int preLength = preQueryString.length();

int queryLength = queryString.length();

ArrayList baseList = new ArrayList();

ArrayList resultList = new ArrayList();

if (preLength > 0 && (preLength == queryLength - 1)

&& queryString.startsWith(preQueryString)) {

//如果本次搜索的字符串是上次搜索的字符串开头,那么将只在contacts里面搜索(contacts是当前列表的数据集合)

baseList = contacts;

} else {

//过滤所有联系人

baseList = AllContacts;

}

for (Iterator iterator = baseList.iterator(); iterator

.hasNext();) {

Contact contact = (Contact) iterator.next();

if (contact.match(queryString) > 0) {

resultList.add(contact);

}

}

sortContact(resultList);// 这是ContactAdapter中的方法,将ContactAdapter的数据换成resultList。

preQueryString = queryString;

results.values = resultList;

results.count = resultList.size();

setContacts(resultList);

return results;

}

};

如果用户搜索的手速十分快的话将会带来线程同步的问题。在执行performFiltering的时候有可能正在执行ContactAdapter的getView方法,而match()方法是有可能改变Contact的数据的,这将导致显示出错。比如未匹配到结果的话,Contact的匹配结果的nameIndex会是-1,如果在上次搜索中某用户成功匹配,nameIndex=0,就意味着将取用户的第一种拼音组合做为匹配结果,但是如果手速过快,在执行getView之前就进行了下一次搜索,那么有可能这个联系人不再匹配,这里的nameIndex将会是-1,取第-1个拼音的时候就会报错。这里的解决方法很简单,并没有做过多的保证同步的工作(让getView,publishResults和performFiltering不互相打断貌似是很困难的),所以如果发现nameIndex不对,就直接不显示这个拼音,因为用户操作非常之快,他是无法发现也没必要关心这几十毫秒的显示不正常的。

还有一个线程同步的问题,在notifyDataSetChanged之后,adapter会顺序执行getView,但是在getView的时候,setContacts可能又会执行,从而改变了contacts的长度,contacts.get(position)可能会发生越界的问题,因此这时候getView要捕获这个错误,返回一个空view,跟上次一样,空view存在时间很短,不会有人注意的……

搜索某个单词的时候,使用getFilter.filter(queryString)即可实现搜索。剩下的不用多说,都是普通的adapter和listview的问题。

2.高亮显示匹配的字符串

高亮显示匹配的字符串使用户知道是如何匹配的。比如输入pan得出结果PanAnNing的时候,高亮的是三个首字母PanAnNing.高亮这里用的是SpannableStringBuilder。

高亮方法如下if (contact.matchValue.matchLevel == Contact.Level_Complete) {

//如果是完全匹配,那么只要全部高亮对应的姓名拼音或者电话号码就OK了

if (contact.matchValue.matchType == Contact.Match_Type_Name) {

String str = contact.fullNamesString.get(

contact.matchValue.nameIndex).replaceAll(" ", "");

SpannableStringBuilder builder = new SpannableStringBuilder(

str);

ForegroundColorSpan redSpan = new ForegroundColorSpan(

Color.RED);

builder.setSpan(redSpan, 0, str.length(),

Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

pinyinTextView.setText(builder);

} else {

shouldDisplayMorePhones = false;

String str = contact.getPhones().get(

contact.matchValue.nameIndex).phoneNumber;

SpannableStringBuilder builder = new SpannableStringBuilder(

str);

ForegroundColorSpan redSpan = new ForegroundColorSpan(

Color.RED);

builder.setSpan(redSpan, 0, str.length(),

Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

phoneTextView.setText(builder);

}

} else if (contact.matchValue.matchLevel == Contact.Level_Headless) {

//如果是后置无头匹配,那么高亮从strIndex开始的regString长度的一串就行了

shouldDisplayMorePhones = false;

String str = contact.getPhones().get(

contact.matchValue.nameIndex).phoneNumber;

SpannableStringBuilder builder = new SpannableStringBuilder(str);

ForegroundColorSpan redSpan = new ForegroundColorSpan(Color.RED);

builder.setSpan(redSpan,

contact.matchValue.pairs.get(0).strIndex,

contact.matchValue.pairs.get(0).strIndex

+ contact.matchValue.reg.length(),

Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

phoneTextView.setText(builder);

for (int i = 1; i 

int idx = contact.matchValue.pairs.get(i).listIndex;

PhoneStruct phoneStruct = contact.getPhones().get(idx);

PhoneView phoneView = new PhoneView(getContext());

phoneView.setPhone(phoneStruct, contact.matchValue.reg);

phoneViews.addView(phoneView);

}

} else {

// 剩下的情况就是两个首字母匹配了。首字母匹配到的字符串位置不是连续的

// 匹配到的字母一个一个记录在contact.matchValue.pairs里面

// 所以要先将contact.matchValue.pairs里的一个个不连续的字母连接成几个字符串

String str = contact.fullNamesString.get(

contact.matchValue.nameIndex).replaceAll(" ", "");

ArrayList pa = getColoredString(

contact.fullNameNumber

.get(contact.matchValue.nameIndex),

contact.matchValue.pairs, "#FF0000");

SpannableStringBuilder builder = new SpannableStringBuilder(str);

for (Iterator iterator = pa.iterator(); iterator

.hasNext();) {

PointPair pointPair = iterator.next();

builder.setSpan(new ForegroundColorSpan(Color.RED),

pointPair.listIndex, pointPair.strIndex,

Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

}

pinyinTextView.setText(builder);

}

// getColoredString是将PointPairs列表单个的字符转化成几个字符串范围。这时候返回的PointPair的listIndex

// 变成了字符串开关的位置,strIndex变成了长度。builder.setSpan将使这几段范围内的字符高亮

private ArrayList getColoredString(ArrayList strings,

ArrayList pairs, String color) {

int k = 0;

int idx = -1;

int crtHead = -1;

int crtTail = -1;

ArrayList ps = new ArrayList();

for (int i = 0; i 

String str = strings.get(i);

for (int j = 0; j 

idx++;

if (pairs.get(k).listIndex == i && pairs.get(k).strIndex == j) {

if (crtHead == -1) {

crtHead = idx;

crtTail = idx + 1;

} else {

if (crtTail == idx) {

crtTail = idx + 1;

}

}

k++;

} else {

if (crtHead != -1) {

ps.add(new PointPair(crtHead, crtTail));

crtHead = -1;

crtTail = -1;

}

}

}

}

if (crtHead != -1) {

ps.add(new PointPair(crtHead, crtTail));

crtHead = -1;

crtTail = -1;

}

return ps;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值