最近公司项目需要做一个搜索查询,查阅资料后思路如下(本文的前提是后台没有返回数据中文转化成首字母之后的字段):
1.编辑xml页面设置edittext 监听edittext输入框内容变化
searchEdiText.addTextChangedListener(new TextWatcher() {
@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) {
getPresenter().checkInput(s.toString());
}
});
2.我们需要考虑如果我们输入简拼的时候后台没有返回简拼的字段 所以我们刚进入页面就进行数据转换(这个比较耗时 所以我们需要考虑当用户往输入框输入内容的时候我们数据还没转换完 该怎么出来) ,同时我们也要考虑输入一个就查询一次这种比较耗时的操作,所以我会采用延时处理,还有查询数据是个耗时操作 因为需要放在子线程执行.
/**
*校验输入框输入的内容。
*/
protected void checkInput(String inputText) {
if (rzrqBeanList == null || rzrqBeanList.size() <= 0) {
return;
}
isInputContentByConvert = false;
if (!dataConvertFinish && PinyinUtils.containEnglishMaybeDigit(inputText)) {
if (!getView().isLoading()) {
getView().showLoding();
}
isInputContentByConvert = true;
} else {
if (getView().isLoading()) {
getView().dismissLoading();
}
handInputMessage(inputText);
}
}
处理耗时操作我是采用HandlerThread来处理的,处理避免一输入就查询这种耗性能操作我是采用handler延时处理的如下写的
/**
* 处理输入字符串消息。
*
* @param input 输入查询的内容。
*/
public void handInputMessage(String input) {
subHandler.removeMessages(0);
Message message = subHandler.obtainMessage();
message.obj = input;
message.what = 0;
subHandler.sendMessageDelayed(message, 200);
}
最后一点是边转换数据边输入,我是采用弹个菊花,加个标志位,当转换完成的时候才去查询列表数据.
以上是在处理数据之前所做的准备工作.
2.模糊查询 (页面肯定是recycleview)
这个简单 如果列表数据是小区名字 只要输入的内容包含在列表数据的小区名字中,那就把数据添加进去展示在页面就好了
3.简拼查询
如果后台返回了数据的简拼字段 那就跟步骤2一样即可.如果后台没有返回的话,我们就需要自己处理,处理方式有2种
3.1使用第三方库(这种我不做讲解说明)
pinyin4j的原理简单说明(后续我自己也会重新研究源码)
pinyin4j简单使用教程
3.2自我实现
因为自我实现的话,初衷就是代码越精简越好,步骤如下
1.先转换后台的数据
我会把每条汉字(转换成相应首字母) 英文 数字 保留下来 字段命名为xiaoquJP
2.判断输入内容是否需要使用简拼
经过个人整理发现只有英文 或者英文+数字才需要简拼
3.然后遍历数据匹配就好了
以上是思路 ,下面解决的问题是怎么判断这个字段是英文 汉字 数字等等,以及怎么获取汉字相对应的首字母
4.判断英文 汉字 数字 直接采用正则表达式(这里我贴出来)
/**
* 只有英文数字的字符串。
* @param str 字符串
* @return
*/
public static boolean isEnglishOrDigit(String str) {
return str.matches("[a-zA-Z0-9]+");
}
/**
* 字符串包含英文字母。
* @param str 字符串
* @return
*/
public static boolean containEnglish(String str) {
return str.matches(".*[a-zA-Z].*");
}
/**
* 1.输入的只有英文数字。
* 2.必须有英文。
* @param str 字符串
* @return
*/
public static boolean containEnglishMaybeDigit(String str) {
return isEnglishOrDigit(str) && containEnglish(str);
}
/**
* 如果是一个汉字返回true,否则返回false。
* @param c 字符
* @return
*/
private static boolean checkCharCN(char c) {
//[\u2E80-\u9FFF]+$ 匹配所有东亚区的语言
//[\u4E00-\u9FFF]+$ 匹配简体和繁体
//[\u4E00-\u9FA5]+$ 匹配简体
//Pattern p = Pattern.compile("[\u4e00-\u9fa5]");
//Matcher m = p.matcher(s);
//return m.matches();
if (c >= 0x4E00 && c <= 0x9FA5) {
return true;
}
return false;
}
5.获取汉字首字母
这里我查阅网上的资料后采用了一种错误的方案,我贴出来避免入坑
/**
* 获取一个汉字的拼音首字母。 GB码两个字节分别减去160,转换成10进制码组合就可以得到区位码
* 例如汉字“你”的GB码是0xC4/0xE3,分别减去0xA0(160)就是0x24/0x43
* 0x24转成10进制就是36,0x43是67,那么它的区位码就是3667,在对照表中读音为‘n’
* Created by YJ on 2016/3/23.
*/
public class ChineseToInitialUtil {
static final int GB_SP_DIFF = 160;
// 存放国标一级汉字不同读音的起始区位码
static final int[] SEC_POS_VALUE_LIST = {
1601, 1637, 1833, 2078, 2274, 2302, 2433, 2594, 2787, 3106, 3212, 3472,
3635, 3722, 3730, 3858, 4027, 4086, 4390, 4558, 4684, 4925, 5249, 5600 };
//存放国标一级汉字不同读音的起始区位码对应读音,汉字首字母中不会出现i
static final char[] FIRST_LETTER = {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h','j', 'k', 'l',
'm','n', 'o', 'p', 'q', 'r', 's', 't', 'w', 'x', 'y', 'z' };
public static String getChineseInitial(String characters) {
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < characters.length(); i++) {
char ch = characters.charAt(i);
if ((ch >> 7) == 0) { // 判断是否为汉字,如果左移7为为0就不是汉字,否则是汉字
} else {
char spell = getFirstLetter(ch);
if (!Character.isSpaceChar(spell)) {
buffer.append(String.valueOf(spell));
}
}
}
return buffer.toString();
}
/**
* 获取一个汉字的首字母
*/
public static Character getFirstLetter(char ch) {
byte[] uniCode = null;
try {
uniCode = String.valueOf(ch).getBytes("GBK");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
if (uniCode[0] < 128 && uniCode[0] > 0) { // 非汉字
return null;
} else {
return convert(uniCode);
}
}
/**
* 获取一个汉字的拼音首字母。 GB码两个字节分别减去160,转换成10进制码组合就可以得到区位码
* 例如汉字“你”的GB码是0xC4/0xE3,分别减去0xA0(160)就是0x24/0x43
* 0x24转成10进制就是36,0x43是67,那么它的区位码就是3667,在对照表中读音为‘n’
*/
static char convert(byte[] bytes) {
char result = '-';
int secPosValue = 0;
int i;
for (i = 0; i < bytes.length; i++) {
bytes[i] -= GB_SP_DIFF;
}
secPosValue = bytes[0] * 100 + bytes[1];
for (i = 0; i < 23; i++) {
if (secPosValue >= SEC_POS_VALUE_LIST[i] && secPosValue < SEC_POS_VALUE_LIST[i + 1]) {
result = FIRST_LETTER[i];
break;
}
}
return result;
}
}
这里面的注释非常详细,我本来以为直接使用就万事大吉 结果偶然中发现 “圳”(圳是属于国标二级汉字) 这个字用这个工具并没有获取到首字母 于是我重新审阅这个工具类 我网上查询到“圳”的区位码是5958完整简体汉字区位码, 我这个工具类最大的区位码才5600,由此我断定这个工具类看起来虽然简洁,但是不完整,因为我放弃这个工具类.
经过一番查询 我找到另外一篇文章
文章链接
这篇文章应该还是基于pinyin4j整理后的(我自己也拿项目4000条数据测试了,的确能达到效果)
核心代码如下
/**
* 汉字转拼音
*
* @param ccs 汉字字符串(Chinese characters)
* @param split 汉字拼音之间的分隔符
* @return 拼音
*/
public static String ccs2Pinyin(CharSequence ccs, CharSequence split) {
if (ccs == null || ccs.length() == 0) return null;
StringBuilder sb = new StringBuilder();
for (int i = 0, len = ccs.length(); i < len; i++) {
char ch = ccs.charAt(i);
if (ch >= 0x4E00 && ch <= 0x9FA5) {
int sp = (ch - 0x4E00) * 6;
sb.append(pinyinTable.substring(sp, sp + 6).trim());
} else {
sb.append(ch);
}
sb.append(split);
}
return sb.toString();
}
//pinyinTable完整字段在上面那个文章了链接里面有
//noinspection StringBufferReplaceableByString
pinyinTable = new StringBuilder(125412)
.append("yi ding kao qi shang xia none wan zhang san shang xia ji bu ...)..append("xie yue ").toString();
6.注意事项
1.一边输入一边滑动列表的时候会出现崩溃(角标指针错误),所以需要新建个集合保留遍历过程中的数据
2.使用 Collections.sort(...)排序的时候一定要把 0 1 -1三种场景考虑完整 不然也会引用崩溃(这个崩溃信息在我其他博客中有提及)
3.handler、子线程(线程资源使用我是通过return达到的)等资源的释放
总结:
看似很简单的一个功能,实现起来的时候注意的场景还是比较多的,尤其是转义数据的这个过程中用户往输入框输入内容怎么处理,以及转义完后,用户边输入边滑动列表时候的注意,最后还有一点 怎么保证所有汉字我们都能获取到首字母.