前言

  最近心血来潮,突然想搞点仿制品玩玩,很不幸小米成为我苦逼的第一个试验品。既然雷布斯的MIUI挺受欢迎的(本人就是其的屌丝用户),所以就拿其中的一些小功能做一些小demo来玩玩。小米的通讯录大家估计用过小米的都清楚是啥子样的,没用过小米的也别着急,瞧瞧我的demo,起码也有七八分相似滴。先上图看效果

我是图:

PS:吐槽一下,博客园上个图真难,所以搞了个短点的gif上才没失败。。。。唉。。。

在这里仅仅是实现了逻辑交互的效果,并没有点击打电话的功能,因为也不难就懒得加了。。。

分析

我们说说这个东西主要的实现功能点在哪些?

1、输入数据按首字母排序

2、查询框输入时快速更改数据显示

3、右端首字母跟随数据列表位置高亮显示

4、滑动时,出现数据第一个字提示

解决方案

1、输入数据按首字母排序

  这个怎么办呢?毕竟一个android小小的一个软件不可用把整个汉字拼音转换库放进去对吧,如果真的放进去,你觉得这样用户会买你的帐,对不起你的APP太大啦;所以Stop这个想法!但是我们换一个思路,既然汉字在计算机中的是以编码的存放的,我们是不是可以再编码这里面做点文章。就以GB2312来说,里面的汉字是按字典序排列的,就是越前面其声母越靠前,例如第一个汉字编码代表的就是“啊”这个字,那好办了,我们只要把各个首字母的编码获取区间,然后对于任何一个汉字,判断其是否在相对应区间中即可获取该汉字的首字母。但是但是,对于多音字这个东西,大家当我没说好吧???

附核心源码:

/**
* GetPYUntl工具类提供根据汉字获取首字母的功能
* 仅支持GB2312简体汉字
*/
public class GetPYUntl { //简体中文的编码范围从B0A1(45217)一直到F7FE(63486)
private static final int BEGIN = 45217;
private static final int END = 63486; //按照声 母表示,这个表是在GB2312中的出现的第一个汉字,也就是说“啊”是代表首字母a的第一个汉字
//i, u, v都不做声母, 自定规则跟随前面的字母
//最后保持空格
private static final char[] charTable = new char[]{'啊', '芭', '擦', '搭', '蛾', '发',
'噶', '哈', '哈', '击', '喀', '垃', '妈',
'拿', '哦', '啪', '期', '然', '撒', '塌',
'塌', '塌', '挖', '昔', '压', '匝'}; //对应首字母区间表
private static final char[] initialtable = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'H', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'T', 'T', 'W', 'X', 'Y',
'Z'}; //二十六个字母区间对应二十七个端点
//GB2312码汉字区间十进制表示
private static int[] table = new int[27]; //工具类初始化代码块,进行初始化操作
static{
for(int i = 0; i < 26; i++){
table[i] = getGBValue(charTable[i]);//得到GB2312码的首字母区间端点表,十进制。
}
table[26] = END;//区间表的结尾数值
} /**
* 根据汉字c获取其对应的编码
* 将一个汉字(GB2312)转换为十进制表示
*/
private static int getGBValue(char c) {
String str = c + "";
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;
}
} //----------------------对外调用方法区---------------------------------------//
/**
* 根据输入的单汉字,返回首字母
* @param ch 所需要查询的汉字
* @return 返回对应输入汉字的首字母
*/
public static char getFristWord(char ch){
//ch为英文字符,小写转为大写,大写直接返回
if(ch >= 'a' && ch <= 'z'){
return (char)(ch - 'a' + 'A');
}
if(ch >= 'A' && ch <= 'Z'){
return ch;
}
//若为汉字,获取其区间值,并判断是否在码表的范围中
//若不是,返回&
//若是,在码表对其进行判断
int gb = getGBValue(ch); if(gb < BEGIN || gb > END){
return '&';
}
int i;
for(i = 0; i < 26; i++){//判断匹配码表区间,匹配到就break,判断区间形如“[,)”
if(gb >= table[i] && gb < table[i+1]){
break;
}
}
if(gb == END){//补齐GB2312区间最右端,就是首字母编码值为END
i = 25;
}
return initialtable[i];//在码表区间中,返回首字母
} /**
* 根据输入的汉字字符串,返回首字母字符串,内部机制依赖调用 getFristWord(char ch)方法。
* @param str 所需要查询的汉字字符串
* @return 返回对应输入汉字字符串的首字母字符串
*/
public static String getFristWord(String str){
String reslut = "";
if(str != null || str != ""){//容错操作,校验输入的字符串
int length = str.length();
for(int i = 0; i < length; i++){//分别对str的各个字符进行取首字母操作
reslut += getFristWord(str.charAt(i));
}
}
return reslut;
} /**
* 提供两个字符串,根据其首字母字符串的排列顺序
* @param str1
* @param str2
* @return true:str1位于str2前面,false:str1位于str2后面
*/
public static int Compare(String str1, String str2){
String cstr1 = getFristWord(str1);
String cstr2 = getFristWord(str2);
return cstr1.compareTo(cstr2);
}
}

