实现ListView A~Z快速索引

ListView A~Z快速索引这种效果在通信录和城市列表中经常看到,方便用户查找,是一种增加用户体验的好方法。

实现步骤:

   1.自定义一个名叫SlideBar 的View。

   2.在布局文件中加入这个自定义的View。

   3. 在Activity中处理监听事件。

接下来讲讲我是怎样实现的:

先来看SlideBar这个类:

[java]  view plain copy print ?
  1. package com.folyd.tuan.view;  
  2.   
  3. import android.content.Context;  
  4. import android.graphics.Canvas;  
  5. import android.graphics.Color;  
  6. import android.graphics.Paint;  
  7. import android.graphics.Typeface;  
    1. import android.util.AttributeSet;  
  8. import android.view.MotionEvent;  
  9. import android.view.View;  
  10.   
  11. /** 
  12.  * 自定义的View,实现ListView A~Z快速索引效果 
  13.  *  
  14.  * @author Folyd 
  15.  *  
  16.  */  
  17. public class SlideBar extends View {  
  18.     private Paint paint = new Paint();  
  19.     private OnTouchLetterChangeListenner listenner;  
  20.     // 是否画出背景  
  21.     private boolean showBg = false;  
  22.     // 选中的项  
  23.     private int choose = -1;  
  24.     // 准备好的A~Z的字母数组  
  25.     public static String[] letters = { "#""A""B""C""D""E""F""G",  
  26.             "H""I""J""K""L""M""N""O""P""Q""R""S""T",  
  27.             "U""V""W""X""Y""Z" };  
  28.   
  29.     // 构造方法  
  30.     public SlideBar(Context context) {  
  31.         super(context);  
  32.     }  
  33.   
  34.     public SlideBar(Context context, AttributeSet attrs) {  
  35.         super(context, attrs);  
  36.     }  
  37.   
  38.     @Override  
  39.     protected void onDraw(Canvas canvas) {  
  40.         super.onDraw(canvas);  
  41.         // 获取宽和高  
  42.         int width = getWidth();  
  43.         int height = getHeight() - 30;  
  44.         // 每个字母的高度  
  45.         int singleHeight = height / letters.length;  
  46.         if (showBg) {  
  47.             // 画出背景  
  48.             canvas.drawColor(Color.parseColor("#55000000"));  
  49.         }  
  50.         // 画字母  
  51.         for (int i = 0; i < letters.length; i++) {  
  52.             paint.setColor(Color.BLACK);  
  53.             // 设置字体格式  
  54.             paint.setTypeface(Typeface.DEFAULT_BOLD);  
  55.             paint.setAntiAlias(true);  
  56.             paint.setTextSize(20f);  
  57.             // 如果这一项被选中,则换一种颜色画  
  58.             if (i == choose) {  
  59.                 paint.setColor(Color.parseColor("#F88701"));  
  60.                 paint.setFakeBoldText(true);  
  61.             }  
  62.             // 要画的字母的x,y坐标  
  63.             float posX = width / 2 - paint.measureText(letters[i]) / 2;  
  64.             float posY = i * singleHeight + singleHeight;  
  65.             // 画出字母  
  66.             canvas.drawText(letters[i], posX, posY, paint);  
  67.             // 重新设置画笔  
  68.             paint.reset();  
  69.         }  
  70.     }  
  71.   
  72.     /** 
  73.      * 处理SlideBar的状态 
  74.      */  
  75.     @Override  
  76.     public boolean dispatchTouchEvent(MotionEvent event) {  
  77.         final float y = event.getY();  
  78.         // 算出点击的字母的索引  
  79.         final int index = (int) (y / getHeight() * letters.length);  
  80.         // 保存上次点击的字母的索引到oldChoose  
  81.         final int oldChoose = choose;  
  82.         switch (event.getAction()) {  
  83.         case MotionEvent.ACTION_DOWN:  
  84.             showBg = true;  
  85.             if (oldChoose != index && listenner != null && index > 0  
  86.                     && index < letters.length) {  
  87.                 choose = index;  
  88.                 listenner.onTouchLetterChange(event, letters[index]);  
  89.                 invalidate();  
  90.             }  
  91.             break;  
  92.   
  93.         case MotionEvent.ACTION_MOVE:  
  94.             if (oldChoose != index && listenner != null && index > 0  
  95.                     && index < letters.length) {  
  96.                 choose = index;  
  97.                 listenner.onTouchLetterChange(event, letters[index]);  
  98.                 invalidate();  
  99.             }  
  100.             break;  
  101.         case MotionEvent.ACTION_UP:  
  102.         default:  
  103.             showBg = false;  
  104.             choose = -1;  
  105.             if (listenner != null && index > 0 && index < letters.length)  
  106.                 listenner.onTouchLetterChange(event, letters[index]);  
  107.             invalidate();  
  108.             break;  
  109.         }  
  110.         return true;  
  111.     }  
  112.   
  113.     /** 
  114.      * 回调方法,注册监听器 
  115.      *  
  116.      * @param listenner 
  117.      */  
  118.     public void setOnTouchLetterChangeListenner(  
  119.             OnTouchLetterChangeListenner listenner) {  
  120.         this.listenner = listenner;  
  121.     }  
  122.   
  123.     /** 
  124.      * SlideBar 的监听器接口 
  125.      *  
  126.      * @author Folyd 
  127.      *  
  128.      */  
  129.     public interface OnTouchLetterChangeListenner {  
  130.   
  131.         void onTouchLetterChange(MotionEvent event, String s);  
  132.     }  
  133.   
  134. }  

