您的位置:首页 > 移动开发 > Android开发

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

2013-04-20 05:23 841 查看
前言: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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="50dip"
android:background="@drawable/title_bg_color"
>
<LinearLayout
android:orientation="horizontal"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
>
<SlidingDrawer
android:id="@+id/slidingdrawer"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:handle="@+id/handle"
android:content="@+id/search"
android:orientation="horizontal"
>
<ImageView android:id="@+id/handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/search2"
android:scaleType="fitCenter"
/>
<AutoCompleteTextView
android:id="@+id/search"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="个股查询"
android:singleLine="true"
android:completionThreshold="1"
/>
</SlidingDrawer>
</LinearLayout>
</RelativeLayout>

</LinearLayout>


/res/drawable/title_bg_color.xml

<?xml version="1.0" encoding="utf-8"?>
<!--
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#505050"
android:centerColor="#202020"
android:endColor="#000000"
android:angle="270"/>
<corners android:radius="0dip" />
</shape>


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

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

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

step3:SearchAdapter.java

package cn.roco.quicksearch.util;

import java.util.*;

import android.content.Context;
import android.util.Log;
import android.view.*;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;

public class SearchAdapter<T> extends BaseAdapter implements Filterable {
private List<T> mObjects;

private List<Set<String>> pinyinList;//支持多音字,类似:{{z,c},{j},{z},{q,x}}的集合

private final Object mLock = new Object();

private int mResource;

private int mFieldId = 0;

private Context mContext;

private ArrayList<T> mOriginalValues;
private ArrayFilter mFilter;

private LayoutInflater mInflater;

public static final int ALL=-1;//全部
private int maxMatch=10;//最多显示多少个可能选项
/**
* 支持多音字
*/
public SearchAdapter(Context context,int textViewResourceId, T[] objects,int maxMatch) {
// TODO Auto-generated constructor stub
init(context, textViewResourceId, 0, Arrays.asList(objects));
this.pinyinList = getHanziSpellList(objects);
this.maxMatch=maxMatch;
}

public SearchAdapter(Context context,int textViewResourceId, List<T> objects,int maxMatch) {
// TODO Auto-generated constructor stub
init(context, textViewResourceId, 0, objects);
this.pinyinList = getHanziSpellList(objects);
this.maxMatch=maxMatch;
}

private void init(Context context, int resource, int textViewResourceId,List<T> objects) {
mContext = context;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mResource = resource;
mObjects = objects;
mFieldId = textViewResourceId;
}

/**
* 获得汉字拼音首字母列表
*/
private List<Set<String>> getHanziSpellList(T[] hanzi){
List<Set<String>> listSet=new ArrayList<Set<String>>();
PinYin4j pinyin=new PinYin4j();
for(int i=0;i<hanzi.length;i++){
listSet.add(pinyin.getPinyin(hanzi[i].toString()));
}
return listSet;
}
/**
* 获得汉字拼音首字母列表
*/
private List<Set<String>> getHanziSpellList(List<T> hanzi){
List<Set<String>> listSet=new ArrayList<Set<String>>();
PinYin4j pinyin=new PinYin4j();
for(int i=0;i<hanzi.size();i++){
listSet.add(pinyin.getPinyin(hanzi.get(i).toString()));
}
return listSet;
}

public int getCount() {
return mObjects.size();
}

public T getItem(int position) {
return mObjects.get(position);
}

public int getPosition(T item) {
return mObjects.indexOf(item);
}

public long getItemId(int position) {
return position;
}

public View getView(int position, View convertView, ViewGroup parent) {
return createViewFromResource(position, convertView, parent, mResource);
}

private View createViewFromResource(int position, View convertView,
ViewGroup parent, int resource) {
View view;
TextView text;

if (convertView == null) {
view = mInflater.inflate(resource, parent, false);
} else {
view = convertView;
}

try {
if (mFieldId == 0) {
text = (TextView) view;
} else {
text = (TextView) view.findViewById(mFieldId);
}
} catch (ClassCastException e) {
Log.e("ArrayAdapter",
"You must supply a resource ID for a TextView");
throw new IllegalStateException(
"ArrayAdapter requires the resource ID to be a TextView", e);
}

text.setText(getItem(position).toString());

return view;
}

public Filter getFilter() {
if (mFilter == null) {
mFilter = new ArrayFilter();
}
return mFilter;
}

private class ArrayFilter extends Filter {
@Override
protected FilterResults performFiltering(CharSequence prefix) {
FilterResults results = new FilterResults();

if (mOriginalValues == null) {
synchronized (mLock) {
mOriginalValues = new ArrayList<T>(mObjects);//
}
}

if (prefix == null || prefix.length() == 0) {
synchronized (mLock) {
//					ArrayList<T> list = new ArrayList<T>();//无
ArrayList<T> list = new ArrayList<T>(mOriginalValues);//List<T>
results.values = list;
results.count = list.size();
}
} else {
String prefixString = prefix.toString().toLowerCase();

final ArrayList<T> hanzi = mOriginalValues;//汉字String
final int count = hanzi.size();

final Set<T> newValues = new HashSet<T>(count);//支持多音字,不重复

for (int i = 0; i < count; i++) {
final T value = hanzi.get(i);//汉字String
final String valueText = value.toString().toLowerCase();//汉字String
final Set<String> pinyinSet=pinyinList.get(i);//支持多音字,类似:{z,c}
Iterator iterator= pinyinSet.iterator();//支持多音字
while (iterator.hasNext()) {//支持多音字
final String pinyin = iterator.next().toString().toLowerCase();//取出多音字里的一个字母

if (pinyin.indexOf(prefixString)!=-1) {//任意匹配
newValues.add(value);
}
else if (valueText.indexOf(prefixString)!=-1) {//如果是汉字则直接添加
newValues.add(value);
}
}
if(maxMatch>0){//有数量限制
if(newValues.size()>maxMatch-1){//不要太多
break;
}
}

}
List<T> list=Set2List(newValues);//转成List
results.values = list;
results.count = list.size();
}
return results;
}

protected void publishResults(CharSequence constraint,FilterResults results) {

mObjects = (List<T>) results.values;
if (results.count > 0) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}
}

//List Set 相互转换
public <T extends Object> Set<T> List2Set(List<T> tList) {
Set<T> tSet = new HashSet<T>(tList);
//TODO 具体实现看需求转换成不同的Set的子类。
return tSet;
}
public <T extends Object> List<T> Set2List(Set<T> oSet) {
List<T> tList = new ArrayList<T>(oSet);
// TODO 需要在用到的时候另外写构造,根据需要生成List的对应子类。
return tList;
}
}


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