2、查询框输入时快速更改数据显示

  这个实现比较简单,主要是用到TextWatcher这个类来监听EditText的内容变化,如果我们在输入查询内容,TextWatcher会监听EditText的状态改变,调用onTextChanged()方法响应。

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
dealData.clear();//清空上次的query查询处理后的数据,用于加载新一次数据
if(data != null && !"".equals(s.toString())){//query有内容,显示根据query处理后的数据
for(int i = 0; i < data.size(); i++){//编译显示内容,将符合的数据收集到dealData
String str = data.get(i);
//判断s是否为str的子字符串,如果是,则是需要收集的数据,s为query当前输入的内容
boolean find = str.contains(s.toString());
if(find){
dealData.add(str);
}
}
//更新listView的数据源
adapter.setData(dealData, s.toString());
//刷新listView
list.invalidateViews();
}
else{//数据集合为空,或者不存在查询的query请求
adapter.setData(data, s.toString());
list.invalidateViews();//刷新listView为初始状态
}
}

但是!这个东西实现的最重要逻辑并不是TextWatcher的代码,而是我们重写的BaseAdapter里面的getView方法。因为我们要根据查询内容高亮显示字体,所以我们的ListView的item对应的View需要着重处理,但是这样会引入大量的View,估计教育我们要记得重用View,所以我在getView里面也大量用重用思想,减少资源占用率。

由于逻辑比较复杂,说以简略说说实现思路吧:BaseAdapter会根据是否有查询请求的接入,如果没有查询请求则是显示全部数据,那么我们会在每一个convertView里面add一个TextView的实例,显示全部内容。如果有查询请求,BaseAdapter会整理要显示的数据,然后在convertView里面add若干个TextView,每一个TextView将会只显示一个字,在对应需要高亮的TextView会进行设置字体颜色。但是这样会导致很多TextView的存在,所以我们在每一个convertView里面会设置缓存缓存这些TextView的引用,如果下次复用时直接从Tag中取得TextView进行处理后add到convertView里面显示,然后清理掉多余的缓存。如果你看懵了不要紧,注释非常详细,可以过一下代码。

核心代码:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
List<TextView> cache = null;
//进行convertView的复用,如果第一次使用,则实例化该View
if(convertView == null){
convertView = inflater.inflate(R.layout.mark_list_item, null, false);
//申请一个缓冲cache用于保存其子TextView的引用
cache = new ArrayList<TextView>(); TextView tv = null;
if(query == null || "".equals(query)){//如果query为null,或者为空字符串,则直接加载
tv = new TextView(context);
tv.setText(data.get(position));//将其整个字符串设置到TextView里面,而不单字加载
setTextParmas(tv);
((LinearLayout)convertView).addView(tv);//加载到convertView节点下
cache.add(tv);//缓存集合中保存tv的引用,便于下次复用该TextView
}
else{//query不为null,其有query内容,我们需要将该查询内容对于的字符高亮显示
//将显示内容的字符串拆分为字符数组,用于单字加载
char[] words = data.get(position).toCharArray();
for(int i = 0; i < words.length; i++){
tv = new TextView(context);
tv.setText(words[i]+"");//单字加载,即一个字用一个TextView显示
setTextParmas(tv);
//查询是否在query的内容之中,如果为是则将该TextView高亮显示
for(char c : query){
if(c == words[i])
tv.setTextColor(Color.RED);
}
((LinearLayout)convertView).addView(tv);
cache.add(tv);
}
}
}
else{//如果不是第一次使用,则该convertView可以被复用
cache = (List<TextView>) convertView.getTag();//获取缓存cache集合
TextView tv;
int usingSize = 0;//记录当前convertView所有使用的TextView个数
int length = cache.size();
if(query == null || "".equals(query)){//如果query为null,或者为空字符串,则直接加载
if(length > 0){//检测cache是否存在可复用的View
tv = cache.get(0);//取首个TextView复用
tv.setTextColor(Color.GRAY);
}
else{//如果没有可复用的TextView,实例化一个TextView并加入缓存中
tv = new TextView(context);
setTextParmas(tv);
((LinearLayout)convertView).addView(tv);
cache.add(tv);
}
tv.setText(data.get(position));
usingSize++;
}
else{//query不为null,其有query内容,我们需要将该查询内容对于的字符高亮显示
//将显示内容的字符串拆分为字符数组,用于单字加载
char[] words = data.get(position).toCharArray();
for(int i = 0; i < words.length; i++){
if(i < length){//获取第i个缓存的TextView
tv = cache.get(i);
tv.setTextColor(Color.GRAY);
}
else{//如果已经没有缓存TextView可获取了
tv = new TextView(context);
setTextParmas(tv);
((LinearLayout)convertView).addView(tv);
cache.add(tv);
}
//查询是否在query的内容之中,如果为是则将该TextView高亮显示
for(char c : query){
if(c == words[i])
tv.setTextColor(Color.RED);
}
tv.setText(words[i]+"");//单字加载,即一个字用一个TextView显示
usingSize++;
}
}
clearCache(convertView, cache, usingSize);
}
convertView.setTag(cache);//保存缓存cache集合
firstWord.add(position, cache.get(0).getText().toString().charAt(0));
return convertView;
}

