我的Android进阶之旅------>Android之AutoCompleteTextView输入汉字拼音首字母实现过滤提示(支持多音字)

来源:互联网 发布:c 编程工具 编辑:程序博客网 时间:2024/04/28 19:12

我的Android进阶之旅------>Android之AutoCompleteTextView输入汉字拼音首字母实现过滤提示(支持多音字)       

        分类:            Android应用开发698人阅读评论(2)收藏举报
AndroidAutoCompleteTextView我的Android进阶之旅输入汉字拼音首字母实现过滤提示

前言:AutoCompleteTextView介绍:

AutoCompleteTextView(自动完成文本框)是从EditText派生而出,实际上它是一个文本编辑框,但是它比普通编辑多了一个功能:当用户输入一定字符之后,自动完成文本框会显示一个下拉菜单,供用户选择,当用户选择某个菜单项后,AutoCompleteTextView按用户选择自动填写该文本框。AutoCompleteTextView除了可以使用EditText提供的XML属性和方法之外,还支持下表所示的常用XML属性和相关方法。


AutoCompleteTextView常用XML属性及相关方法XML属性相关方法说明android:completionHintsetCompletionHint(CharSequence)设置出现在下拉菜单中的提示标语android:comletionThresholdsetThreshold(int)设置至少输入几个字符才会显示提示android:dropDownHeightsetDropDownHeight(int)设置下拉菜单的高度android:dropDownHorizontalOffset
设置下拉菜单于文本框之前的水平偏移。下拉菜单默认与文本框左对齐android:dropDownVerticalOffset

设置下拉菜单于文本框之前的垂直偏移。下拉菜单默认紧跟文本框
android:dropDownWidthsetDropDownWidth(int)设置下拉菜单的宽度android:popupBackgroundsetDropDownBackgroundResource(int)设置下拉菜单的背景

使用AutoCompleteTextView很简单,只要为它设置一个Adapter,该Adapter封装了AutoCompleteTextView预设的提示文本。

step1:新建项目TestQuickSearch,项目结构如下图



step2:设计应用的的UI界面   /layout/main.xml

[html] view plaincopyprint?
  1. <?xmlversion="1.0"encoding="utf-8"?> 
  2. <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" 
  3.     android:orientation="vertical" 
  4.     android:layout_width="fill_parent" 
  5.     android:layout_height="fill_parent" 
  6.     > 
  7.     <RelativeLayout 
  8.         android:layout_width="fill_parent" 
  9.         android:layout_height="50dip" 
  10.         android:background="@drawable/title_bg_color" 
  11.         > 
  12.         <LinearLayout 
  13.             android:orientation="horizontal" 
  14.             android:layout_width="250dp"  
  15.             android:layout_height="wrap_content"  
  16.             android:layout_centerVertical="true" 
  17.             android:layout_alignParentRight="true" 
  18.             > 
  19.             <SlidingDrawer  
  20.                   android:id="@+id/slidingdrawer" 
  21.                   android:layout_width="fill_parent" 
  22.                   android:layout_height="fill_parent"  
  23.                   android:handle="@+id/handle" 
  24.                   android:content="@+id/search"  
  25.                   android:orientation="horizontal" 
  26.                   > 
  27.                   <ImageViewandroid:id="@+id/handle" 
  28.                         android:layout_width="wrap_content"  
  29.                         android:layout_height="wrap_content"  
  30.                         android:src="@drawable/search2" 
  31.                         android:scaleType="fitCenter" 
  32.                         /> 
  33.                    <AutoCompleteTextView  
  34.                         android:id="@+id/search" 
  35.                         android:layout_width="fill_parent"  
  36.                         android:layout_height="wrap_content"  
  37.                         android:hint="个股查询" 
  38.                         android:singleLine="true" 
  39.                         android:completionThreshold="1" 
  40.                         /> 
  41.             </SlidingDrawer> 
  42.         </LinearLayout> 
  43.     </RelativeLayout> 
  44.      
  45. </LinearLayout> 

  /res/drawable/title_bg_color.xml

