Android应用系列:手把手教你做一个小米通讯录(附图附源码)
前言
最近心血来潮,突然想搞点仿制品玩玩,很不幸小米成为我苦逼的第一个试验品。既然雷布斯的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应用系列:手把手教你做一个小米通讯录(附图附源码)的更多相关文章
- Android应用系列:完美运行GIF格式的ImageView(附源码)
前言 我们都知道ImageView是不能完美加载Gif格式的图片,如果我们在ImageView中src指定的资源是gif格式的话,我们将会惊喜的发觉画面永远停留在第一帧,也就是不会有动画效果.当然,经 ...
- R数据分析:跟随top期刊手把手教你做一个临床预测模型
临床预测模型也是大家比较感兴趣的,今天就带着大家看一篇临床预测模型的文章,并且用一个例子给大家过一遍做法. 这篇文章来自护理领域顶级期刊的文章,文章名在下面 Ballesta-Castillejos ...
- 手把手教你做一个Shell命令窗口
这是一个类似于win下面的cmd打开后的窗口,可以跨平台使用,可以在win和linux下面同时使用,主要功能如下: 首先我们需要把这些功能的目录写出来,通过写一个死循环,让其每次回车之后都可以保持同样 ...
- 手把手教你撸一套Redux(Redux源码解读)
Redux 版本:3.7.2 Redux 是 JavaScript 状态容器,提供可预测化的状态管理. 说白了Redux就是一个数据存储工具,所以数据基础模型有get方法,set方法以及数据改变后通知 ...
- 手把手教你调试SpringBoot启动 IoC容器初始化源码,spring如何解决循环依赖
授人以鱼不如授人以渔,首先声明这篇文章并没有过多的总结和结论,主要内容是教大家如何一步一步自己手动debug调试源码,然后总结spring如何解决的循环依赖,最后,操作很简单,有手就行. 本次调试 是 ...
- 手把手教你实现栈以及C#中Stack源码分析
定义 栈又名堆栈,是一种操作受限的线性表,仅能在表尾进行插入和删除操作. 它的特点是先进后出,就好比我们往桶里面放盘子,放的时候都是从下往上一个一个放(入栈),取的时候只能从上往下一个一个取(出栈), ...
- 【MVVMLight小记】二.开发一个简单图表生成程序附源码
上一篇文章介绍了怎样快速搭建一个基于MVVMLight的程序http://www.cnblogs.com/whosedream/p/mvvmlight1.html算是简单入门了下,今天我们来做一个稍许 ...
- 【Android初级】利用startActivityForResult返回数据到前一个Activity(附源码+解析)
在Android里面,从一个Activity跳转到另一个Activity.再返回,前一个Activity默认是能够保存数据和状态的.但这次我想通过利用startActivityForResult达到相 ...
- 【Android初级】使用TypeFace设置TextView的文字字体(附源码)
在Android里面设置一个TextView的文字颜色和文字大小,都很简单,也是一个常用的基本功能.但很少有设置文字字体的,今天要分享的是通过TypeFace去设置TextView的文字字体,布局里面 ...
随机推荐
- [leetcode]88. Merge Sorted Array归并有序数组
Given two sorted integer arrays nums1 and nums2, merge nums2 into nums1 as one sorted array. Note: T ...
- 五子棋 AI(AIpha-beta算法)
博弈树 下过五子棋的人都应该知道,越厉害的人,对棋面的预测程度越深.换句话讲,就是当你下完一步棋,我就能在我的脑海里假设把我所有可能下的地方都下一遍,然后考虑我下完之后你又会下在哪里,最后我根据每次预 ...
- 代码之髓读后感——类&继承
面向对象 语言中的用语并不是共通的,在不同语言中,同一个用语的含义可能会有很大差别. C++的设计者本贾尼·斯特劳斯特卢普对类和继承给予了正面肯定,然而,"面向对象"这个词的发明者 ...
- 搭建Fabric网络(二)下载bin和images
上一篇已经把运行和开发Fabric需要的程序都安装好了,这一篇主要讲怎么运行一个简单的Fabric网络. 1. 下载官方Sample代码 git clone -b master https://gi ...
- 22. pt-sift
pt-sift /var/lib/pt-stalk/ ======== server01 at 2018_11_23_15_56_46 DEFAULT (1 of 1) ========--disks ...
- windows下使用redis c++
redis是高效key-value NOSQL 数据库 代码开源 windows下使用需要使用微软在redis官方上的改进版 地址 https://redis.io/download 寻找window ...
- JavaSE 初学进度条JProgressBar
预备知识 创建进度条类后将其直接加入JFrame看看效果 public class JProgressBarDemo2 { public static void main(String args[]) ...
- JAVA中内部类(匿名内部类)访问的局部变量为什么要用final修饰?
本文主要记录:在JAVA中,(局部)内部类访问某个局部变量,为什么这个局部变量一定需要用final 关键字修饰? 首先,什么是局部变量?这里的局部是:在方法里面定义的变量. 因此,内部类能够访问某局部 ...
- android资源文件
代码与资源分离原则:便于维护与修改shape:定义图形 selector:按照不同的情况加载不同的color或drawable layer-list:从下往上图形层叠加载 资源文件有:/res/dra ...
- [smf]论坛实现编辑器附件插入的插件
smf论坛代码的相关介绍,可以参见博客园的其他文章,这里不再详细说明了. 插件功能: 在帖子里的任何位置插入附件图片. 关于安装: 该插件已经经过“维尼熊的百宝箱”的修改,在新版本SMF 2.0.14 ...