1、前言

最近在做公司项目的时候遇到一个添加手机联系人的需求,主要有以下几个功能点:

  • 读取联系人:读取用户手机上的通讯录里的联系人列表
  • 好友排序:按照拼音顺序对好友进行排序,兼容英文数字符号等
  • 字母索引:右侧字母导航条,既可拖动也可点击,联动ListView滑动
  • 匹配:最后要将通讯录里的联系人列表与后台数据库里的用户表进行匹配

最终的大致效果如下:

特意写篇博客将整个实现过程记录下来,方便以后再次遇到这样的需求的时候可以直接使用CV大法,也希望能帮到刚好有这方面需求的朋友。

2、读取联系人

读取手机通讯录里的联系人主要是通过ContentResolver 来获取的,代码比较固定,直接贴代码:

先定义一个用来接收联系人的数据bean,主要是对id,name和phone三个字段进行赋值,其他字段主要是为了排序和匹配用户表用到的。

/*** @author hydCoder* @date 2017/10/11 10:50* @desc 手机联系人的数据bean* @email hyd_coder@163.com*/public class ContactInfo implements Comparable<ContactInfo> {public String id;public String name;public String phone;public String pinyin; // 姓名对应的拼音public String firstLetter; // 拼音的首字母public String userAvatar;public String userName;public String userNick;public int    isFriend;public String userId;public int gradeLevel;public String userPosition;public String userCompany;public int userType;public boolean isUser = false;public ContactInfo(String id, String name, String phone) {this.id = id;this.name = name;this.phone = phone;pinyin = Cn2Spell.getPinYin(name); // 根据姓名获取拼音firstLetter = pinyin.substring(0, 1).toUpperCase(); // 获取拼音首字母并转成大写if (!firstLetter.matches("[A-Z]")) { // 如果不在A-Z中则默认为“#”firstLetter = "#";}}@Overridepublic int compareTo(@NonNull ContactInfo another) {if (firstLetter.equals("#") && !another.firstLetter.equals("#")) {return 1;} else if (!firstLetter.equals("#") && another.firstLetter.equals("#")){return -1;} else {return pinyin.compareToIgnoreCase(another.pinyin);}}
}
复制代码