[html] view plaincopyprint?
  1. <?xmlversion="1.0"encoding="utf-8"?> 
  2.     <!--
  3.     --> 
  4. <shape xmlns:android="http://schemas.android.com/apk/res/android"> 
  5.     <gradient  
  6.         android:startColor="#505050"  
  7.         android:centerColor="#202020" 
  8.         android:endColor="#000000"  
  9.         android:angle="270"/> 
  10.     <cornersandroid:radius="0dip"/> 
  11. </shape> 


AutoCompleteTextView具有输入提示的功能,但是它的这种提示不适合对股票列表的过滤,如果你玩过股票软件,就会知道只要输入股票名称的首字母或股票代码就会出现符合匹配的股票,这种过滤怎么实现呢?

还有个问题,汉字具有多音字,如何实现多音字的匹配,比如“长江证券”,无论你输入“cjzq”或者“zjzq”都会匹配到它,这都是需要解决的问题!

匹配的关键在于重写BaseAdapter,让它实现Filterable接口,重写其中的getFilter(),如果你参照ArrayAdaper源码的话,写起来就会容易很多,事实上我就是这么做的。

step3:SearchAdapter.java

[java] view plaincopyprint?
  1. package cn.roco.quicksearch.util; 
  2.  
  3. import java.util.*; 
  4.  
  5. import android.content.Context; 
  6. import android.util.Log; 
  7. import android.view.*; 
  8. import android.widget.BaseAdapter; 
  9. import android.widget.Filter; 
  10. import android.widget.Filterable; 
  11. import android.widget.TextView; 
  12.  
  13. public class SearchAdapter<T>extends BaseAdapter implements Filterable { 
  14.     private List<T> mObjects; 
  15.  
  16.     private List<Set<String>> pinyinList;//支持多音字,类似:{{z,c},{j},{z},{q,x}}的集合 
  17.  
  18.     private final Object mLock =new Object(); 
  19.  
  20.     private int mResource; 
  21.  
  22.     private int mFieldId =0
  23.  
  24.     private Context mContext; 
  25.  
  26.     private ArrayList<T> mOriginalValues; 
  27.     private ArrayFilter mFilter; 
  28.  
  29.     private LayoutInflater mInflater; 
  30.  
  31.     public staticfinal int ALL=-1;//全部 
  32.     private int maxMatch=10;//最多显示多少个可能选项 
  33.     /**
  34.      * 支持多音字
  35.      */ 
  36.     public SearchAdapter(Context context,int textViewResourceId, T[] objects,int maxMatch) { 
  37.         // TODO Auto-generated constructor stub 
  38.         init(context, textViewResourceId, 0, Arrays.asList(objects)); 
  39.         this.pinyinList = getHanziSpellList(objects); 
  40.         this.maxMatch=maxMatch; 
  41.     } 
  42.      
  43.     public SearchAdapter(Context context,int textViewResourceId, List<T> objects,int maxMatch) { 
  44.         // TODO Auto-generated constructor stub 
  45.         init(context, textViewResourceId, 0, objects); 
  46.         this.pinyinList = getHanziSpellList(objects); 
  47.         this.maxMatch=maxMatch; 
  48.     } 
  49.      
  50.     private void init(Context context,int resource, int textViewResourceId,List<T> objects) { 
  51.         mContext = context; 
  52.         mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
  53.         mResource = resource; 
  54.         mObjects = objects; 
  55.         mFieldId = textViewResourceId; 
  56.     } 
  57.  
  58.      
  59.     /**
  60.      * 获得汉字拼音首字母列表
  61.      */ 
  62.     private List<Set<String>> getHanziSpellList(T[] hanzi){ 
  63.         List<Set<String>> listSet=new ArrayList<Set<String>>(); 
  64.         PinYin4j pinyin=new PinYin4j(); 
  65.         for(int i=0;i<hanzi.length;i++){ 
  66.             listSet.add(pinyin.getPinyin(hanzi[i].toString())); 
  67.         } 
  68.         return listSet; 
  69.     } 
  70.     /**
  71.      * 获得汉字拼音首字母列表
  72.      */ 
  73.     private List<Set<String>> getHanziSpellList(List<T> hanzi){ 
  74.         List<Set<String>> listSet=new ArrayList<Set<String>>(); 
  75.         PinYin4j pinyin=new PinYin4j(); 
  76.         for(int i=0;i<hanzi.size();i++){ 
  77.             listSet.add(pinyin.getPinyin(hanzi.get(i).toString())); 
  78.         } 
  79.         return listSet; 
  80.     } 
  81.      
  82.     public int getCount() { 
  83.         return mObjects.size(); 
  84.     } 
  85.  
  86.     public T getItem(int position) { 
  87.         return mObjects.get(position); 
  88.     } 
  89.  
  90.     public int getPosition(T item) { 
  91.         return mObjects.indexOf(item); 
  92.     } 
  93.  
  94.     public long getItemId(int position) { 
  95.         return position; 
  96.     } 
  97.  
  98.     public View getView(int position, View convertView, ViewGroup parent) { 
  99.         return createViewFromResource(position, convertView, parent, mResource); 
  100.     } 
  101.  
  102.     private View createViewFromResource(int position, View convertView, 
  103.             ViewGroup parent, int resource) { 
  104.         View view; 
  105.         TextView text; 
  106.  
  107.         if (convertView ==null) { 
  108.             view = mInflater.inflate(resource, parent, false); 
  109.         } else
  110.             view = convertView; 
  111.         } 
  112.  
  113.         try
  114.             if (mFieldId == 0) { 
  115.                 text = (TextView) view; 
  116.             } else
  117.                 text = (TextView) view.findViewById(mFieldId); 
  118.             } 
  119.         } catch (ClassCastException e) { 
  120.             Log.e("ArrayAdapter"
  121.                     "You must supply a resource ID for a TextView"); 
  122.             throw new IllegalStateException( 
  123.                     "ArrayAdapter requires the resource ID to be a TextView", e); 
  124.         } 
  125.  
  126.         text.setText(getItem(position).toString()); 
  127.  
  128.         return view; 
  129.     } 
  130.  
  131.     public Filter getFilter() { 
  132.         if (mFilter == null) { 
  133.             mFilter = new ArrayFilter(); 
  134.         } 
  135.         return mFilter; 
  136.     } 
  137.  
  138.     private class ArrayFilterextends Filter { 
  139.         @Override 
  140.         protected FilterResults performFiltering(CharSequence prefix) { 
  141.             FilterResults results = new FilterResults(); 
  142.  
  143.             if (mOriginalValues ==null) { 
  144.                 synchronized (mLock) { 
  145.                     mOriginalValues = new ArrayList<T>(mObjects);// 
  146.                 } 
  147.             } 
  148.  
  149.             if (prefix ==null || prefix.length() == 0) { 
  150.                 synchronized (mLock) { 
  151. //                  ArrayList<T> list = new ArrayList<T>();//无 
  152.                     ArrayList<T> list = new ArrayList<T>(mOriginalValues);//List<T> 
  153.                     results.values = list; 
  154.                     results.count = list.size(); 
  155.                 } 
  156.             } else
  157.                 String prefixString = prefix.toString().toLowerCase(); 
  158.  
  159.                 final ArrayList<T> hanzi = mOriginalValues;//汉字String 
  160.                 final int count = hanzi.size(); 
  161.  
  162.                 final Set<T> newValues =new HashSet<T>(count);//支持多音字,不重复 
  163.  
  164.                 for (int i =0; i < count; i++) { 
  165.                     final T value = hanzi.get(i);//汉字String 
  166.                     final String valueText = value.toString().toLowerCase();//汉字String 
  167.                     final Set<String> pinyinSet=pinyinList.get(i);//支持多音字,类似:{z,c} 
  168.                     Iterator iterator= pinyinSet.iterator();//支持多音字 
  169.                     while (iterator.hasNext()) {//支持多音字 
  170.                         final String pinyin = iterator.next().toString().toLowerCase();//取出多音字里的一个字母 
  171.                          
  172.                         if (pinyin.indexOf(prefixString)!=-1) {//任意匹配 
  173.                             newValues.add(value); 
  174.                         }  
  175.                         elseif (valueText.indexOf(prefixString)!=-1) {//如果是汉字则直接添加 
  176.                             newValues.add(value); 
  177.                         } 
  178.                     } 
  179.                     if(maxMatch>0){//有数量限制 
  180.                         if(newValues.size()>maxMatch-1){//不要太多 
  181.                             break
  182.                         } 
  183.                     } 
  184.                      
  185.                 } 
  186.                 List<T> list=Set2List(newValues);//转成List 
  187.                 results.values = list; 
  188.                 results.count = list.size(); 
  189.             } 
  190.             return results; 
  191.         } 
  192.  
  193.         protected void publishResults(CharSequence constraint,FilterResults results) { 
  194.  
  195.             mObjects = (List<T>) results.values; 
  196.             if (results.count > 0) { 
  197.                 notifyDataSetChanged(); 
  198.             } else
  199.                 notifyDataSetInvalidated(); 
  200.             } 
  201.         } 
  202.     } 
  203.      
  204.     //List Set 相互转换 
  205.     public <T extends Object> Set<T> List2Set(List<T> tList) {    
  206.         Set<T> tSet = new HashSet<T>(tList);    
  207.         //TODO 具体实现看需求转换成不同的Set的子类。   
  208.         return tSet;    
  209.     } 
  210.     public <T extends Object> List<T> Set2List(Set<T> oSet) {    
  211.         List<T> tList = new ArrayList<T>(oSet);    
  212.         // TODO 需要在用到的时候另外写构造,根据需要生成List的对应子类。    
  213.         return tList;    
  214.     } 


