常用的自定义控件四(QuickBarView)

常用的自定义控件四(QuickBarView)

自定义View 通讯录字母快速索引



在Android日常开发中,我们经常在联系人界面看到一些字母导航栏,点击字母的时候,会根据汉字的首拼音来查找是否存在相应的item,这种效果很常见,几乎所有涉及到通讯的都会用到,包括qq,微信,微博等,今天我为大家带来的就是这种自定义控件

废话不多说 ,大家先来看一下实际的效果

  • 效果图一

转载请注明原博客地址:http://blog.csdn.net/gdutxiaoxu/article/details/51804865

源码下载地址:https://github.com/gdutxiaoxu/QuickIndex.git

大家先来看一下源码

  1. /**
  2. * 博客地址:http://blog.csdn.net/gdutxiaoxu
  3. * 快速索引,根据字母的索引查找相应的联系人
  4. *
  5. * @author xujun
  6. * @time 2015/11/1 21:40.
  7. */
  8. public class QuickIndexBar extends View {
  9. private static final String[] LETTERS = new String[]{
  10. "A", "B", "C", "D", "E", "F",
  11. "G", "H", "I", "J", "K", "L",
  12. "M", "N", "O", "P", "Q", "R",
  13. "S", "T", "U", "V", "W", "X",
  14. "Y", "Z"};
  15. private static final String TAG = "xujun";
  16. private Paint mPaint;
  17. //字母的宽度
  18. private int cellWidth;
  19. //字母的高度
  20. private float cellHeight;
  21. //记录上一次触摸的Index
  22. private int mLastTouchIndex = -1;
  23. //字母被选中显示的颜色
  24. private int mSelectColor = Color.GRAY;
  25. //字母正常显示的颜色
  26. private int mNormalColor = Color.WHITE;
  27. private Context mContext;
  28. /**
  29. * 暴露一个字母的监听
  30. */
  31. public interface OnLetterUpdateListener {
  32. void onLetterUpdate(String letter);
  33. }
  34. private OnLetterUpdateListener listener;
  35. public OnLetterUpdateListener getListener() {
  36. return listener;
  37. }
  38. /**
  39. * 设置字母更新监听
  40. *
  41. * @param listener
  42. */
  43. public void setListener(OnLetterUpdateListener listener) {
  44. this.listener = listener;
  45. }
  46. public QuickIndexBar(Context context) {
  47. this(context, null);
  48. }
  49. public QuickIndexBar(Context context, AttributeSet attrs) {
  50. this(context, attrs, 0);
  51. }
  52. public QuickIndexBar(Context context, AttributeSet attrs, int defStyle) {
  53. super(context, attrs, defStyle);
  54. mContext = context;
  55. //初始化自定义属性
  56. obtainAttrs(attrs);
  57. // 初始化画笔
  58. mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  59. float textSize = UIUtils.dip2px(15, mContext);
  60. mPaint.setTextSize(textSize);
  61. mPaint.setTypeface(Typeface.DEFAULT_BOLD);
  62. }
  63. private void obtainAttrs(AttributeSet attrs) {
  64. TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.QuickIndexBar);
  65. int selectColor = typedArray.getColor(R.styleable.QuickIndexBar_select_color, -1);
  66. if (selectColor != -1) {
  67. mSelectColor = selectColor;
  68. }
  69. int normalColor = typedArray.getColor(R.styleable.QuickIndexBar_normal_color, -1);
  70. if (normalColor != -1) {
  71. mNormalColor = normalColor;
  72. }
  73. typedArray.recycle();
  74. }
  75. @Override
  76. protected void onDraw(Canvas canvas) {
  77. for (int i = 0; i < LETTERS.length; i++) {
  78. String text = LETTERS[i];
  79. // 计算坐标
  80. int x = (int) (cellWidth / 2.0f - mPaint.measureText(text) / 2.0f);
  81. // 获取文本的高度
  82. Rect bounds = new Rect();// 矩形
  83. mPaint.getTextBounds(text, 0, text.length(), bounds);
  84. int textHeight = bounds.height();
  85. int y = (int) (cellHeight / 2.0f + textHeight / 2.0f + i * cellHeight);
  86. // 根据按下的字母, 设置画笔颜色
  87. mPaint.setColor(mLastTouchIndex == i ? mSelectColor : mNormalColor);
  88. // 绘制文本A-Z
  89. canvas.drawText(text, x, y, mPaint);
  90. }
  91. }
  92. @Override
  93. public boolean onTouchEvent(MotionEvent event) {
  94. int index = -1;
  95. switch (MotionEventCompat.getActionMasked(event)) {
  96. case MotionEvent.ACTION_DOWN:
  97. // 获取当前触摸到的字母索引
  98. index = (int) (event.getY() / cellHeight);
  99. if (index >= 0 && index < LETTERS.length) {
  100. // 判断是否跟上一次触摸到的一样,不一样才进行回调
  101. if (index != mLastTouchIndex) {
  102. if (listener != null) {
  103. //
  104. listener.onLetterUpdate(LETTERS[index]);
  105. }
  106. Log.d(TAG, "onTouchEvent: " + LETTERS[index]);
  107. //记录上一次触摸的Index为当前的index;
  108. mLastTouchIndex = index;
  109. }
  110. }
  111. break;
  112. case MotionEvent.ACTION_MOVE:
  113. index = (int) (event.getY() / cellHeight);
  114. if (index >= 0 && index < LETTERS.length) {
  115. // 判断是否跟上一次触摸到的一样
  116. if (index != mLastTouchIndex) {
  117. if (listener != null) {
  118. listener.onLetterUpdate(LETTERS[index]);
  119. }
  120. Log.d(TAG, "onTouchEvent: " + LETTERS[index]);
  121. mLastTouchIndex = index;
  122. }
  123. }
  124. break;
  125. case MotionEvent.ACTION_UP:
  126. // 手指抬起的时候重置
  127. mLastTouchIndex = -1;
  128. break;
  129. default:
  130. break;
  131. }
  132. //调用这个方法会重新调用draw方法,重新绘制
  133. invalidate();
  134. return true;
  135. }
  136. /**
  137. * 当大小 改变的时候会回调这个方法,
  138. * 这里我们就不主动调用measure()方法了
  139. *
  140. * @param w
  141. * @param h
  142. * @param oldw
  143. * @param oldh
  144. */
  145. @Override
  146. protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  147. super.onSizeChanged(w, h, oldw, oldh);
  148. // 获取单元格的宽和高
  149. cellWidth = getMeasuredWidth();
  150. int mHeight = getMeasuredHeight();
  151. cellHeight = mHeight * 1.0f / LETTERS.length;
  152. }
  153. }

