AutoCompleteTextView输入汉字拼音首字母实现过滤提示(支持多音字,Filterable的使用)

时间:2023-03-08 17:14:22

AutoCompleteTextView具有输入提示的功能,但是它的这种提示不适合对股票列表的过滤,如果你玩过股票软件,就会知道只要输入股票名称的首字母或股票代码就会出现符合匹配的股票,这种过滤怎么实现呢?
还有个问题,汉字具有多音字,如何实现多音字的匹配,比如“长江证券”,无论你输入“cjzq”或者“zjzq”都会匹配到它,这都是需要解决的问题!
匹配的关键在于重写BaseAdapter,让它实现Filterable接口,重写其中的getFilter(),如果你参照ArrayAdaper源码的话,写起来就会容易很多,事实上我就是这么做的,^o^
下面看一下源码:

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

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

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

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

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

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

最后,看看使用方法:

  1. public class QuickSearchActivity extends Activity {
  2. private static final String tag="QuickSearchActivity";
  3. private AutoCompleteTextView search ;
  4. private SlidingDrawer mDrawer;
  5. public SearchAdapter adapter=null;//
  6. //需要读取
  7. public String[] hanzi = new String[] {
  8. "长江证券100002","长江证券100001", "农业银行200001","工商银行300001" ,
  9. "招商银行100001", "建设银行100001", "中国银行100002", "华夏银行500002",
  10. "上海银行100010", "浦发银行200009"
  11. };
  12. /** Called when the activity is first created. */
  13. @Override
  14. public void onCreate(Bundle savedInstanceState) {
  15. super.onCreate(savedInstanceState);
  16. setContentView(R.layout.main);
  17. initViews();
  18. }
  19. private void initViews(){
  20. search = (AutoCompleteTextView) findViewById(R.id.search);
  21. search.setOnItemClickListener(new OnItemClickListener() {
  22. @Override
  23. public void onItemClick(AdapterView<?> arg0, View arg1, int position,
  24. long id) {
  25. // TODO Auto-generated method stub
  26. Log.d(tag, "onItemClick:"+position);
  27. }
  28. });
  29. search.setThreshold(1);
  30. adapter = new SearchAdapter<String>(this,
  31. android.R.layout.simple_dropdown_item_1line, hanzi,SearchAdapter.ALL);//速度优先
  32. search.setAdapter(adapter);//
  33. mDrawer = (SlidingDrawer) findViewById(R.id.slidingdrawer);
  34. }
  35. }