获取联系人数据的工具类:

    /*** @author hydCoder* @date 2017/10/11 10:53* @desc 获取手机联系人数据* @email hyd_coder@163.com*/public class ContactUtils {/*** 获取联系人数据** @param context* @return*/public static List<ContactInfo> getAllContacts(Context context) {List<ContactInfo> list = new ArrayList<>();// 获取解析者ContentResolver resolver = context.getContentResolver();// 访问地址Uri raw_contacts = Uri.parse("content://com.android.contacts/raw_contacts");Uri data = Uri.parse("content://com.android.contacts/data");// 查询语句// select contact_id from raw_contacts;//1 2 3 4// select mimetype,data1 from view_data where raw_contact_id=3;// Cursor cursor=resolver.query(访问地址, 返回字段 null代表全部, where 语句, 参数, 排序)Cursor cursor = resolver.query(raw_contacts, new String[] { "contact_id" }, null, null, null);while (cursor.moveToNext()) {// getColumnIndex根据名称查列号String id = cursor.getString(cursor.getColumnIndex("contact_id"));// 创建实例String name = "";String phone = "";Cursor item = resolver.query(data, new String[] { "mimetype", "data1" }, "raw_contact_id=?", new String[] { id }, null);while (item.moveToNext()) {String mimetype = item.getString(item.getColumnIndex("mimetype"));String data1 = item.getString(item.getColumnIndex("data1"));if ("vnd.android.cursor.item/name".equals(mimetype)) {name = data1;} else if ("vnd.android.cursor.item/phone_v2".equals(mimetype)) {// 有的手机号中间会带有空格phone = data1.replace(" ","");}}ContactInfo info = new ContactInfo(id,name,phone);item.close();// 添加集合list.add(info);}cursor.close();return list;}
}
复制代码

3、好友排序和字母索引

3.1、右侧字母索引的导航条--SideBar

这个可以在网上找到很多类似的,你也可以找一个自己喜欢的甚至自己写一个出来,我在项目里用的是这个

github.com/AlexLiuShen…

我把他的SideBar.java拷贝到项目里,修改了部分代码:

   public class SideBar extends TextView {private String[] letters = new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "I","J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V","W", "X", "Y", "Z", "#"};private Paint textPaint;private Paint bigTextPaint;private Paint scaleTextPaint;private Canvas canvas;private int    itemH;private int    w;private int    h;/*** 普通情况下字体大小*/float singleTextH;/*** 缩放离原始的宽度*/private float scaleWidth;/*** 滑动的Y*/private float eventY = 0;/*** 缩放的倍数*/private int scaleSize = 1;/*** 缩放个数item,即开口大小*/private int scaleItemCount = 6;private ISideBarSelectCallBack callBack;public SideBar(Context context) {this(context, null);}public SideBar(Context context, AttributeSet attrs) {this(context, attrs, 0);}public SideBar(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(attrs);}private void init(AttributeSet attrs) {if (attrs != null) {TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.SideBar);scaleSize = ta.getInteger(R.styleable.SideBar_scaleSize, 1);scaleItemCount = ta.getInteger(R.styleable.SideBar_scaleItemCount, 6);scaleWidth = ta.getDimensionPixelSize(R.styleable.SideBar_scaleWidth, dp(100));ta.recycle();}textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);textPaint.setColor(getCurrentTextColor());textPaint.setTextSize(getTextSize());textPaint.setTextAlign(Paint.Align.CENTER);bigTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);bigTextPaint.setColor(getCurrentTextColor());bigTextPaint.setTextSize(getTextSize() * (scaleSize + 3));bigTextPaint.setTextAlign(Paint.Align.CENTER);scaleTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);scaleTextPaint.setColor(getCurrentTextColor());scaleTextPaint.setTextSize(getTextSize() * (scaleSize + 1));scaleTextPaint.setTextAlign(Paint.Align.CENTER);}public void setDataResource(String[] data) {letters = data;invalidate();}public void setOnStrSelectCallBack(ISideBarSelectCallBack callBack) {this.callBack = callBack;}/*** 设置字体缩放比例** @param scale*/public void setScaleSize(int scale) {scaleSize = scale;invalidate();}/*** 设置缩放字体的个数,即开口大小** @param scaleItemCount*/public void setScaleItemCount(int scaleItemCount) {this.scaleItemCount = scaleItemCount;invalidate();}private int dp(int px) {final float scale = getContext().getResources().getDisplayMetrics().density;return (int) (px * scale + 0.5f);}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:case MotionEvent.ACTION_MOVE:if (event.getX() > (w - getPaddingRight() - singleTextH - 10)) {eventY = event.getY();invalidate();return true;} else {eventY = 0;invalidate();break;}case MotionEvent.ACTION_CANCEL:eventY = 0;invalidate();return true;case MotionEvent.ACTION_UP:if (event.getX() > (w - getPaddingRight() - singleTextH - 10)) {eventY = 0;invalidate();return true;} elsebreak;}return super.onTouchEvent(event);}@Overrideprotected void onDraw(Canvas canvas) {this.canvas = canvas;DrawView(eventY);}private void DrawView(float y) {int currentSelectIndex = -1;if (y != 0) {for (int i = 0; i < letters.length; i++) {float currentItemY = itemH * i;float nextItemY = itemH * (i + 1);if (y >= currentItemY && y < nextItemY) {currentSelectIndex = i;if (callBack != null) {callBack.onSelectStr(currentSelectIndex, letters[i]);}//画大的字母Paint.FontMetrics fontMetrics = bigTextPaint.getFontMetrics();float bigTextSize = fontMetrics.descent - fontMetrics.ascent;canvas.drawText(letters[i], w - getPaddingRight() - scaleWidth - bigTextSize, singleTextH + itemH * i, bigTextPaint);}}}drawLetters(y, currentSelectIndex);}private void drawLetters(float y, int index) {//第一次进来没有缩放情况,默认画原图if (index == -1) {w = getMeasuredWidth();h = getMeasuredHeight();itemH = h / letters.length;Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();singleTextH = fontMetrics.descent - fontMetrics.ascent;for (int i = 0; i < letters.length; i++) {canvas.drawText(letters[i], w - getPaddingRight(), singleTextH + itemH * i, textPaint);}//触摸的时候画缩放图} else {//遍历所有字母for (int i = 0; i < letters.length; i++) {//要画的字母的起始Y坐标float currentItemToDrawY = singleTextH + itemH * i;float centerItemToDrawY;if (index < i)centerItemToDrawY = singleTextH + itemH * (index + scaleItemCount);elsecenterItemToDrawY = singleTextH + itemH * (index - scaleItemCount);float delta = 1 - Math.abs((y - currentItemToDrawY) / (centerItemToDrawY - currentItemToDrawY));float maxRightX = w - getPaddingRight();//如果大于0,表明在y坐标上方scaleTextPaint.setTextSize(getTextSize() + getTextSize() * delta);float drawX = maxRightX - scaleWidth * delta;//超出边界直接花在边界上if (drawX > maxRightX)canvas.drawText(letters[i], maxRightX, singleTextH + itemH * i, textPaint);elsecanvas.drawText(letters[i], drawX, singleTextH + itemH * i, scaleTextPaint);}}}public interface ISideBarSelectCallBack {void onSelectStr(int index, String selectStr);}}
复制代码

其中3个自定义的属性如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>  <declare-styleable name="SideBar">  <attr name="scaleSize" format="integer"/>  <attr name="scaleItemCount" format="integer"/>  <attr name="scaleWidth" format="dimension"/>  </declare-styleable>
</resources>
复制代码

3.2、汉字转拼音工具类

比较尴尬的是,java中是没有提供接口和方法让我们直接将汉字转成拼音的。所以我们只能自己想办法了,一般有以下两种办法:

**1、 使用第三方pinyin4j的jar包 ** 下载地址
Android Studio也可直接依赖

compile 'com.belerweb:pinyin4j:2.5.0'
复制代码

优点:使用简单,实用性好
缺点:需要依赖第三方jar包

2、 使用ASCII码和拼音的映射

优点:零依赖,只有一个Class,使用简单
缺点:只支持常见的一级汉字,对于一些不常见的汉字(亳bo)则无法正确获取拼音 (中文编码中一级汉字是按拼音排序的,容易映射。而二级汉字是按笔画部首排序的)

其实,无论是使用哪种方法,我发现都是没有去处理多音字的情况(毕竟这个真不好写),不过基本需求都可以满足啦。

我在这里直接使用的就是pinyin4j的jar包。但我还是基于pinyin4j写了个转换的工具类:

 /** * 汉字转换位汉语拼音,英文字符不变 */
public class Cn2Spell {  public static StringBuffer sb = new StringBuffer();  /** * 获取汉字字符串的首字母,英文字符不变 * 例如:阿飞→af */  public static String getPinYinHeadChar(String chines) {  sb.setLength(0);  char[] chars = chines.toCharArray();  HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();  defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);  defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);  for (int i = 0; i < chars.length; i++) {  if (chars[i] > 128) {  try {  sb.append(PinyinHelper.toHanyuPinyinStringArray(chars[i], defaultFormat)[0].charAt(0));  } catch (Exception e) {  e.printStackTrace();  }  } else {  sb.append(chars[i]);  }  }  return sb.toString();  }  /** * 获取汉字字符串的第一个字母 */  public static String getPinYinFirstLetter(String str) {  sb.setLength(0);  char c = str.charAt(0);  String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(c);  if (pinyinArray != null) {  sb.append(pinyinArray[0].charAt(0));  } else {  sb.append(c);  }  return sb.toString();  }  /** * 获取汉字字符串的汉语拼音,英文字符不变 */  public static String getPinYin(String chines) {  sb.setLength(0);  char[] nameChar = chines.toCharArray();  HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();  defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);  defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);  for (int i = 0; i < nameChar.length; i++) {  if (nameChar[i] > 128) {  try {  sb.append(PinyinHelper.toHanyuPinyinStringArray(nameChar[i], defaultFormat)[0]);  } catch (Exception e) {  e.printStackTrace();  }  } else {  sb.append(nameChar[i]);  }  }  return sb.toString();  }  }
复制代码