在源码当中使用了PinYin4j去获得汉字的首字母,由于可能是多音字,所以将每个汉字的拼音都放在了Set中。当然PinYin4j很多强大的功能在这里都用不到,所以被我统统去掉了,这样大大提高了匹配效率。

step4:再看一下PinYin4j.java:

[java] view plaincopyprint?
  1. package cn.roco.quicksearch.util; 
  2.  
  3. import java.util.HashSet; 
  4. import java.util.Set; 
  5.  
  6. public class PinYin4j { 
  7.  
  8.     public PinYin4j() { 
  9.     } 
  10.  
  11.     /**
  12.      * 字符串集合转换字符串(逗号分隔)
  13.      *
  14.      * @author wyh
  15.      * @param stringSet
  16.      * @return
  17.      */ 
  18.     public String makeStringByStringSet(Set<String> stringSet) { 
  19.         StringBuilder str = new StringBuilder(); 
  20.         int i = 0
  21.         for (String s : stringSet) { 
  22.             if (i == stringSet.size() -1) { 
  23.                 str.append(s); 
  24.             } else
  25.                 str.append(s + ","); 
  26.             } 
  27.             i++; 
  28.         } 
  29.         return str.toString().toLowerCase(); 
  30.     } 
  31.  
  32.     /**
  33.      * 获取拼音集合
  34.      *
  35.      * @author wyh
  36.      * @param src
  37.      * @return Set<String>
  38.      */ 
  39.     public Set<String> getPinyin(String src) { 
  40.         char[] srcChar; 
  41.         srcChar = src.toCharArray(); 
  42.  
  43.         // 1:多少个汉字 
  44.         // 2:每个汉字多少种读音 
  45.         String[][] temp = new String[src.length()][]; 
  46.         for (int i =0; i < srcChar.length; i++) { 
  47.             char c = srcChar[i]; 
  48.             // 是中文或者a-z或者A-Z转换拼音(我的需求,是保留中文或者a-z或者A-Z) 
  49.             if (String.valueOf(c).matches("[\\u4E00-\\u9FA5]+")) {// 中文 
  50.                 String[] t = PinyinHelper 
  51.                         .getUnformattedHanyuPinyinStringArray(c); 
  52.                 temp[i] = new String[t.length]; 
  53.                 for (int j =0; j < t.length; j++) { 
  54.                     // temp[i][j]=t[j].substring(0,1);//获取首字母 
  55.                     temp[i][j] = t[j];// //获取首字母,不需要再截取了 
  56.                 } 
  57.             } else if (((int) c >=65 && (int) c <=90
  58.                     || ((int) c >= 97 && (int) c <=122) || c >= 48 && c <=57 
  59.                     || c == 42) {// a-zA-Z0-9* 
  60.                 temp[i] = new String[] { String.valueOf(srcChar[i]) }; 
  61.             } else
  62.                 temp[i] = new String[] {"null!" }; 
  63.             } 
  64.  
  65.         } 
  66.         return paiLie(temp);// 为了去掉重复项,直接返回Set  
  67.     } 
  68.  
  69.     /*
  70.      * 求2维数组所有排列组合情况 比如:{{1,2},{3},{4},{5,6}}共有2中排列,为:1345,1346,2345,2346
  71.      */ 
  72.     private Set<String> paiLie(String[][] str) { 
  73.         int max = 1
  74.         for (int i =0; i < str.length; i++) { 
  75.             max *= str[i].length; 
  76.         } 
  77.         Set<String> result = new HashSet<String>(); 
  78.         for (int i =0; i < max; i++) { 
  79.             String s = ""
  80.             int temp = 1;// 注意这个temp的用法。 
  81.             for (int j =0; j < str.length; j++) { 
  82.                 temp *= str[j].length; 
  83.                 s += str[j][i / (max / temp) % str[j].length]; 
  84.             } 
  85.             result.add(s); 
  86.         } 
  87.  
  88.         return result; 
  89.     } 
  90.  
  91.     /**
  92.      * @param args
  93.      */ 
  94.     public staticvoid main(String[] args) { 
  95.         // nongyeyinheng,nongyeyinhang,nongyeyinxing 
  96.         PinYin4j t = new PinYin4j(); 
  97.         String str = "农业银行1234567890abcdefghijklmnopqrstuvwxyz*"
  98.         System.out.println(t.makeStringByStringSet(t.getPinyin(str))); 
  99.     } 
  100.  


