感觉之前写的有点乱,所以有重新整理了一下这个博客:

demo下载地址:http://download.csdn.net/detail/qq_34501274/9799175

最近跟朋友聊天,说道博客相关的事,朋友说,有什么好写的没什么用,你想用的网上都有,又不是什么新东西! 反正我个人不是这样认为的,即使同样的功能,从不会,到上网查资料,看别人的博客,最后自己搞明白了。我认为写博客就跟做笔记一样的。以后在用的着,可以直接在自己博客上找了!这样岂不更好

写之前卡了几片博客:

  1. http://blog.csdn.net/zxt0601/article/details/52420706
  2. http://blog.csdn.net/zxt0601/article/details/52355199
  3. http://blog.piasy.com/2016/03/26/Insight-Android-RecyclerView-ItemDecoration/?utm_source=tuicool&utm_medium=referral

    效果图:

1.获取手机通讯录联系人作为真实数据

public static final String[] PHONES_PROJECTION = new String[]{Phone.DISPLAY_NAME, //联系人姓名Phone.NUMBER,       //电话号码};/*** 获取手机联系人*/
public static List getContactInformation(Context context) {Uri contact_uri = Phone.CONTENT_URI; //获得联系人默认uriContentResolver resolver = context.getContentResolver();  //获得ContentResolver对象Cursor cursor = resolver.query(contact_uri, PHONES_PROJECTION, null, null, null); //获取电话本中开始一项光标List contacts = new ArrayList<Contacts>();if (null != cursor) {while (cursor.moveToNext()) {Contacts peson = new Contacts();/*** 获取电话号码*/String number = cursor.getString(1);/***  当手机号码为空的或者为空字段 跳过当前循环*/if (TextUtils.isEmpty(number))continue;/***  得到联系人名称*/String name = cursor.getString(0);peson.name = name;peson.phone = number;/***  Pinyin.toPinyin(peson.name.charAt(0))*  如果是中文转换成字母* */String zifuchuang = Pinyin.toPinyin(peson.name.charAt(0));peson.tag = getSortKey(zifuchuang);contacts.add(peson);}cursor.close();}return contacts;
}

通过查询系统数据后,拿到数据后这里只取了名字name和手机号number两个字段,头像等字段什么的没有取。然后通过Pinyin.toPinyin将中文转换成拼音,然后利用getSortKey截取第一个字母并转换成大写字母,存到Bean里,tag就是存这些大写字母的字段。这里中文转换成拼音:
Pinyin的引用 compile 'com.github.promeg:tinypinyin:1.0.0'

getSortKey截取第一个字母并转换成大写字母

/*** 截取首字母 转换成大写*/
public static final String getSortKey(String sortKeyString) {String key = sortKeyString.substring(0, 1).toUpperCase();if (key.matches("[A-Z]")) {return key;}return "#";
}

因为我们需要把数据按照A-Z的顺序进行排序,这里我们存数据的Bean。

public class Contacts  implements Comparable<Contacts>

然后重写他的方法:

public int compareTo(Contacts o) {return this.tag.compareTo(o.tag);//这里tag就是用来存储那些大写字母的,所以按这个字段进行排序
}

Bean完成这些后,重载Collections.sort();方法即可,这样就完成了数据字母A—Z的排序

/*** bean实现Comparable接口  然后重载 Collections.sort(mDatas);方法排序* A-Z* **/
Collections.sort(mDatas);

Recyclerview+index的实现

完成上面步骤后,就可以进入我们进入今天要说的Recyclerview+index实现仿微信通讯录

1.首先ItemDecoration的使用:

      mRv.setAdapter(mAdapter = new CityAdapter(this, mDatas));mRv.addItemDecoration(mDecoration = new TitleItemDecoration(this, mDatas));
//        //如果add两个,那么按照先后顺序,依次渲染。
//        //mRv.addItemDecoration(new TitleItemDecoration2(this,mDatas));
//        //分割线mRv.addItemDecoration(new DividerItemDecoration(MainActivity.this, DividerItemDecoration.VERTICAL_LIST));

直接用addItemDecoration即可,这是两个同名的重载方法:

  • addItemDecoration(ItemDecoration decor) 常用,(按照add顺序,依次渲染ItemDecoration)
  • addItemDecoration(ItemDecoration decor, int index) add一个ItemDecoration,并为它指定顺序

在源码中可以看到:

我们再继续跟踪mItemDecorations,发现我们add的ItemDecoration存储在RecyclerView类中的mItemDecorations集合中。

在RecyclerView.ItemDecoration这个类中有三个方法:

  • public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    super.getItemOffsets(outRect, view, parent, state);

  • -public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    super.onDraw(c, parent, state);

  • public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
    super.onDrawOver(c, parent, state);
    }