3、右端首字母跟随数据列表位置高亮显示

这个实现的思路主要用到OnScrollListener这个监听接口,其中onScroll()这个方法会在滚动时调用,那么我们在滚动listView的时候就可以在这里操作首字母的高亮显示了。

@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
ListViewAdapter adapter = (ListViewAdapter)(view.getAdapter());
if(adapter != null){//在首次加载listview时会调用onScroll(),那时候的adapter为null,容错处理
char c1 = adapter.getFristWord(firstVisibleItem + 4);
char c2 = GetPYUntl.getFristWord(c1);
TextView tv = tvMaps.get(c2+"");
if(tv != null){
if(oldTv != null)//如果前次已经有TextView高亮显示,取消高亮效果
oldTv.setTextColor(Color.GRAY);
tv.setTextColor(Color.RED);//设置高亮效果
centerTextView.setText(c1+"");
oldTv = tv;
}
}
}

4、滑动时,出现数据第一个字提示

这个做的不是那么好,如果快速多次滑动还是会出现一些问题。如果大家有兴趣就整改一下哈,还是用到OnScrollListener这个监听接口里面的onScrollStateChanged方法,逻辑也简单,在滚动时设置显示的TextView可见,在停止滚动时启动定时器2秒后取消TextView可见。但是对于多次快速滚动来说,这个方法还需要微调一下。

核心代码

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) { //构造一个定时器,用于延时2000毫秒取消显示
Runnable runnable = new Runnable() {
@Override
public void run() {
centerTextView.setVisibility(View.INVISIBLE);
}
};
Handler handler = new Handler(); switch (scrollState){
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
//如果手指还接触界面,则取消定时,主要用于第二次后触发事件使用
if(centerTextView.getVisibility() == View.VISIBLE)
handler.removeCallbacks(runnable);
break;
case OnScrollListener.SCROLL_STATE_FLING:
//如果手指离开屏幕,屏幕滑动,设置centerTextView可见
centerTextView.setVisibility(View.VISIBLE);
//如果第二次手指离开屏幕,屏幕滑动,则取消定时,主要用于第二次后触发事件使用
if(centerTextView.getVisibility() == View.VISIBLE)
handler.removeCallbacks(runnable);
break;
case OnScrollListener.SCROLL_STATE_IDLE:
//启动定时任务,于2秒后取消显示
handler.postDelayed(runnable, 1000);
break;
} }

后记

由于代码量太多,也不好仔细讲实现的逻辑,那么一篇博客肯定写不完,我又比较懒,不喜欢折腾好几个博客来写,所以我在代码里面进行了详尽的注释,希望你参考demo能看懂这些功能的实现思路。