代码 解析

  • 代码其实不长,加上一些注释总共才180多行,总体来说,思路分分为以下几个步骤

    1. 在构造方法里面初始化画笔,同时为了使用方便,我们封装了自定义属性
  1. public QuickIndexBar(Context context, AttributeSet attrs, int defStyle) {
  2. super(context, attrs, defStyle);
  3. mContext = context;
  4. //初始化自定义属性
  5. obtainAttrs(attrs);
  6. // 初始化画笔
  7. mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  8. float textSize = UIUtils.dip2px(15, mContext);
  9. mPaint.setTextSize(textSize);
  10. mPaint.setTypeface(Typeface.DEFAULT_BOLD);
  11. }
  12. private void obtainAttrs(AttributeSet attrs) {
  13. TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.QuickIndexBar);
  14. int selectColor = typedArray.getColor(R.styleable.QuickIndexBar_select_color, -1);
  15. if (selectColor != -1) {
  16. mSelectColor = selectColor;
  17. }
  18. int normalColor = typedArray.getColor(R.styleable.QuickIndexBar_normal_color, -1);
  19. if (normalColor != -1) {
  20. mNormalColor = normalColor;
  21. }
  22. typedArray.recycle();
  23. }
  1. 接着我们在onSizeChange 方法里面拿到我们时间的宽度和高度,有人可能会问了为什么不在onMeasure里面获取了,其实在onMeasure方法里面获取是可以的,只不过我们还需要调用一下measure方法而已,在onSizeChnage方法里面,我们直接调用
  1. cellWidth = getMeasuredWidth();