然后在布局文件中加入这个自定义的控件:
[html]  view plain copy print ?
  1. <!-- 上面的代码省略掉了-->  
  2.   
  3.   
  4. <FrameLayout  
  5.     android:layout_width="match_parent"  
  6.     android:layout_height="wrap_content" >  
  7.   
  8.     <ListView  
  9.         android:id="@android:id/list"  
  10.         android:layout_width="match_parent"  
  11.         android:layout_height="wrap_content"  
  12.         android:divider="@drawable/line3" />  
  13.   
  14.     <TextView  
  15.         android:id="@+id/float_letter"  
  16.         android:layout_width="80dp"  
  17.         android:layout_height="80dp"  
  18.         android:layout_gravity="center"  
  19.         android:background="#F88701"  
  20.         android:gravity="center"  
  21.         android:textSize="40sp"  
  22.         android:visibility="gone" />  
  23.   
  24.     <com.folyd.tuan.view.SlideBar  
  25.         android:id="@+id/slideBar"  
  26.         android:layout_width="30dp"  
  27.         android:layout_height="wrap_content"  
  28.         android:layout_gravity="right|bottom" />  
  29. </FrameLayout>  

然后在Activity中注册监听事件:

[java]  view plain copy print ?
  1. mSlideBar  
  2.                 .setOnTouchLetterChangeListenner(new OnTouchLetterChangeListenner() {  
  3.   
  4.                     @Override  
  5.                     public void onTouchLetterChange(MotionEvent event, String s) {  
  6.   
  7.                         float_letter.setText(s);  
  8.                         switch (event.getAction()) {  
  9.                         case MotionEvent.ACTION_DOWN:  
  10.                         case MotionEvent.ACTION_MOVE:  
  11.                             float_letter.setVisibility(View.VISIBLE);  
  12.                             break;  
  13.   
  14.                         case MotionEvent.ACTION_UP:  
  15.                         default:  
  16.                             float_letter.postDelayed(new Runnable() {  
  17.   
  18.                                 @Override  
  19.                                 public void run() {  
  20.                                     float_letter.setVisibility(View.GONE);  
  21.                                 }  
  22.                             }, 100);  
  23.                             break;  
  24.                         }  
  25.                        int position  = array.indexOf(s);//这个array就是传给自定义Adapter的  
  26.                        mListView.setSelection(position);//调用ListView的setSelection()方法就可实现了  
  27.                     }  
  28.                 });  

实现效果如下:


     


不过这样子有一个小小的Bug 。

请看图:



我的Bug是这样的:如果用户手指滑过A之后一直向上滑到,滑到切换城市的黄色标题栏(或滑过Z之后一直向下到滑划出屏幕),因为在整个FrameLayout内用户一直没有弹起手指,所以不能触发MotionEvent.ACTION_UP 这个状态,中间的TextView不能消失。可是我试了MotionEvent的其他一些状态,甚至在switch语句中后面加个default都没用。

---------------------------------------*******************************************************------------------------------------------------

我研究的时候发现只要showBg值为true,中间的字母就显示,而当showBg 的值为false的时候中间的字母就可以消失。只要SlideBar的状态为ACTION_DOWN和ACTION_MOVE 的时候showBg的值为true,而ACTION_UP的时候showBg的值就为false;

所以根据上面这个特征,我们只要把OnToucheLetterChange()这个回调函数的参数改一下就可以了。改成onTouchLetterChange(boolean isTouched, String s) 

boolean类型的参数直接把showBg传过去就可以了。