step4:再看一下PinYin4j.java:

package cn.roco.quicksearch.util;

import java.util.HashSet;
import java.util.Set;

public class PinYin4j {

public PinYin4j() {
}

/**
* 字符串集合转换字符串(逗号分隔)
*
* @author wyh
* @param stringSet
* @return
*/
public String makeStringByStringSet(Set<String> stringSet) {
StringBuilder str = new StringBuilder();
int i = 0;
for (String s : stringSet) {
if (i == stringSet.size() - 1) {
str.append(s);
} else {
str.append(s + ",");
}
i++;
}
return str.toString().toLowerCase();
}

/**
* 获取拼音集合
*
* @author wyh
* @param src
* @return Set<String>
*/
public Set<String> getPinyin(String src) {
char[] srcChar;
srcChar = src.toCharArray();

// 1:多少个汉字
// 2:每个汉字多少种读音
String[][] temp = new String[src.length()][];
for (int i = 0; i < srcChar.length; i++) {
char c = srcChar[i];
// 是中文或者a-z或者A-Z转换拼音(我的需求,是保留中文或者a-z或者A-Z)
if (String.valueOf(c).matches("[\\u4E00-\\u9FA5]+")) {// 中文
String[] t = PinyinHelper
.getUnformattedHanyuPinyinStringArray(c);
temp[i] = new String[t.length];
for (int j = 0; j < t.length; j++) {
// temp[i][j]=t[j].substring(0,1);//获取首字母
temp[i][j] = t[j];// //获取首字母,不需要再截取了
}
} else if (((int) c >= 65 && (int) c <= 90)
|| ((int) c >= 97 && (int) c <= 122) || c >= 48 && c <= 57
|| c == 42) {// a-zA-Z0-9*
temp[i] = new String[] { String.valueOf(srcChar[i]) };
} else {
temp[i] = new String[] { "null!" };
}

}
return paiLie(temp);// 为了去掉重复项,直接返回Set
}

/*
* 求2维数组所有排列组合情况 比如:{{1,2},{3},{4},{5,6}}共有2中排列,为:1345,1346,2345,2346
*/
private Set<String> paiLie(String[][] str) {
int max = 1;
for (int i = 0; i < str.length; i++) {
max *= str[i].length;
}
Set<String> result = new HashSet<String>();
for (int i = 0; i < max; i++) {
String s = "";
int temp = 1; // 注意这个temp的用法。
for (int j = 0; j < str.length; j++) {
temp *= str[j].length;
s += str[j][i / (max / temp) % str[j].length];
}
result.add(s);
}

return result;
}

/**
* @param args
*/
public static void main(String[] args) {
// nongyeyinheng,nongyeyinhang,nongyeyinxing
PinYin4j t = new PinYin4j();
String str = "农业银行1234567890abcdefghijklmnopqrstuvwxyz*";
System.out.println(t.makeStringByStringSet(t.getPinyin(str)));
}

}


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