3.3、让联系人可以根据拼音来排序

代码在上面已经贴过了,这里就不重复贴了,其实就是让JavaBean实现comparable接口,并重写comparaTo方法。再在comparaTo方法里根据首字母判断,首字母为“#”都放在最后,都为“#”或者都是字母时才根据拼音来比较排序。

4、与后台数据库进行匹配

我与我们后台的开发大佬商量好的是将获取到的联系人集合里的手机号用","连接起来传给他,他再将匹配到的联系人数据返回给我,我再进行数据整合并处理交互逻辑。代码比较简单,这里就不贴了。

5、组装

由于没有把这个单独抽出来,所以只能贴部分关键代码。。。。。。

Activity的布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/gray_e5e5e5"><com.sdalolo.genius.utils.CustomNavigatorBarandroid:id="@+id/cn_bar"android:layout_width="match_parent"android:layout_height="@dimen/title_bar_size"android:paddingRight="10dp"android:layout_alignParentTop="true"app:leftImage="@drawable/backtrack"app:leftImageVisiable="true"app:leftTextVisibale="false"app:midText="添加手机联系人"app:midTextFontSize="@dimen/title_size"app:midTextFontColor="@color/auxiliary_color"app:rightTextVisible="false"app:rightImageVisible="false"app:titleBarBackground="@color/main_color"></com.sdalolo.genius.utils.CustomNavigatorBar><ListViewandroid:id="@+id/list_view"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_below="@+id/cn_bar"android:divider="@color/divide_color"android:scrollbars="none"android:dividerHeight="1dp"></ListView><com.sdalolo.genius.ui.view.SideBarandroid:id="@+id/side_bar"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_below="@+id/cn_bar"android:layout_alignParentRight="true"android:paddingRight="10dp"android:textColor="@color/auxiliary_color"android:textSize="14sp" /></RelativeLayout>
复制代码