改进后的代码如下:

  1. package com.folyd.tuan.view;  
  2. import android.content.Context;  
  3. import android.graphics.Canvas;  
  4. import android.graphics.Color;  
  5. import android.graphics.Paint;  
  6. import android.graphics.Typeface;  
  7. import android.util.AttributeSet;  
  8. import android.view.MotionEvent;  
  9. import android.view.View;  
  10. /**
  11.  * 自定义的View,实现ListView A~Z快速索引效果
  12.  * 
  13.  * @author Folyd
  14.  * 
  15.  */
  16. publicclass SlideBar extends View {  
  17. private Paint paint = new Paint();  
  18. private OnTouchLetterChangeListenner listenner;  
  19. // 是否画出背景
  20. privateboolean showBg = false;  
  21. // 选中的项
  22. privateint choose = -1;  
  23. // 准备好的A~Z的字母数组
  24. publicstatic String[] letters = { "#""A""B""C""D""E""F""G",  
  25. "H""I""J""K""L""M""N""O""P""Q""R""S""T",  
  26. "U""V""W""X""Y""Z" };  
  27. // 构造方法
  28. public SlideBar(Context context) {  
  29. super(context);  
  30.     }  
  31. public SlideBar(Context context, AttributeSet attrs) {  
  32. super(context, attrs);  
  33.     }  
  34. @Override
  35. protectedvoid onDraw(Canvas canvas) {  
  36. super.onDraw(canvas);  
  37. // 获取宽和高
  38. int width = getWidth();  
  39. int height = getHeight();  
  40. // 每个字母的高度
  41. int singleHeight = height / letters.length;  
  42. if (showBg) {  
  43. // 画出背景
  44.             canvas.drawColor(Color.parseColor("#55000000"));  
  45.         }  
  46. // 画字母
  47. for (int i = 0; i < letters.length; i++) {  
  48.             paint.setColor(Color.BLACK);  
  49. // 设置字体格式
  50.             paint.setTypeface(Typeface.DEFAULT_BOLD);  
  51.             paint.setAntiAlias(true);  
  52.             paint.setTextSize(20f);  
  53. // 如果这一项被选中,则换一种颜色画
  54. if (i == choose) {  
  55.                 paint.setColor(Color.parseColor("#F88701"));  
  56.                 paint.setFakeBoldText(true);  
  57.             }  
  58. // 要画的字母的x,y坐标
  59. float posX = width / 2 - paint.measureText(letters[i]) / 2;  
  60. float posY = i * singleHeight + singleHeight;  
  61. // 画出字母
  62.             canvas.drawText(letters[i], posX, posY, paint);  
  63. // 重新设置画笔
  64.             paint.reset();  
  65.         }  
  66.     }  
  67. /**
  68.      * 处理SlideBar的状态
  69.      */
  70. @Override
  71. publicboolean dispatchTouchEvent(MotionEvent event) {  
  72. finalfloat y = event.getY();  
  73. // 算出点击的字母的索引
  74. finalint index = (int) (y / getHeight() * letters.length);  
  75. // 保存上次点击的字母的索引到oldChoose
  76. finalint oldChoose = choose;  
  77. switch (event.getAction()) {  
  78. case MotionEvent.ACTION_DOWN:  
  79.             showBg = true;  
  80. if (oldChoose != index && listenner != null && index > 0
  81.                     && index < letters.length) {  
  82.                 choose = index;  
  83.                 listenner.onTouchLetterChange(showBg, letters[index]);  
  84.                 invalidate();  
  85.             }  
  86. break;  
  87. case MotionEvent.ACTION_MOVE:  
  88. if (oldChoose != index && listenner != null && index > 0
  89.                     && index < letters.length) {  
  90.                 choose = index;  
  91.                 listenner.onTouchLetterChange(showBg, letters[index]);  
  92.                 invalidate();  
  93.             }  
  94. break;  
  95. case MotionEvent.ACTION_UP:  
  96.             showBg = false;  
  97.             choose = -1;  
  98. if (listenner != null) {  
  99. if (index <= 0) {  
  100.                     listenner.onTouchLetterChange(showBg, "A");  
  101.                 } elseif (index > 0 && index < letters.length) {  
  102.                     listenner.onTouchLetterChange(showBg, letters[index]);  
  103.                 } elseif (index >= letters.length) {  
  104.                     listenner.onTouchLetterChange(showBg, "Z");  
  105.                 }  
  106.             }  
  107.             invalidate();  
  108. break;  
  109.         }  
  110. returntrue;  
  111.     }  
  112. /**
  113.      * 回调方法,注册监听器
  114.      * 
  115.      * @param listenner
  116.      */
  117. publicvoid setOnTouchLetterChangeListenner(  
  118.             OnTouchLetterChangeListenner listenner) {  
  119. this.listenner = listenner;  
  120.     }  
  121. /**
  122.      * SlideBar 的监听器接口
  123.      * 
  124.      * @author Folyd
  125.      * 
  126.      */
  127. publicinterface OnTouchLetterChangeListenner {  
  128. void onTouchLetterChange(boolean isTouched, String s);  
  129.     }  
  130. }  


Activity中就很容易处理了:

  1. mSlideBar  
  2.                 .setOnTouchLetterChangeListenner(new OnTouchLetterChangeListenner() {  
  3. @Override
  4. publicvoid onTouchLetterChange(boolean isTouched, String s) {  
  5.                         float_letter.setText(s);  
  6. if (isTouched) {  
  7.                             float_letter.setVisibility(View.VISIBLE);  
  8.                         } else {  
  9.                             float_letter.postDelayed(new Runnable() {  
  10. @Override
  11. publicvoid run() {  
  12.                                     float_letter.setVisibility(View.GONE);  
  13.                                 }  
  14.                             }, 100);  
  15.                         }  
  16. int position = array.indexOf(s);  
  17.                         mListView.setSelection(position);  
  18.                     }  
  19.                 });  

这样就解决Bug了。哈哈。


良心的公众号,更多精品文章,不要忘记关注哈

《Android和Java技术栈》


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值