在前一篇博客里分享了通过字母索引查找地名或联系人,接下来再分享下通过搜索查找地名或联系人。
使用搜索查找地名,实际上就是对ListView中的数据进行过滤,将符合条件的数据项重新保存起来进行显示。Demo效果如下:
关键代码包括:
1)检查输入文本框内容变动,调用适配器进行过滤
public void afterTextChanged(Editable s) {
/*在搜索时隐藏右边字母索引*/
if(s.length() == 0){
layout.setVisibility(View.VISIBLE);
}else{
layout.setVisibility(View.GONE);
}
listAdapter.getFilter().filter(s.toString());
}
2)自定义适配器,并
添加自定义的过滤选项
class ListAdapter extends BaseAdapter implements Filterable{
private Context context;
private List<String> list;
private ShortNameFilter filter;
public ListAdapter(Context context , List<String> list){
this.context = context;
this.list = list;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null){
convertView = LayoutInflater.from(context).inflate(android.R.layout.simple_list_item_1,null);
}
TextView textView = (TextView) convertView.findViewById(android.R.id.text1);
textView.setText(list.get(position));
return convertView;
}
@Override
public Filter getFilter() {
if(filter == null)
filter = new ShortNameFilter(list);
return filter;
}
private class ShortNameFilter extends Filter {
private List<String> original;
public ShortNameFilter(List<String> list){
this.original = list;
}
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
if(constraint == null || constraint.length() == 0){
results.values = original;
results.count = original.size();
}else{
List<String> fList = new ArrayList<>();
String constraintUpper = constraint.toString().toUpperCase();
char firstChar = constraintUpper.charAt(0);
/*搜索文本为字母,则获取城市名称的首字母缩写,然后是否包含搜索文本
* 否则直接使用城市名检查是否包含搜索文本,将符合条件的数据项重新保存显示*/
if(firstChar >= 'A' && firstChar <= 'Z'){
for(String name:original){
if(FirstLetterUtil.Char2Initial(name.charAt(0)) == firstChar){
if(FirstLetterUtil.getFirstLetter(name).contains(constraintUpper))
fList.add(name);
}
}
}else {
for(String name:original){
if(name.contains(constraint))
fList.add(name);
}
}
results.values = fList;
results.count = fList.size();
}
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
list = (List<String>) results.values;
notifyDataSetChanged();
}
}
}
3)显示过滤后选中的数据
<span > </span>listView = (ListView) findViewById(R.id.listView);
Collections.addAll(cityNames, Cities.mCitiesStrings);
listAdapter = new ListAdapter(context,cityNames);
listView.setAdapter(listAdapter);
listView.setOnItemClickListener(itemClickListener);
AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String choice = (String) listAdapter.getItem(position);
Log.d(TAG, "onItemClick.choice = " + choice);
textView.setText(choice);
}
};
当搜索文本框输入字母时,需要将城市名选项转化成字母进行匹配,这里参考了一个工具类,该工具类将每个声母中第一个汉字的GB2312编码存起来作为参考样本,然后查找目标汉字的编码,与样本汉字进行比较,从而确定目标汉字的首字母。但有些汉字的GB2312编码查找出来得出的结果不正确,例如我所使用的地名中,“ 亳州 重庆 漯河 泸州 濮阳 衢州”的首字母都在Z部中,会导致结果有一些小小的差异,也算一个bug吧。
全部代码如下:
public class CityActivity extends Activity implements TextWatcher {
private static final String TAG = "CityActivity";
private Map<Character,Integer> map = new TreeMap<Character,Integer>();
private List<String> cityNames = new ArrayList<String>();
private Context context;
private ListView listView;
private ListAdapter listAdapter;
private LinearLayout layout;
private EditText searchName;
private TextView textView;
private CharSequence temp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_city);
initView();
}
private void initView(){
context = CityActivity.this;
textView = (TextView) findViewById(R.id.city);
searchName = (EditText) findViewById(R.id.search_name);
searchName.addTextChangedListener(this);
listView = (ListView) findViewById(R.id.listView);
Collections.addAll(cityNames, Cities.mCitiesStrings);
listAdapter = new ListAdapter(context,cityNames);
listView.setAdapter(listAdapter);
listView.setOnItemClickListener(itemClickListener);
initMap(Cities.mCitiesStrings);
layout = (LinearLayout) findViewById(R.id.letter_index);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
for(final char letter:map.keySet()){
TextView tv = new TextView(context);
tv.setText(String.valueOf(letter));
tv.setTextColor(getResources().getColor(R.color.text_darker));
tv.setGravity(Gravity.CENTER);
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String indexLetter = ((TextView) v).getText().toString();
for (int i = 0; i < layout.getChildCount(); i++) {
TextView tv = (TextView) layout.getChildAt(i);
tv.setTextColor(getResources().getColor(R.color.text_darker));
}
((TextView) v).setTextColor(getResources().getColor(R.color.main_color));
Log.d(TAG, "onClick.letter = " + indexLetter);
int position = map.get(indexLetter.charAt(0)) != null ? map.get(indexLetter.charAt(0)) : 0;
listView.setSelection(position);
}
});
layout.addView(tv);
}
}
/*
* 将城市名称首字母第一次出现的id存放在map中
* */
private void initMap(final String[] strings){
char curChar = '0';
char firstChar = '0';
for(int i = 0; i < strings.length; i++){
firstChar = FirstLetterUtil.Char2Initial(strings[i].charAt(0));
if(firstChar != curChar){
curChar = firstChar;
if(firstChar == 'Z' && "亳州 重庆 漯河 泸州 濮阳 衢州".contains(strings[i])) {
Log.d(TAG, "initMap.index = " + i + " " + strings[i]);
continue;
}
if(map.get(curChar) == null)
map.put(curChar,i);
}
}
}
AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String choice = (String) listAdapter.getItem(position);
Log.d(TAG, "onItemClick.choice = " + choice);
textView.setText(choice);
}
};
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
/*在搜索时隐藏右边字母索引*/
if(s.length() == 0){
layout.setVisibility(View.VISIBLE);
}else{
layout.setVisibility(View.GONE);
}
listAdapter.getFilter().filter(s.toString());
}
class ListAdapter extends BaseAdapter implements Filterable{
private Context context;
private List<String> list;
private ShortNameFilter filter;
public ListAdapter(Context context , List<String> list){
this.context = context;
this.list = list;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return list.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null){
convertView = LayoutInflater.from(context).inflate(android.R.layout.simple_list_item_1,null);
}
TextView textView = (TextView) convertView.findViewById(android.R.id.text1);
textView.setText(list.get(position));
return convertView;
}
@Override
public Filter getFilter() {
if(filter == null)
filter = new ShortNameFilter(list);
return filter;
}
private class ShortNameFilter extends Filter {
private List<String> original;
public ShortNameFilter(List<String> list){
this.original = list;
}
@Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
if(constraint == null || constraint.length() == 0){
results.values = original;
results.count = original.size();
}else{
List<String> fList = new ArrayList<>();
String constraintUpper = constraint.toString().toUpperCase();
char firstChar = constraintUpper.charAt(0);
/*搜索文本为字母,则获取城市名称的首字母缩写,然后是否包含搜索文本
* 否则直接使用城市名检查是否包含搜索文本,将符合条件的数据项重新保存显示*/
if(firstChar >= 'A' && firstChar <= 'Z'){
for(String name:original){
if(FirstLetterUtil.Char2Initial(name.charAt(0)) == firstChar){
if(FirstLetterUtil.getFirstLetter(name).contains(constraintUpper))
fList.add(name);
}
}
}else {
for(String name:original){
if(name.contains(constraint))
fList.add(name);
}
}
results.values = fList;
results.count = fList.size();
}
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
list = (List<String>) results.values;
notifyDataSetChanged();
}
}
}
}
首字母转换类代码如下:
package com.huiyu.hornsey.mylibrary.common.util;
/**
* Created by hornsey on 2015/11/23.
*/
public class FirstLetterUtil {
private static int BEGIN = 45217;
private static int END = 63486;
// 按照声母表示,这个表是在GB2312中的出现的第一个汉字,也就是说“啊”是代表首字母a的第一个汉字。
// i, u, v都不做声母, 自定规则跟随前面的字母
private static char[] chartable = {'啊', '芭', '擦', '搭', '蛾', '发', '噶', '哈',
'哈', '击', '喀', '垃', '妈', '拿', '哦', '啪', '期', '然', '撒', '塌', '塌',
'塌', '挖', '昔', '压', '匝',};
// 二十六个字母区间对应二十七个端点
// GB2312码汉字区间十进制表示
private static int[] table = new int[27];
// 对应首字母区间表
private static char[] initialtable = "ABCDEFGHHJKLMNOPQRSTTTWXYZ".toCharArray();
// 初始化
static {
for (int i = 0; i < 26; i++) {
table[i] = gbValue(chartable[i]);// 得到GB2312码的首字母区间端点表,十进制。
}
table[26] = END;// 区间表结尾
}
/**
* 根据一个包含汉字的字符串返回一个汉字拼音首字母的字符串 最重要的一个方法,思路如下:一个个字符读入、判断、输出
*/
public static String getFirstLetter(String sourceStr) {
String result = "";
String str = sourceStr.toUpperCase();
int StrLength = str.length();
int i;
try {
for (i = 0; i < StrLength; i++) {
result += Char2Initial(str.charAt(i));
}
} catch (Exception e) {
result = "";
}
return result;
}
/**
* 输入字符,得到他的声母,英文字母返回对应的大写字母,其他非简体汉字返回 '0'
*/
public static char Char2Initial(char ch) {
// 对英文字母的处理:直接返回
if (ch >= 'a' && ch <= 'z') {
return ch;
}
if (ch >= 'A' && ch <= 'Z') {
return ch;
}
// 对非英文字母的处理:转化为首字母,然后判断是否在码表范围内,
// 若不是,则直接返回。
// 若是,则在码表内的进行判断。
int gb = gbValue(ch);// 汉字转换首字母
if ((gb < BEGIN) || (gb > END))// 在码表区间之前,直接返回
{
return ch;
}
int i;
for (i = 0; i < 26; i++) {// 判断匹配码表区间,匹配到就break,判断区间形如“[,)”
if ((gb >= table[i]) && (gb < table[i + 1])) {
break;
}
}
if (gb == END) {//补上GB2312区间最右端
i = 25;
}
return initialtable[i]; // 在码表区间中,返回首字母
}
/**
* 取出汉字的编码 cn 汉字
*/
private static int gbValue(char ch) {// 将一个汉字(GB2312)转换为十进制表示。
String str = new String();
str += ch;
try {
byte[] bytes = str.getBytes("GB2312");
if (bytes.length < 2) {
return 0;
}
return (bytes[0] << 8 & 0xff00) + (bytes[1] & 0xff);
} catch (Exception e) {
return 0;
}
}
}