完整源码在我的github上 https://github.com/NashLegend/QuicKid
有了匹配算法,下面是如何将搜索联系人并显示出来以及高亮显示匹配到的字符串。
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<Contact> baseList = new ArrayList<Contact>(); ArrayList<Contact> resultList = new ArrayList<Contact>(); if (preLength > 0 && (preLength == queryLength - 1) && queryString.startsWith(preQueryString)) { //如果本次搜索的字符串是上次搜索的字符串开头,那么将只在contacts里面搜索(contacts是当前列表的数据集合) baseList = contacts; } else { //过滤所有联系人 baseList = AllContacts; } for (Iterator<Contact> 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 < contact.matchValue.pairs.size(); 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<PointPair> pa = getColoredString( contact.fullNameNumber .get(contact.matchValue.nameIndex), contact.matchValue.pairs, "#FF0000"); SpannableStringBuilder builder = new SpannableStringBuilder(str); for (Iterator<PointPair> 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<PointPair> getColoredString(ArrayList<String> strings, ArrayList<PointPair> pairs, String color) { int k = 0; int idx = -1; int crtHead = -1; int crtTail = -1; ArrayList<PointPair> ps = new ArrayList<PointPair>(); for (int i = 0; i < strings.size(); i++) { String str = strings.get(i); for (int j = 0; j < str.length() && k < pairs.size(); 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; }
本文出自 “NashLegend” 博客,请务必保留此出处http://nashlegend.blog.51cto.com/5635342/1566479
原文地址:http://nashlegend.blog.51cto.com/5635342/1566479