如果我们自定义ItemDecoration继承RecyclerView.ItemDecoration,必须重写这三个方法:
如果对ItemDecoration想更深的理解的话可以去看下这篇博客:http://blog.piasy.com/2016/03/26/Insight-Android-RecyclerView-ItemDecoration/?utm_source=tuicool&utm_medium=referral

  • getItemOffsets方法:

    通过 parent 获取 postion 信息,在通过 postion 拿到数据里的每个bean里的分类——tag,因为数据集已经排好序,如果tag与前一个tag不一样,说明是一个新的分类,
    则需要绘制头部outRect.set(0, mTitleHeight, 0, 0),否则不需要 outRect.set(0, 0, 0, 0)。

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {super.getItemOffsets(outRect, view, parent, state);int position = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();if (position > -1) {if (position == 0) {outRect.set(0, mTitleHeight, 0, 0);} else {/**如果数据  对应position的tag值不为空   且  对应position的tag值与下个数据源position的tag值不一样  需要设置title*/if (null != mDatas.get(position).tag && !mDatas.get(position).tag.equals(mDatas.get(position - 1).tag)) {outRect.set(0, mTitleHeight, 0, 0);//不为空 且跟前一个tag不一样了,说明是新的分类,也要title} else {outRect.set(0, 0, 0, 0);}}}
}
  • 这里mTitleHeight 是这样获取的:TypedValue.applyDimension方法是转变为标准尺寸的一个函数
mTitleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, context.getResources().getDisplayMetrics());
titleFontSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16, context.getResources().getDisplayMetrics());
  • onDraw方法:
//绘制在最下层@Overridepublic void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {super.onDraw(c, parent, state);//求出绘制title的位置 大小//绘制title的开始位置 左边距int paddingLeft = parent.getPaddingLeft();
//        绘制title的结束位置  右边距int right = parent.getWidth() - parent.getPaddingRight();//得到一共需要绘制多少个 titleint childCount = parent.getChildCount();for (int i = 0; i < childCount; i++) {final View child = parent.getChildAt(i);final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();int position = params.getViewLayoutPosition();//RecyclerView的item position在重置时可能为-1.保险点判断一下吧if (position > -1) {if (position == 0) {//等于0肯定要有title的//绘制titledrawTitleArea(c, paddingLeft, right, child, params, position);} else {//其他的通过判断if (null != mDatas.get(position).tag && !mDatas.get(position).tag.equals(mDatas.get(position - 1).tag)) {//不为空 且跟前一个tag不一样了,说明是新的分类,也要titledrawTitleArea(c, paddingLeft, right, child, params, position);} else {//none}}}}}private void drawTitleArea(Canvas c, int left, int right, View child, RecyclerView.LayoutParams params, int position) {//最先调用,绘制在最下层//设置tilte的背景颜色mPaint.setColor(COLOR_TITLE_BG);c.drawRect(left, child.getTop() - params.topMargin - mTitleHeight, right, child.getTop() - params.topMargin, mPaint);mPaint.setColor(COLOR_TITLE_FONT);
//Paint.getTextBounds(String text, int start, int end, Rect bounds)mPaint.getTextBounds(mDatas.get(position).tag, 0, mDatas.get(position).tag.length(), mBounds);c.drawText(mDatas.get(position).tag, child.getPaddingLeft(), child.getTop() - params.topMargin - (mTitleHeight / 2 - mBounds.height() / 2), mPaint);}

完成上面两步就已经把title绘制好了。

  • onDrawOver方法:带悬停头部的分组列表
  /**绘制在最上层
*/@Overridepublic void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {super.onDrawOver(c, parent, state);int pos = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition();String tag = mDatas.get(pos).tag;//View child = parent.getChildAt(pos);View child = parent.findViewHolderForLayoutPosition(pos).itemView;//出现一个奇怪的bug,有时候child为空,所以将 child = parent.getChildAt(i)。-》 parent.findViewHolderForLayoutPosition(pos).itemViewmPaint.setColor(COLOR_TITLE_BG);
/**
左   上  右  下   画笔  绘制的区域
*/c.drawRect(parent.getPaddingLeft(), parent.getPaddingTop(), parent.getRight() - parent.getPaddingRight(), parent.getPaddingTop() + mTitleHeight, mPaint);mPaint.setColor(COLOR_TITLE_FONT);
//Paint.getTextBounds(String text, int start, int end, Rect bounds)mPaint.getTextBounds(tag, 0, tag.length(), mBounds);c.drawText(tag, child.getPaddingLeft(),parent.getPaddingTop() + mTitleHeight - (mTitleHeight / 2 - mBounds.height() / 2),mPaint);}
  • 下面就开始绘制右侧的索引indexbar了
//    onFinishInflate 当View中所有的子控件均被映射成xml后触发
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);Log.e("onSizeChanged:-----","走了onSizeChanged"+"---"+w+"---"+h+"---"+oldw+"---"+oldh);//解决源数据为空 或者size为0的情况,if (null == mIndexDatas || mIndexDatas.isEmpty()) {return;}mWidth = w;height = h;/*** 得到每个字母的高度*用右侧indexbar总高度-padingtop和padingbottom=所有字母所占的高度,  然后除以所有字母得到每个字母的高度* */mGapHeight = (height - getPaddingTop() - getPaddingBottom()) / mIndexDatas.size();
}//绘制
@Override
protected void onDraw(Canvas canvas) {super.onDraw(canvas);Log.e("onDraw:-----","onDraw");int t = getPaddingTop();//top的基准点(支持padding)Rect indexBounds = new Rect();//存放每个绘制的index的Rect区域String index;//每个要绘制的index内容for (int i = 0; i < mIndexDatas.size(); i++) {//得到每个字母index = mIndexDatas.get(i);/**getTextBounds* 四个参数:* 1.要绘制的内容* 2.开始位置* 3.结束未知* 4.存放每个绘制的index的Rect区域* */paint.getTextBounds(index, 0, index.length(), indexBounds);Paint.FontMetrics fontMetrics = paint.getFontMetrics();//获得画笔的FontMetrics,用来计算baseLine。因为drawText的y坐标,代表的是绘制的文字的baseLine的位置int baseline = (int) ((mGapHeight - fontMetrics.bottom - fontMetrics.top) / 2);//计算出在每格index区域,竖直居中的baseLine值canvas.drawText(index, mWidth / 2 - paint.measureText(index) / 2, t + mGapHeight * i + baseline, paint);//调用drawText,居中显示绘制index}
}