即可获取到我们需要的宽度和高度。用起来比较方便,不过更多的是为了让大家知道View有这一个方法存在以及怎么使用它

顺便我们来看一下google官方对onSizeChange方法的解释

This is called during layout when the size of this view has changed. If you were just added to the view hierarchy, you‘re called with the old values of 0.

从官方的解释我们可以知道这个方法是在onLayout方法中当大小改变的时候会调用这个方法,因此我们直接调用getMeasuredWidth();是可以获取得到宽度的,因为onMeasure 是先于onLayout方法调用的。

3. 接着我们重写onDraw方法,在onDraw方法我们所做的工作就是绘制 我们需要的26个字母

  1. protected void onDraw(Canvas canvas) {
  2. for (int i = 0; i < LETTERS.length; i++) {
  3. String text = LETTERS[i];
  4. // 计算坐标
  5. int x = (int) (cellWidth / 2.0f - mPaint.measureText(text) / 2.0f);
  6. // 获取文本的高度
  7. Rect bounds = new Rect();// 矩形
  8. mPaint.getTextBounds(text, 0, text.length(), bounds);
  9. int textHeight = bounds.height();
  10. int y = (int) (cellHeight / 2.0f + textHeight / 2.0f + i * cellHeight);
  11. // 根据按下的字母, 设置画笔颜色
  12. mPaint.setColor(mLastTouchIndex == i ? mSelectColor : mNormalColor);
  13. // 绘制文本A-Z
  14. canvas.drawText(text, x, y, mPaint);
  15. }
  16. }
  1. 讲到这里,我们的工作已经完成一大半了,接着就是处理我们是按下或者一个字母了,我们重写onTouchEvent方法,并且return true;是为了保证 Action_down动作按下以后,Action_move以后的动作能够顺利接受到,这涉及到View的事件分发机制,有空的话我会尝试总结一下,这里就不说了
  1. // 获取当前触摸到的字母索引
  2. index = (int) (event.getY() / cellHeight);

同时我们记录下我们当前是触摸或者按下哪一个字母

  1. mLastTouchIndex = index;
  1. 知道了我们当前是触摸或者按下哪一个字母了,那我们要怎样将这些信息暴露出去了,不难想象就是采用接口回调的方法,为此我们提供了这样一个接口
  1. /**
  2. * 暴露一个字母的监听
  3. */
  4. public interface OnLetterUpdateListener {
  5. void onLetterUpdate(String letter);
  6. }

并且提供了设置监听器的方法,这样我们就成功将我们的按下字母的信息提供给外界了

  1. public void setListener(OnLetterUpdateListener listener) {
  2. this.listener = listener;
  3. }

详细代码如下

  1. int index = -1;
  2. switch (MotionEventCompat.getActionMasked(event)) {
  3. case MotionEvent.ACTION_DOWN:
  4. // 获取当前触摸到的字母索引
  5. index = (int) (event.getY() / cellHeight);
  6. if (index >= 0 && index < LETTERS.length) {
  7. // 判断是否跟上一次触摸到的一样,不一样才进行回调
  8. if (index != mLastTouchIndex) {
  9. if (listener != null) {
  10. //
  11. listener.onLetterUpdate(LETTERS[index]);
  12. }
  13. Log.d(TAG, "onTouchEvent: " + LETTERS[index]);
  14. //记录上一次触摸的Index为当前的index;
  15. mLastTouchIndex = index;
  16. }
  17. }
  18. break;
  19. case MotionEvent.ACTION_MOVE:
  20. index = (int) (event.getY() / cellHeight);
  21. if (index >= 0 && index < LETTERS.length) {
  22. // 判断是否跟上一次触摸到的一样
  23. if (index != mLastTouchIndex) {
  24. if (listener != null) {
  25. listener.onLetterUpdate(LETTERS[index]);
  26. }
  27. Log.d(TAG, "onTouchEvent: " + LETTERS[index]);
  28. mLastTouchIndex = index;
  29. }
  30. }
  31. break;
  32. case MotionEvent.ACTION_UP:
  33. // 手指抬起的时候重置
  34. mLastTouchIndex = -1;
  35. break;
  36. default:
  37. break;
  38. }
  39. //调用这个方法会重新调用draw方法,重新绘制
  40. invalidate();
  41. return true;