这只是一个工具类,它使用到了PinyinHelper,PinyinHelper是加载字库文件用的,字库文件为/assets/unicode_to_hanyu_pinyin.txt,里面每一个汉字都对应着几个读音。

step5:PinyinHelper.java

[java] view plaincopyprint?
  1. package cn.roco.quicksearch.util; 
  2.  
  3. import java.io.BufferedInputStream; 
  4. import java.io.FileNotFoundException; 
  5. import java.io.IOException; 
  6. import java.util.Properties; 
  7.  
  8. public class PinyinHelper{ 
  9.     private static PinyinHelper instance; 
  10.     private Properties properties = null
  11.      
  12.     public static String[] getUnformattedHanyuPinyinStringArray(char ch){ 
  13.         return getInstance().getHanyuPinyinStringArray(ch); 
  14.     } 
  15.  
  16.     private PinyinHelper(){ 
  17.         initResource(); 
  18.     } 
  19.  
  20.     public static PinyinHelper getInstance(){ 
  21.         if(instance==null){ 
  22.             instance = new PinyinHelper(); 
  23.         } 
  24.         return instance; 
  25.     } 
  26.      
  27.     private void initResource(){ 
  28.         try
  29. //          final String resourceName = "/assets/unicode_to_hanyu_pinyin.txt"; 
  30.           final String resourceName ="/assets/unicode_to_simple_pinyin.txt"
  31.  
  32.           BufferedInputStream bis=new BufferedInputStream(PinyinHelper.class.getResourceAsStream(resourceName)); 
  33.           properties=new Properties(); 
  34.           properties.load(bis); 
  35.  
  36.         } catch (FileNotFoundException ex){ 
  37.             ex.printStackTrace(); 
  38.         } catch (IOException ex){ 
  39.             ex.printStackTrace(); 
  40.         } 
  41.     } 
  42.  
  43.     private String[] getHanyuPinyinStringArray(char ch){ 
  44.         String pinyinRecord = getHanyuPinyinRecordFromChar(ch); 
  45.  
  46.         if (null != pinyinRecord){ 
  47.             int indexOfLeftBracket = pinyinRecord.indexOf(Field.LEFT_BRACKET); 
  48.             int indexOfRightBracket = pinyinRecord.lastIndexOf(Field.RIGHT_BRACKET); 
  49.  
  50.             String stripedString = pinyinRecord.substring(indexOfLeftBracket 
  51.                     + Field.LEFT_BRACKET.length(), indexOfRightBracket); 
  52.  
  53.             return stripedString.split(Field.COMMA); 
  54.  
  55.         } else 
  56.             return null
  57.          
  58.     } 
  59.      
  60.     private String getHanyuPinyinRecordFromChar(char ch){ 
  61.         int codePointOfChar = ch; 
  62.         String codepointHexStr = Integer.toHexString(codePointOfChar).toUpperCase(); 
  63.         String foundRecord = properties.getProperty(codepointHexStr); 
  64.         return foundRecord; 
  65.     } 
  66.  
  67.     class Field{ 
  68.         static final String LEFT_BRACKET ="("
  69.         static final String RIGHT_BRACKET =")"
  70.         static final String COMMA =","
  71.     } 
  72.      