源码请戳(这里

作者:enjoy风铃
出处:http://www.cnblogs.com/net168/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则下次不给你转载了。

Android应用系列:手把手教你做一个小米通讯录(附图附源码)的更多相关文章

  1. Android应用系列:完美运行GIF格式的ImageView(附源码)

    前言 我们都知道ImageView是不能完美加载Gif格式的图片,如果我们在ImageView中src指定的资源是gif格式的话,我们将会惊喜的发觉画面永远停留在第一帧,也就是不会有动画效果.当然,经 ...

  2. R数据分析:跟随top期刊手把手教你做一个临床预测模型

    临床预测模型也是大家比较感兴趣的,今天就带着大家看一篇临床预测模型的文章,并且用一个例子给大家过一遍做法. 这篇文章来自护理领域顶级期刊的文章,文章名在下面 Ballesta-Castillejos ...

  3. 手把手教你做一个Shell命令窗口

    这是一个类似于win下面的cmd打开后的窗口,可以跨平台使用,可以在win和linux下面同时使用,主要功能如下: 首先我们需要把这些功能的目录写出来,通过写一个死循环,让其每次回车之后都可以保持同样 ...

  4. 手把手教你撸一套Redux(Redux源码解读)

    Redux 版本:3.7.2 Redux 是 JavaScript 状态容器,提供可预测化的状态管理. 说白了Redux就是一个数据存储工具,所以数据基础模型有get方法,set方法以及数据改变后通知 ...

  5. 手把手教你调试SpringBoot启动 IoC容器初始化源码,spring如何解决循环依赖

    授人以鱼不如授人以渔,首先声明这篇文章并没有过多的总结和结论,主要内容是教大家如何一步一步自己手动debug调试源码,然后总结spring如何解决的循环依赖,最后,操作很简单,有手就行. 本次调试 是 ...

  6. 手把手教你实现栈以及C#中Stack源码分析

    定义 栈又名堆栈,是一种操作受限的线性表,仅能在表尾进行插入和删除操作. 它的特点是先进后出,就好比我们往桶里面放盘子,放的时候都是从下往上一个一个放(入栈),取的时候只能从上往下一个一个取(出栈), ...

  7. 【MVVMLight小记】二.开发一个简单图表生成程序附源码

    上一篇文章介绍了怎样快速搭建一个基于MVVMLight的程序http://www.cnblogs.com/whosedream/p/mvvmlight1.html算是简单入门了下,今天我们来做一个稍许 ...

  8. 【Android初级】利用startActivityForResult返回数据到前一个Activity(附源码+解析)

    在Android里面,从一个Activity跳转到另一个Activity.再返回,前一个Activity默认是能够保存数据和状态的.但这次我想通过利用startActivityForResult达到相 ...

  9. 【Android初级】使用TypeFace设置TextView的文字字体(附源码)

    在Android里面设置一个TextView的文字颜色和文字大小,都很简单,也是一个常用的基本功能.但很少有设置文字字体的,今天要分享的是通过TypeFace去设置TextView的文字字体,布局里面 ...

随机推荐

  1. java基础 ----- 选择结构

    ---------    流程控制 ------     流程图 ------   基本的  if  选择结构 import java.util.Scanner; public class GetPr ...

  2. 227. Basic Calculator II 无括号版本计算器

    [抄题]: Implement a basic calculator to evaluate a simple expression string. The expression string con ...

  3. jdk1.8 HashMap的实现

    在了解HashMap之前,我们先进行位运算知识的补充 1.Java 位运算:(都是二进制的运算) << :相当于乘以2的倍数  --->1<<4  =1*2*2*2*2 ...

  4. Linux系统minicom命令详解

    minicom 功能说明:调制解调器通信程序 语 法:minicom [-8lmMostz][-a<on或0ff>][-c<on或off>][-C<取文件>][-d ...

  5. Android 发送邮件以及定时发送邮件的实现

    本文以腾讯企业邮箱为例,展示如何发送邮件 及相关问题  选择腾讯企业邮箱是因为腾讯企业邮箱一般都是开启了smtp服务 项目地址:https://gitee.com/bimingcong/MySendE ...

  6. Step by Step Guide on Yanhua ACDP Clear BMW EGS ISN

    Yanhua Mini ACDP authorize new function on BMW EGS ISN clearing.So here UOBDII want to share this st ...

  7. JPanel JScrollPanel

    JPanel 和 JScrollPanel 都属于面板,也是 Swing 中间容器,可以作为容器存放组件,但必须被添加到其他容器中. JPanel 可以聚集一些组件来布局, JScrollPanel ...

  8. C++ 提取网页内容系列之四正则

    标 题: C++ 提取网页内容系列之四作 者: itdef链 接: http://www.cnblogs.com/itdef/p/4173833.html 欢迎转帖 请保持文本完整并注明出处 将网页内 ...

  9. Netsharp总体介绍

    作者:秋时   日期:2014年02月05日   转载须说明出处  Netsharp交流群:338963050(请有详细的请求说明) Netsharp系列文章目录结构 Netsharp是一款免费的基于 ...

  10. 分享Pos函数(比FastPos还要快)

    ): Integer; ): Integer; 主要用途是搜索字符串中第n个Substr. 经过测试,这2个函数的速度比直接用Pos+Copy快好几倍(如果字符串够长,可能10几倍) 比Pos+Del ...