step5:PinyinHelper.java

package cn.roco.quicksearch.util;

import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;

public class PinyinHelper{
private static PinyinHelper instance;
private Properties properties = null;

public static String[] getUnformattedHanyuPinyinStringArray(char ch){
return getInstance().getHanyuPinyinStringArray(ch);
}

private PinyinHelper(){
initResource();
}

public static PinyinHelper getInstance(){
if(instance==null){
instance = new PinyinHelper();
}
return instance;
}

private void initResource(){
try{
//        	final String resourceName = "/assets/unicode_to_hanyu_pinyin.txt";
final String resourceName = "/assets/unicode_to_simple_pinyin.txt";

BufferedInputStream bis=new BufferedInputStream(PinyinHelper.class.getResourceAsStream(resourceName));
properties=new Properties();
properties.load(bis);

} catch (FileNotFoundException ex){
ex.printStackTrace();
} catch (IOException ex){
ex.printStackTrace();
}
}

private String[] getHanyuPinyinStringArray(char ch){
String pinyinRecord = getHanyuPinyinRecordFromChar(ch);

if (null != pinyinRecord){
int indexOfLeftBracket = pinyinRecord.indexOf(Field.LEFT_BRACKET);
int indexOfRightBracket = pinyinRecord.lastIndexOf(Field.RIGHT_BRACKET);

String stripedString = pinyinRecord.substring(indexOfLeftBracket
+ Field.LEFT_BRACKET.length(), indexOfRightBracket);

return stripedString.split(Field.COMMA);

} else
return null;

}

private String getHanyuPinyinRecordFromChar(char ch){
int codePointOfChar = ch;
String codepointHexStr = Integer.toHexString(codePointOfChar).toUpperCase();
String foundRecord = properties.getProperty(codepointHexStr);
return foundRecord;
}

class Field{
static final String LEFT_BRACKET = "(";
static final String RIGHT_BRACKET = ")";
static final String COMMA = ",";
}

}


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

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

package cn.roco.quicksearch;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AutoCompleteTextView;
import android.widget.SlidingDrawer;
import cn.roco.quicksearch.util.SearchAdapter;

public class QuickSearchActivity extends Activity {
private static final String tag = "QuickSearchActivity";
private AutoCompleteTextView search;
private SlidingDrawer mDrawer;

public SearchAdapter adapter = null;//
// 需要读取
public String[] hanzi = new String[] { "长江证券100002", "长江证券100001",
"农业银行200001", "工商银行300001", "招商银行100001", "建设银行100001",
"中国银行100002", "华夏银行500002", "上海银行100010", "浦发银行200009"
};

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

initViews();
}

private void initViews() {
search = (AutoCompleteTextView) findViewById(R.id.search);
search.setOnItemClickListener(new OnItemClickListener() {

@Override
public void onItemClick(AdapterView<?> arg0, View arg1,
int position, long id) {
// TODO Auto-generated method stub
Log.d(tag, "onItemClick:" + position);
}

});

search.setThreshold(1);

adapter = new SearchAdapter<String>(this,
android.R.layout.simple_dropdown_item_1line, hanzi,
SearchAdapter.ALL);//速度优先
search.setAdapter(adapter);//

mDrawer = (SlidingDrawer) findViewById(R.id.slidingdrawer);

}

}


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







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

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

==================================================================================================

作者:欧阳鹏 欢迎转载,与人分享是进步的源泉!

转载请保留原文地址:http://blog.csdn.net/ouyang_peng

==================================================================================================
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