到此 QuickBarView的源码分析为止,下面我们来学习一下是怎样结合ListView使用的



下面我贴出核心代码,想仔细了解的请点击源码下载 源码下载地址:

  1. mQuickIndexBar.setListener(new OnLetterUpdateListener() {
  2. @Override
  3. public void onLetterUpdate(String letter) {
  4. // UIUtils.showToast(getApplicationContext(), letter);
  5. showLetter(letter);
  6. // 根据字母定位ListView, 找到集合中第一个以letter为拼音首字母的对象,得到索引
  7. for (int i = 0; i < persons.size(); i++) {
  8. Person person = persons.get(i);
  9. String l = person.getPinyin().charAt(0) + "";
  10. if (TextUtils.equals(letter, l)) {
  11. // 匹配成功
  12. mListView.setSelection(i);
  13. break;
  14. }
  15. }
  16. }
  17. });

思路解析 如下

1. 在我们的List数据里面查找是否有有相应的首字母是触摸的字母,有的话返回相应的index,

2. 然后再调用ListView的setSelection(i)方法选中哪一个Item

  1. mListView.setSelection(i);

至于有些item有显示字母,有一些没有显示字母,其实就是判断上一个item的首字母是不是跟当前的首字母是不是一样的,不一样的话,显示当前item的字母,不过要注意一点,就是position等于0的时候,我们需要做特殊处理,代码如下

  1. String str = null;
  2. String currentLetter = p.getPinyin().charAt(0) + "";
  3. // 根据上一个首字母,决定当前是否显示字母
  4. if(position == 0){
  5. str = currentLetter;
  6. }else {
  7. // 上一个人的拼音的首字母
  8. String preLetter = persons.get(position - 1).getPinyin().charAt(0) + "";
  9. if(!TextUtils.equals(preLetter, currentLetter)){
  10. str = currentLetter;
  11. }
  12. }
  13. // 根据str是否为空,决定是否显示索引栏
  14. mViewHolder.mIndex.setVisibility(str == null ? View.GONE : View.VISIBLE);

到此我们的分析为止

转载请注明原博客地址:http://blog.csdn.net/gdutxiaoxu/article/details/51804865

源码下载地址:https://github.com/gdutxiaoxu/QuickIndex.git

时间: 2024-08-28 14:38:39

常用的自定义控件四(QuickBarView)的相关文章

Go语言开发(十四)、Go语言常用标准库四