activity中的部分代码:

mSideBar.setOnStrSelectCallBack(new SideBar.ISideBarSelectCallBack() {@Overridepublic void onSelectStr(int index, String selectStr) {if (mAllContacts != null) {for (int i = 0; i < mAllContacts.size(); i++) {if (selectStr.equalsIgnoreCase(mAllContacts.get(i).firstLetter)) {mListView.setSelection(i); // 选择到首字母出现的位置return;}}}}});
复制代码

adapter中的部分代码:

/*** 获取首字母首次出现位置*/
public int getPositionForSection(String catalog) {for (int i = 0; i < getCount(); i++) {String sortStr = list.get(i).firstLetter;if (catalog.equalsIgnoreCase(sortStr)) {return i;}}return -1;
}//如果当前位置等于该分类首字母的Char的位置 ,则认为是第一次出现if(position == getPositionForSection(catalog)){viewHolder.catalog.setVisibility(View.VISIBLE);viewHolder.catalog.setText(contact.firstLetter.toUpperCase());}else{viewHolder.catalog.setVisibility(View.GONE);}
复制代码

6、结语

到这里基本上效果就实现了,是不是很简单,不过如果编译版本大于23的话,记得动态申请Manifest.permission.READ_CONTACTS权限,不然会Crash,别问我怎么知道的。。。。。。

Android获取手机联系人匹配用户表并按字母A Z排序展示相关推荐

  1. Android获取手机联系人匹配用户表并按字母A-Z排序展示

    1.前言 最近在做公司项目的时候遇到一个添加手机联系人的需求,主要有以下几个功能点: 读取联系人:读取用户手机上的通讯录里的联系人列表 好友排序:按照拼音顺序对好友进行排序,兼容英文数字符号等 字母索 ...

  2. Android获取手机联系人的基本信息(如姓名、电话、邮箱、备注)

    在做项目的过程中,需要获取我们手机通讯录联系人的基本信息,如姓名.电话.邮箱.备注.昵称.公司.职位.家庭电话等等信息,下面就是我总结的一些具体方法. 1:首先读取联系人需要添加读取权限,6.0以上需 ...

  3. Android获取手机联系人或通讯录的基本信息(如姓名、电话)

    1.添加权限 <uses-permission android:name="android.permission.READ_CONTACTS"/> <uses-p ...

  4. Android 获取手机联系人代码

    ContentResolver resolver = getContentResolver();// 1. 查询raw_contacts表,把联系人的id取出来Uri uri = Uri.parse( ...

  5. 浅谈android中手机联系人字母索引表的实现

    实际上字母索引表的效果,可以说在现在的众多APP中使用的非常流行,比如支付宝,微信中的联系人,还有购物,买票的APP中选择全国城市,切换城市的时候,这时候的城市也就是按照一个字母索引的顺序来显示,看起 ...

  6. android 获取phone实例,Android ContentProvider获取手机联系人实例

    在做项目的时候,因为要用到我们自动获取联系人的姓名和电话,就想到了ContentProvider分享数据的功能,这样做既节省了时间,也减少了我们输入错误号码的几率,所以,想在这里把小demo分享给大家 ...

  7. android 使用内容提供者获取手机联系人

    最近在学习安卓的相关内容, 正好在写获取手机联系人的程序, 就想到了内容提供者, 这里有几点需要注意, 写到这里, 以后方便自己查询. 1. 手机联系人的数据库是存储在 data/data/com.a ...

  8. android的contentResolver,contentProvider简介(附带个获取手机联系人及头像实例)

    介绍 android中有个ContentProvider(内容提供者)还有个ContentResolver(内容解析者) 简单来说,contentProvider就是将自己app的数据库共享,使得其他 ...

  9. android 通过手机号码查询联系人,android获取手机通讯录联系人

    android获取手机通讯录联系人信息 private void getPhoneContacts() { ContentResolver resolver = this.getContentReso ...

最新文章

  1. 高斯模糊与高反差保留
  2. Java 利用replaceAll 替换中括号
  3. nginx并发模型与traffic_server并发模型简单比较
  4. dpkg: 处理软件包 xxx (--configure)时出错解决方法
  5. FPGA FIFO深度计算
  6. pygame小游戏代码_Py之pygame:有趣好玩——利用pygame库实现一个移动底座弹球的小游戏...
  7. Spring注解——使用@ComponentScan自动扫描组件
  8. c语言函数调用排序用插入法,C语言:编写查找和排序函数(二分查找,冒泡排序,选择排序法,插入排序)...
  9. php版本个版本区别,PHP版本不一样有什么区别
  10. Storm集群安装Version1.0.1开启Kerberos
  11. boost::filesystem 库的简单使用
  12. ERP技术在财务信息化转型中应用探讨
  13. 计算机系统通过执行通道程序完成数据,计算机系统结构_第四章练习 答案
  14. axure iphone8元件库_Axure8最全元件库整理
  15. Java Web实现使用浏览器下载文件代码
  16. 根据域名查询IP地址的网站推荐
  17. Android进阶-NDK学习完整版
  18. Flask部署机器学习模型---基于线性回归模型的销售预测系统实现简易版代码
  19. Win10 文件夹删不掉,提示需要来自XXX的权限才能对此文件夹进行更改
  20. 迁移学习与小样本学习

热门文章

  1. 学生计算机使用计算度方法是,计算器的使用对小学生学习数学的影响
  2. 全国行政区划数据(截止2019年3月)
  3. js仿萤石云的视频回放插件拖动效果-时间标尺timeRuler
  4. python之制作MP3文件
  5. RowBounds分页
  6. 数学是知识,哲学是智慧
  7. 局域网网内ping不通的故障解决方法总结
  8. JavaScript 隐式转换
  9. 遗言网站帮逝者继续交友
  10. sobel算法边缘检测python版