RecyclerView+index索引实现仿微信通讯录
2017-01-17 10:05
609 查看
感觉之前写的有点乱,所以有重新整理了一下这个博客:
写之前卡了几片博客:
http://blog.csdn.net/zxt0601/article/details/52420706
http://blog.csdn.net/zxt0601/article/details/52355199
http://blog.piasy.com/2016/03/26/Insight-Android-RecyclerView-ItemDecoration/?utm_source=tuicool&utm_medium=referral
效果图:
通过查询系统数据后,拿到数据后这里只取了名字name和手机号number两个字段,头像等字段什么的没有取。然后通过Pinyin.toPinyin将中文转换成拼音,然后利用getSortKey截取第一个字母并转换成大写字母,存到Bean里,tag就是存这些大写字母的字段。这里中文转换成拼音:
Pinyin的引用
getSortKey截取第一个字母并转换成大写字母
因为我们需要把数据按照A-Z的顺序进行排序,这里我们存数据的Bean。
然后重写他的方法:
Bean完成这些后,重载Collections.sort();方法即可,这样就完成了数据字母A—Z的排序
直接用addItemDecoration即可,这是两个同名的重载方法:
4000
addItemDecoration(ItemDecoration decor) 常用,(按照add顺序,依次渲染ItemDecoration)
addItemDecoration(ItemDecoration decor, int index) add一个ItemDecoration,并为它指定顺序
在源码中可以看到:
我们再继续跟踪mItemDecorations,发现我们add的ItemDecoration存储在RecyclerView类中的mItemDecorations集合中。
在RecyclerView.ItemDecoration这个类中有三个方法:
-
如果我们自定义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)。
这里mTitleHeight 是这样获取的:TypedValue.applyDimension方法是转变为标准尺寸的一个函数
onDraw方法:
完成上面两步就已经把title绘制好了。
onDrawOver方法:带悬停头部的分组列表
下面就开始绘制右侧的索引indexbar了
完成这些已经实现了右侧索引indexBar
下面我们给自己的操作添加一些行为响应:
重写onTouchEvent事件主要功能有
a.处理手指按下时的View背景变色,抬起时恢复原来颜色
b.根据手指触摸的落点坐标,判断当前处于哪个index区域
c.回调给相应的监听器处理(显示当前index的值,滑动RecyclerView至相应区域等。。)
最后就是实现recyclerview和indexbar的联动
这样就完成了RecyclerView与indexbar的联动
整个功能就完成了!
个人表述能力有限,有什么不好的地方欢迎指出
demo下载地址:http://download.csdn.net/detail/qq_34501274/9799175
最近跟朋友聊天,说道博客相关的事,朋友说,有什么好写的没什么用,你想用的网上都有,又不是什么新东西! 反正我个人不是这样认为的,即使同样的功能,从不会,到上网查资料,看别人的博客,最后自己搞明白了。我认为写博客就跟做笔记一样的。以后在用的着,可以直接在自己博客上找了!这样岂不更好写之前卡了几片博客:
http://blog.csdn.net/zxt0601/article/details/52420706
http://blog.csdn.net/zxt0601/article/details/52355199
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; //获得联系人默认uri ContentResolver 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即可,这是两个同名的重载方法:
4000
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方法:
//绘制在最下层 @Override public 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(); //得到一共需要绘制多少个 title int 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的 //绘制title drawTitleArea(c, paddingLeft, right, child, params, position); } else {//其他的通过判断 if (null != mDatas.get(position).tag && !mDatas.get(position).tag.equals(mDatas.get(position - 1).tag)) { //不为空 且跟前一个tag不一样了,说明是新的分类,也要title drawTitleArea(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方法:带悬停头部的分组列表
/**绘制在最上层 */ @Override public 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).itemView mPaint.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 p cae8 ressI = (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的监听 * */ @Override public void onIndexPressed(int index, String text) { if (tv_index != null) { //显示TexView tv_index.setVisibility(View.VISIBLE); tv_index.setText(text); } // 滑动Rv if (mManager != null) { int position = getPosByTag(text); if (position != -1) { mManager.scrollToPositionWithOffset(position, 0); } } } /** * 触摸事件结束 * */ @Override public void onMotionEventEnd() { //隐藏TextView if (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的联动
整个功能就完成了!
个人表述能力有限,有什么不好的地方欢迎指出
相关文章推荐
- 配置View桌面时找不到域的解决方法
- 完全克隆的虚拟桌面部署问题
- 移除VMware View桌面中孤立的主机与桌面池
- 为虚拟桌面配置网络负载均衡
- flex 控件的重要属性
- 学习Winform文本类控件(Label、Button、TextBox)
- Delphi控件ListView的属性及使用方法详解
- web下载的ActiveX控件自动更新
- MySQL 5.7 create VIEW or FUNCTION or PROCEDURE
- WinForm实现按名称递归查找控件的方法
- 实例讲解JavaScript的Backbone.js框架中的View视图
- C#中父窗口和子窗口之间控件互操作实例
- C#编写ActiveX网页截图控件
- Android编程之Button控件用法实例分析
- 详解Android XML中引用自定义内部类view的四个why
- Android控件之CheckBox、RadioButton用法实例分析
- Android中View自定义组合控件的基本编写方法
- Android 自定义View步骤
- Android 自定义View的构造函数详细介绍
- 在Android开发中使用自定义组合控件的例子