Go语言开发(十四).Go语言常用标准库四 一.heap 1.heap简介 heap仅仅提供了最小堆的操作,没有提供堆的数据结构,堆的数据结构必须由开发者自己实现.heap提供了一个heap.Interface接口来作为堆的操作和堆的数据结构(开发者自己实现)之间的桥梁,堆的数据结构必须满足此接口: type Interface interface { sort.Interface Push(x interface{}) // add x as element Len() Pop() inter

java基础教程-常用类(四)

四.常用类 4.1字符串相关的类(String StringBuffer) 4.1.1String类   4.1.2StringBuffer类(代表可变的) 解释一下可变和不可变 String s1 = “hello”; String s2 = “world”; s1+=s2; 实际上又开辟了一块内存,将hello和world copy进去,s1指向新的内存 而StringBuffer只是在s1后面增加了一块内存,把world加上,不需要copy String与StringBuffer只有这一定

Android TVBox开发:logcat常用命令,四条足矣

看过我博文的朋友就知道,我目前从事Android智能机顶盒的Application/Framework的开发工作,因为开发程的调试是使用secureCRT连着机顶盒串口的,那么logcat命令自然少不了.众多logcat命令,常用的有哪些呢?我来告诉你,就四条! 转载请注明出处和链接:http://blog.csdn.net/xiong_it/article/details/45197851 logcat -c 清除已有log,一般在查看log前使用此命令 logcat -s ActivityM

TensorFlow基础——常用函数(四)

Tensorflow一些常用基本概念与函数(四) 摘要:本系列主要对tf的一些常用概念与方法进行描述.本文主要针对tensorflow的模型训练Training与测试Testing等相关函数进行讲解.为‘Tensorflow一些常用基本概念与函数’系列之四. 1.序言 本文所讲的内容主要为以下列表中相关函数.函数training()通过梯度下降法为最小化损失函数增加了相关的优化操作,在训练过程中,先实例化一个优化函数,比如 tf.train.GradientDescentOptimizer,并基

CSS常用样式(四)之animation

上篇CSS常用样式(三)这篇博文中已经介绍过了CSS中具有动画效果的transition.transform,今天来大概说说CSS中的animation.animation的加入会使得动画效果更加乐观. animation animation的实现需要通过keyframes来实现.keyframes(关键帧),类似于flash当中的关键帧.关键帧有其自己的语法规则,他的命名是由"@keyframes"开头,后面紧接着是这个“动画的名称”加上一对花括号“{}”,括号中就是一些不同时间段样

CSS3 常用属性(四)-- 过渡、动画

过渡--transition 过渡这个属性的作用是当元素的样式发生变化时,使用动画的效果进行变化.有了过渡属性后,很多情况下,写一些简单效果,将不需要再借助 javascript 去计算. transition  过渡属性简写,可以设置四个值 transition-property 过渡的CSS的名字,或是all transition-duration  从一个状态到另一个状态的的时间 transition-timing-function 过渡效果的动画曲线,默认easy,linear是匀速,c

Android自定义控件(四)仿网易客户端上拉加载更多

上一篇仿得网页客户端的抽屉模式,这一篇继续,来写一写加载更多这个功能,通过自定义实现加载更多,先上图: 今天实现的就是如图中最下面的20条载入中...这个功能啦! 先来说一下思路: 1.在listview中加入20条载入中的这个布局并隐藏 2.加入OnScrollListener监听,通过监听滚动事件,当滚动到最低端的时候,显示上面的布局 3.通过接口回调实现加载更多的功能 4.加载完数据时,通知listview加载结束,隐藏上面的布局文件 下面直接上代码: 1.在listview中加入20条载

java常用设计模式(四)装饰者模式

设计模式第四篇,装饰者模式,大家多多指教. 简介 装饰者模式是动态的将责任附加到对象上(引自<Head First设计模式>).这里的重点在于动态这两个字,我们都知道继承的实现的方式,它是是类编译的时候就去加载文件,属于一种静态的附加,而我们要实现动态的附加就不能单纯的通过继承来实现.在这种背景下,装饰者模式就应运而生了.装饰者模式的实现:首先所有的类都有一个共同的抽象,这个抽象可以是一个抽象类,也可以是一个接口,所有的类该抽象的子类或者实现.语言描述比较抽象,下面我们通过一个例子来描述该模式

Redis常用命令(四)数据库管理、键管理、订阅发布

### 数据库管理 keys pattern  # 查找键,参数通配符查找 keys *  # 查看所有键 keys n*  # 查看以n开头的所有键 keys *e  # 查看以e结尾的所有键 keys h?llo keys h[ae]llo exists name  # 查看name这个键是否存在,存在为1,不存在为0 type key  # 查看键对应的value的类型 type name del key1 key2 ...  # 删除键及对应的值 del addr rename key