至于解析字库,比如有一个汉字是这样的格式:4E01 (ding1,zheng1),保存 到String[]当中就是{"ding1","zheng1"}这样的。但是这样的话到了PinYin4j中还需要使用substring(0,1)截取首字母,效率有些低了,事实上文件中完全可以采用这样的格式存储:E01 (d,z),直接存汉字的首字母就行了,这个另论!


step6:最后,看看使用方法:QuickSearchActivity.java


[java] view plaincopyprint?
  1. package cn.roco.quicksearch; 
  2.  
  3. import android.app.Activity; 
  4. import android.os.Bundle; 
  5. import android.util.Log; 
  6. import android.view.View; 
  7. import android.widget.AdapterView; 
  8. import android.widget.AdapterView.OnItemClickListener; 
  9. import android.widget.AutoCompleteTextView; 
  10. import android.widget.SlidingDrawer; 
  11. import cn.roco.quicksearch.util.SearchAdapter; 
  12.  
  13. public class QuickSearchActivityextends Activity { 
  14.     private staticfinal String tag = "QuickSearchActivity"
  15.     private AutoCompleteTextView search; 
  16.     private SlidingDrawer mDrawer; 
  17.  
  18.     public SearchAdapter adapter = null;// 
  19.     // 需要读取 
  20.     public String[] hanzi = new String[] {"长江证券100002", "长江证券100001"
  21.             "农业银行200001","工商银行300001", "招商银行100001","建设银行100001"
  22.             "中国银行100002", "华夏银行500002","上海银行100010", "浦发银行200009" 
  23.             }; 
  24.  
  25.     /** Called when the activity is first created. */ 
  26.     @Override 
  27.     public void onCreate(Bundle savedInstanceState) { 
  28.         super.onCreate(savedInstanceState); 
  29.         setContentView(R.layout.main); 
  30.  
  31.         initViews(); 
  32.     } 
  33.  
  34.     private void initViews() { 
  35.         search = (AutoCompleteTextView) findViewById(R.id.search); 
  36.         search.setOnItemClickListener(new OnItemClickListener() { 
  37.  
  38.             @Override 
  39.             public void onItemClick(AdapterView<?> arg0, View arg1, 
  40.                     int position, long id) { 
  41.                 // TODO Auto-generated method stub 
  42.                 Log.d(tag, "onItemClick:" + position); 
  43.             } 
  44.  
  45.         }); 
  46.  
  47.         search.setThreshold(1); 
  48.  
  49.         adapter = new SearchAdapter<String>(this
  50.                 android.R.layout.simple_dropdown_item_1line, hanzi, 
  51.                 SearchAdapter.ALL);//速度优先  
  52.         search.setAdapter(adapter);// 
  53.  
  54.         mDrawer = (SlidingDrawer) findViewById(R.id.slidingdrawer); 
  55.  
  56.     } 
  57.  

step7:部署应用到模拟器上,查看运行效果

                                         




项目源代码可以在以下地址下载:

  1. http://pan.baidu.com/share/link?shareid=412293&uk=805959799
  2. http://download.csdn.net/detail/qq446282412/6467519