完成这些已经实现了右侧索引indexBar

  • 下面我们给自己的操作添加一些行为响应:

    重写onTouchEvent事件主要功能有
    a.处理手指按下时的View背景变色,抬起时恢复原来颜色
    b.根据手指触摸的落点坐标,判断当前处于哪个index区域
    c.回调给相应的监听器处理(显示当前index的值,滑动RecyclerView至相应区域等。。)

@Override
public boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:setBackgroundColor(mPressedBackground);//手指按下时背景变色//注意这里没有break,因为down时,也要计算落点 回调监听器case MotionEvent.ACTION_MOVE:float y = event.getY();//通过计算判断落点在哪个区域:int pressI = (int) ((y - getPaddingTop()) / mGapHeight);//边界处理(在手指move时,有可能已经移出边界,防止越界)if (pressI < 0) {pressI = 0;} else if (pressI >= mIndexDatas.size()) {pressI = mIndexDatas.size() - 1;}//回调监听器if (null != mOnIndexPressedListener) {mOnIndexPressedListener.onIndexPressed(pressI, mIndexDatas.get(pressI));}Log.e("点击区域位置:",pressI+"-----------------------");break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:default:setBackgroundResource(android.R.color.transparent);//手指抬起时背景恢复透明//回调监听器if (null != mOnIndexPressedListener) {mOnIndexPressedListener.onMotionEventEnd();}break;}return true;
}
/*** 当前被按下的index的监听器*/
private onIndexPressedListener mOnIndexPressedListener;
public interface onIndexPressedListener {void onIndexPressed(int index, String text);//当某个Index被按下void onMotionEventEnd();//当触摸事件结束(UP CANCEL)
}
public void setmOnIndexPressedListener(onIndexPressedListener mOnIndexPressedListener) {this.mOnIndexPressedListener = mOnIndexPressedListener;
}
  • 最后就是实现recyclerview和indexbar的联动
*** 当按下index的监听* */@Overridepublic void onIndexPressed(int index, String text) {if (tv_index != null) { //显示TexViewtv_index.setVisibility(View.VISIBLE);tv_index.setText(text);}
//        滑动Rvif (mManager != null) {int position = getPosByTag(text);if (position != -1) {mManager.scrollToPositionWithOffset(position, 0);}}}
/*** 触摸事件结束* */@Overridepublic void onMotionEventEnd() {
//隐藏TextViewif (tv_index != null) {tv_index.setVisibility(View.GONE);}}
/*** 根据传入的pos返回tag** @param tag* @return*/
private int getPosByTag(String tag) {if (TextUtils.isEmpty(tag)) {return -1;}/*** 根据传入的index    遍历集合数据 拿到list<Contacts>中对应的tag   把tag对应的position返回* */for (int i = 0; i < mDatas.size(); i++) {if (tag.equals(mDatas.get(i).tag)) {return i;}}return -1;
}

这样就完成了RecyclerView与indexbar的联动
整个功能就完成了!
个人表述能力有限,有什么不好的地方欢迎指出

RecyclerView+index索引实现仿微信通讯录相关推荐

  1. android 通讯录 首字母索引,android仿微信通讯录搜索(匹配拼音,字母,索引位置标记颜色)...

    前言: 仿微信通讯录搜索功能,通过汉字或拼音首字母找到匹配的联系人并显示匹配的位置 一:先看效果图 字母索引 搜索匹配 二:功能分析 1:汉字转拼音 通讯录汉字转拼音(首个字符当考虑姓氏多音字), 现 ...

  2. android 字母搜索栏,android仿微信通讯录搜索示例(匹配拼音,字母,索引位置)

    前言: 仿微信通讯录搜索功能,通过汉字或拼音首字母找到匹配的联系人并显示匹配的位置 一:先看效果图 字母索引 搜索匹配 二:功能分析 1:汉字转拼音 通讯录汉字转拼音(首个字符当考虑姓氏多音字), 现 ...

  3. 【Android 仿微信通讯录 导航分组列表-上】使用ItemDecoration为RecyclerView打造带悬停头部的分组列表

    *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 转载请标明出处: http://blog.csdn.net/zxt0601/article/details/52355199 本文 ...

  4. uni-app 写小程序 索引列表,仿微信通讯录

    心里认定了一个女孩 就要好好的珍惜对待她,人生不容辜负,你必须要更加努力 .加油 骚年 uni-app 写小程序 索引列表,仿微信通讯录 去uni-app官网 下载插件 indexlist <m ...

  5. Android仿微信通讯录

    Android仿微信通讯录 分3部: 1.listview实现显示头像.名字(太简单,这里就不写了) 通讯录页面xml布局代码: <LinearLayout xmlns:android=&quo ...

  6. 【uniapp前端组件】仿微信通讯录列表组件

    仿微信通讯录列表组件 示例图 前言 仿微信通讯录列表组件,可实现通讯列表以及选择多个联系人功能. 组件介绍 本组件有三个自定义组件构成,都已经集成在bugking7-contact-list中,该组件 ...

  7. 仿微信通讯录的Demo----PinnedHeaderListView

    仿微信通讯录的Demo--PinnedHeaderListView 侧边栏首字母匹配 + header分组 本示例代码来自网上 这里只贴出效果图,附件Demo源码,仅供学习和以后参考时用 附件 Dem ...

  8. RN仿微信通讯录列表

    源码在此 先看一下预览图效果: pic1.jpg 首先通过构造器初始化state constructor(props) {super(props);this.state = {//Global这里是全 ...

  9. 仿微信通讯录滑动定位ListView功能

    既然是仿,那么我们来看看源微信通讯录是个什么样子什么功能以及我们实现后的效果,如下图所示:    下面我们就来一步一步剖析这个功能实现及其思路. 1.分析界面的构成 界面由上左右中四部分构成,控件分别 ...

最新文章

  1. python运行错误-Python在运行中发生错误怎么正确处理方法,案例详解!
  2. 一个小型的网页抓取系统的架构设计
  3. java xwork_xwork-core-2.3.4源码 - 源码下载|通讯/手机编程|J2ME|源代码 - 源码中国
  4. 【基础】pandas中apply与map的异同
  5. Method db.collection.distinct is not implemented
  6. datalist标签
  7. linux php和java环境变量配置_Linux下Java环境变量的安装与配置
  8. python3除法运算_Python2和Python3中除法操作/的不同
  9. java和javascript双引号嵌套的问题
  10. python运动目标检测_运动目标检测(3)—光流法
  11. 网络流(4)——带有容量的顶点和二部匹配
  12. 手机新闻网站,手持移动新闻,手机报client,jQuery Mobile手机新闻网站,手机新闻网站demo,新闻阅读器开发...
  13. <C语言>简单表白代码小❤❤
  14. 和Leo一起做爱线段树的好孩子HDU5238 Calculator
  15. 58同城的字体解密(一)
  16. “x = a if b else c“是啥意思?【赋值表达式】【if语句】
  17. 读标准02-IEEE1451.5-智能无线传感器标准介绍
  18. 美通企业周刊 | 中国平安将深度参与深圳公共住房建设;北京环球度假区将引入IMAX影院...
  19. 读毛姆《人性的枷锁》
  20. 美团招聘-测试开发工程师

热门文章

  1. 【01】 Nastran 生成adams接口模态中性文件(mnf文件)
  2. 阿里云Linux服务器部署Mysql,JDK以及Tomcat教程
  3. 已知每个部门有一个经理,统计输出部门名称、部门总人数、 总工资和部门经理。
  4. Win11聚焦锁屏壁纸不更新了?Win11锁屏聚焦不更换解决教程
  5. 读彼得林奇的成功投资有感
  6. 平时开发搜集的一些网址
  7. 华为近场通讯nfc在哪里打开_华为手机nfc感应区在手机哪个位置
  8. 基于摄像头实现手写输入字符功能(视频手写)
  9. 办公室搞笑记(2) 李姐
  10. Linux Shell字符串变量头尾去除空格