鱼眼索引控件详解之一 —— 自定义索引器

来源:互联网 发布:js获取img标签的src 编辑:程序博客网 时间:2024/04/16 20:20

前言:只有苦练七十二变,才能笑对八十一难,戏里如此,人生亦然。————六小龄童


相关文章:

1、《鱼眼索引控件详解之一 —— 自定义索引器》
2、《鱼眼索引控件详解之二 —— 快速索引实现》

有些同学,想要多我出些有关自定义控件的知识,我也就顺应民意吧,在总结其它知识的同时,夹杂着出些有关自定义控件的博客给大家。

我们先看看这个系列的最终效果图:


从这个效果图中,可以看到,通过这个系列我们将涉及下面几个方面的知识:

  •  最右边索引器控件的实现(本篇内容)
  • 将索引器与ListView相关联,实现自动索引
在这个效果图中,有个bug:当鼠标在索引器中选中一个字母时,标示选中(字母变黄)却不是鼠标选中的那个字母,这个bug应该是跟使用的是模拟器有关,现实手机中不存在这个问题。

这篇文章先详解索引器控件如何自定义的。本文的最终效果图如下:


一、框架搭建

这部分,我们首先搭出一个框架,把索引条建好,先看效果图:


1、自定义View —— IndexSideBar

首先,创建一个工程BlogSideBar
然后创建一个派生自View的类:IndexSideBar

[java] view plain copy
  1. public class IndexSideBar extends View {  
  2.   
  3.     public IndexSideBar(Context context, AttributeSet attrs) {  
  4.         super(context, attrs);  
  5.     }  
  6.       
  7. }      
非常简单,添加一个构造函数即可。然后将此控件加入到MyActivity的布局文件中:(main.xml)
[html] view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.                 android:orientation="vertical"  
  4.                 android:layout_width="fill_parent"  
  5.                 android:layout_height="fill_parent"  
  6.                 android:background="#ffffff">  
  7.   
  8.     <com.example.BlogSideBar.IndexSideBar  
  9.             android:id="@+id/index_slide_bar"  
  10.             android:layout_width="23dp"  
  11.             android:layout_height="match_parent"  
  12.             android:layout_alignParentRight="true"  
  13.             android:layout_marginRight="10dp"  
  14.             android:layout_marginTop="10dp"  
  15.             android:layout_marginBottom="10dp"  
  16.             android:background="@drawable/index_letter_bg"/>  
  17. </RelativeLayout>       
在xml中,我们将IndexSideBar靠右排列,宽度设为23dp,高度为match_parent,边距为10dp;
非常注意的是,我们给IndexSideBar设置了背景图片(index_letter_bg.9.png):

如果现在运行一下,结果将是下面这样的除了拉伸的背景以外,其它都是一片空白:


这是因为,对于自定义的View,背景可以通过xml来添加,但其中的内容都是通过onDraw函数自己画上去的。由于我们还没有重写onDraw函数,当然除了背景以外的位置都是空白。

2、在View上写上字

在文章的底部,会有点九图中定义显示区域的讲解,但着重在讲一点:**对于自定义控件,不受点九图显示区域的限制,控件的整个区域都是可绘制的。**这里我们需要先明白一点,对于自定义控件,控件中的所有区域都是可画的。对于点九图的显示区域限定,只对系统控件起作用!
下面我们就讲讲,如何实现这节的效果:在顶部画一个小圆圈,在下面画上文字。
我们先看完整的代码,然后再细讲:

[java] view plain copy
  1. public static String[] b = {"#","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"};  
  2. private List<String> indexContent = new ArrayList<>();  
  3. private Paint paint = new Paint();  
  4. public IndexSideBar(Context context, AttributeSet attrs) {  
  5.     super(context, attrs);  
  6.     initIndex();  
  7. }  
  8.   
  9.   
  10. private void initIndex(){  
  11.     for (String str:b){  
  12.         indexContent.add(str);  
  13.     }  
  14. }  
  15.   
  16.   
  17. Override  
  18. protected void onDraw(Canvas canvas) {  
  19.     super.onDraw(canvas);  
  20.   
  21.     int height = getHeight();// 获取对应高度  
  22.     int width = getWidth(); // 获取对应宽度  
  23.     int singleHeight = height / (indexContent.size() + 1);// 获取每一个字母的高度  
  24.   
  25.     //画圆圈  
  26.     paint.setColor(Color.parseColor("#fa7829"));  
  27.     paint.setStyle(Paint.Style.FILL);  
  28.     canvas.drawCircle(width / 2.0f, singleHeight/2, singleHeight/4, paint);  
  29.     paint.reset();  
  30.   
  31.   
  32.     //写字  
  33.     for (int i = 0; i < indexContent.size(); i++) {  
  34.         paint.setColor(Color.parseColor("#888888"));  
  35.         paint.setTypeface(Typeface.DEFAULT_BOLD);  
  36.         paint.setAntiAlias(true);  
  37.         paint.setTextSize(22);  
  38.         paint.setTextAlign(Paint.Align.CENTER);  
  39.   
  40.         //x源相对坐标设置为中间位置  
  41.         float xPos = width / 2;  
  42.   
  43.         //给点中间线的位置,计算出baseline位置  
  44.         Paint.FontMetricsInt fm = paint.getFontMetricsInt();  
  45.         int center  = singleHeight * (i+1) + singleHeight/2;  
  46.         int baseline = center + (fm.bottom - fm.top)/2 - fm.bottom;  
  47.         canvas.drawText(indexContent.get(i), xPos, baseline, paint);  
  48.   
  49.         paint.reset();// 重置画笔  
  50.     }  
  51. }  
这段代码看起来特别长,其实只是分为三部分: 

(1)、初始化

我们将一些共用的变量设置为成员变量并给他们初始化,代码如下:
[java] view plain copy
  1. public static String[] b = {"#","A""B""C""D""E""F""G""H""I","J""K""L""M""N""O""P""Q""R""S""T""U""V",  
  2. "W""X""Y""Z"};  
  3. private List<String> indexContent = new ArrayList<>();  
  4. private Paint paint = new Paint();  
  5. public IndexSideBar(Context context, AttributeSet attrs) {  
  6.     super(context, attrs);  
  7.     initIndex();  
  8. }  
  9.   
  10.   
  11. private void initIndex(){  
  12.     for (String str:b){  
  13.         indexContent.add(str);  
  14.     }  
  15. }  
这段代码,其实就是初始化两个变量,一个是paint,一个是盛装索引字符的List indexContent;在初始化List indexContent时,我们首先定义一个字符数组 String[] b,通过数组b来初始化indexContent。 

(2)画圆圈

在初始化之后,直接就进入重写onDraw函数,在布局上画图了。首先,我们来看看,圆圈是怎么画上去的。
上面我们讲过,控件的任何位置都是可以画的。但控件的位置包括哪些呢?控件的位置就是我们通过xml布局以后,控件的所在位置,针对IndexSideBar而言,它的可绘制部分如下图右侧红框区域所示: 

先看最右侧的IndexSideBar:

  • 红色框,框起来的矩形就是我们控件的所在位置,整个矩形位置都是可以绘图的。在onDraw函数中,通过getWidth()能够得到控件的显示宽度,通过getHeight能够得到控件的显示高度。
  • 由于我们要均匀摆放所有要显示的字符,所以我们将总高度getHeight()等分,由于加上圆圈总共有indexContent.size()+1个字符,所以每一个字符的所占高度是:

[java] view plain copy
  1. int singleHeight = getHeight()/(indexContent.size()+1);  
然后再看左边的圆圈放大图,从图中可以看到:
  • 圆圈的半径是单个字符高度singleHeight的四分之一;
  • 圆圈中心点的位置在x = getWidth()/2, y = singleHeight/2的位置。 
综合上面的讲解,我们再来看看画圆圈的代码:
[java] view plain copy
  1. protected void onDraw(Canvas canvas) {  
  2.    super.onDraw(canvas);  
  3.   
  4.    int height = getHeight();// 获取对应高度  
  5.    int width = getWidth(); // 获取对应宽度  
  6.    int singleHeight = height / (indexContent.size() + 1);// 获取每一个字母的高度  
  7.   
  8.    //画圆圈  
  9.    paint.setColor(Color.parseColor("#fa7829"));  
  10.    paint.setStyle(Paint.Style.FILL);  
  11.    canvas.drawCircle(width / 2.0f, singleHeight/2, singleHeight/4, paint);  
  12.    paint.reset();  
  13. }  
首先,得到单个字符的高度singleHeight:
[java] view plain copy
  1. int height = getHeight();// 获取对应高度  
  2. int width = getWidth(); // 获取对应宽度  
  3. int singleHeight = height / (indexContent.size() + 1);// 获取每一个字母的高度  
然后画出圆形:
[java] view plain copy
  1. paint.setColor(Color.parseColor("#fa7829"));  
  2. paint.setStyle(Paint.Style.FILL);  
  3. canvas.drawCircle(width / 2.0f, singleHeight/2, singleHeight/4, paint);  
  4. paint.reset();  
我们有了圆心位置,圆的半径以后,很容易就能画出圆形了,如果有对画圆函数不了解的同学,可以参考《android Graphics(一):概述及基本几何图形绘制》 

(3)、写文字

在文字的问题最关键的有两点:第一:找到当前要绘制的是哪个字符 ,第二:找到这个字符的基线位置。有关drawText是以基线为基准来绘图的问题,如果有同学不了解,请先移步《android Graphics( 五):drawText()详解》  

前面已经说过,我们将总高度getHeight()根据字符个数等分,每个字符所占的高度是singleHeight;
如上图所示,我们假设要得到字符C的基线位置。
首先,我们要得到字符C之前所有字符的总高度,即C之前的所有字符个数,即四个。所以C字符的所占位置起始位置是singleHeight * 4;但注意一点是,这个起始位置,可并不是我们字符开始画的位置,我们只知道,每个字符占的间距是singleHeight,但单个字符真正在singleHeight的什么位置开始画,我们是不知道的。但我们唯一能确定的是每个字符在singleHeight中都是居中的!这就对了,我们能到字符C的中间线的位置:

[java] view plain copy
  1. centerLine = singleHeight *4 + singleHeight/2;  
即字符C的起始高度 singleHeight *4再加上一半的singleHeight的高度就是字符C的中间线的位置。 
《android Graphics( 五):drawText()详解》中我们讲过,如何根据中间线得到基线的公式为:
[java] view plain copy
  1. baseline = centerLine + (FontMetrics.bottom - FontMetrics.top)/2 - FontMetrics.bottom;  
所以字符C的中间线也就出来了:
[java] view plain copy
  1. centerLine = singleHeight *4 + singleHeight/2;  
  2. baseline = centerLine + (FontMetrics.bottom - FontMetrics.top)/2 - FontMetrics.bottom;  
好了,我们现在看写文字的代码:
[java] view plain copy
  1. //写字  
  2. for (int i = 0; i < indexContent.size(); i++) {  
  3.     paint.setColor(Color.parseColor("#888888"));  
  4.     paint.setTypeface(Typeface.DEFAULT_BOLD);  
  5.     paint.setAntiAlias(true);  
  6.     paint.setTextSize(22);  
  7.   
  8.     // x坐标等于中间-字符串宽度的一半.  
  9.     float xPos = width / 2 - paint.measureText(indexContent.get(i)) / 2;  
  10.   
  11.     //给点中间线的位置,计算出baseline位置  
  12.     Paint.FontMetricsInt fm = paint.getFontMetricsInt();  
  13.     int center  = singleHeight * (i+1) + singleHeight/2;  
  14.     int baseline = center + (fm.bottom - fm.top)/2 - fm.bottom;  
  15.     canvas.drawText(indexContent.get(i), xPos, baseline, paint);  
  16.   
  17.     paint.reset();// 重置画笔  
  18. }  
这段代码是的第一部分,就是设置画笔:
[java] view plain copy
  1. paint.setColor(Color.parseColor("#888888"));  
  2. paint.setTypeface(Typeface.DEFAULT_BOLD);  
  3. paint.setAntiAlias(true);  
  4. paint.setTextSize(22);  
  5. paint.setTextAlign(Paint.Align.CENTER);  
  6.   
  7. // x源相对坐标设置为中间位置  
  8. float xPos = width / 2;  
在这段代码里,最重要的是注意这两句:
[java] view plain copy
  1. paint.setTextAlign(Paint.Align.CENTER);  
  2. // x源绘制坐标设置为中间位置  
  3. float xPos = width / 2;  
根据《android Graphics( 五):drawText()详解》可知,将相对位置设置为控件中间位置的中间,那么这个文字将会在控件正中间绘制出来。理解不了的同学,请移步看看这个篇文章,这里就不再重讲一遍了。 
接下来是就是计算出基线位置了:
[java] view plain copy
  1. Paint.FontMetricsInt fm = paint.getFontMetricsInt();  
  2. int center  = singleHeight * (i+1) + singleHeight/2;  
  3. int baseline = center + (fm.bottom - fm.top)/2 - fm.bottom;  
这些就是利用我们前面讲过的原理,先得到中间线center的位置,然后再利用中间线求基线位置的公式得到基线位置。
最后画出当前的字符:
[java] view plain copy
  1. canvas.drawText(indexContent.get(i), xPos, baseline, paint);  
好了,到这里,我们就在IndexSideBar上画出了所有的字符了。下面我们就是要捕捉点击事件,在用户点击某个字符的时候,将该字符画为选中状态。

二、高级进阶

1、选中状态捕捉

先看看效果图: 

从效果图中可以看到在选中一个字母时,我们做了两件事:

  • 将顶点的圆圈高亮
  • 将选中的字母高亮

(1)、获取当前选中的字母

首先,我们要拿到当前选中哪个字母,所以要重写onTouchEvent()根据当前用户手指的位置来得到选中的字母,代码如下:

[java] view plain copy
  1. private int mChoose = -1;  
  2. public boolean onTouchEvent(MotionEvent event) {  
  3.     final float y = event.getY();// 点击y坐标  
  4.     // 点击y坐标所占总高度的比例*b数组的长度就等于点击b中的个数.  
  5.     int pos = (int)(y / getHeight() * (indexContent.size()+1));  
  6.     //由于总共的字符中包含最顶层的圆圈,所以indexContent中真正字符的位置应当在当前mChoose位置上减一  
  7.     mChoose = pos -1 ;  
  8.   
  9.     switch (event.getAction()) {  
  10.         case MotionEvent.ACTION_CANCEL:  
  11.         case MotionEvent.ACTION_UP:{  
  12.             mChoose = -1;  
  13.         }  
  14.         break;  
  15.         default:  
  16.         break;  
  17.     }  
  18.     //强制重绘  
  19.     invalidate();  
  20.     return true;  
  21. }  
首先定义一个成员变量来标识当前选中字母的索引:
初始值设置为-1,表示未选中任何值。

[java] view plain copy
  1. private int mChoose = -1;  
然后是通过当前手指位置来计算当前选中是哪个字母:
[java] view plain copy
  1. final float y = event.getY();// 点击y坐标  
  2. // 点击y坐标所占总高度的比例*b数组的长度就等于点击b中的个数.  
  3. int pos = (int)(y / getHeight() * (indexContent.size()+1));  
  4. //由于总共的字符中包含最顶层的圆圈,所以indexContent中真正字符的位置应当在当前pos位置上减一  
  5. mChoose = pos -1 ;  
在这段代码中, 我们通过event.getY()来获得用户当前手指的位置。 
然后我们来推算下得到当前字母位置的公式: 
我们知道单个字符所占的高度为:singleHeight = getHeight/(indexContent.size() + 1); 
当前手指选中的字符所在位置应该是int pos = y/singleHeight; 
所以当把singleHeight的公式代入int pos = y/singleHeight;以后就得到: 
int pos = (int)(y / getHeight() * (indexContent.size()+1));
这里要非常注意的是,这里的pos表示的是全部字符中的位置,包含第一个字符圆圈。所以要得到indexContent中字符的索引,就需要在pos的基础上减去1; 
然后当手指抬起的时候,取消选中
[java] view plain copy
  1. switch (event.getAction()) {  
  2.     case MotionEvent.ACTION_CANCEL:  
  3.     case MotionEvent.ACTION_UP:{  
  4.         mChoose = -1;  
  5.     }  
  6.     break;  
  7.     default:  
  8.     break;  
  9. }  
  10. //强制重绘  
  11. invalidate();  
当用户手指抬起的时候,将mChoose设置为-1,表示当前没有选中任何值。 
最后是强制重绘,强制刷新当前控件。 
拦截消息
[java] view plain copy
  1. return true;  
最后在return的时候,一定要return true!不然你会发现,只有点击的时候那一下会走到OnTouchEvent()以后的ACTION_MOVE和ACTION_UP都不会再走到OnTouchEvent()中了。 
在onTouchEvent中,如果return true表示当前事件已经被这个控件消费掉了,就不会再向它的父控件传递了。如果return false,则表示当前事件没有被消费,会继续向父控件传递。 
但因为对于ACTION_DOWN事件而言,如果当前事件没有return true,就表示当前控件没有消费ACTION_DOWN事件,那么以后的ACTION_MOVE和ACTION_UP就不会再向这个控件再次传递了。有关OnTouchEvent和onInterceptTouchEvent知识还是比较复杂的,大家可以去搜搜相关的文章仔细研究下,后续我也会出这方面的文章。 

(2)、画图时,画出选中状态

在得到当前选中字符的索引以后,就需要在画图的时候,根据当前选中字符的位置来设置选中字符的颜色:
完整的OnDraw代码如下:
[java] view plain copy
  1. protected void onDraw(Canvas canvas) {  
  2.     super.onDraw(canvas);  
  3.   
  4.     // 获取焦点改变背景颜色.  
  5.     int height = getHeight();// 获取对应高度  
  6.     int width = getWidth(); // 获取对应宽度  
  7.     int singleHeight = height / (indexContent.size() + 1);// 获取每一个字母的高度  
  8.   
  9.     //画圆圈  
  10.     if (-1 == mChoose){  
  11.         paint.setColor(Color.parseColor("#888888"));  
  12.     }else {  
  13.         paint.setColor(Color.parseColor("#fa7829"));  
  14.     }  
  15.     paint.setStyle(Paint.Style.FILL);  
  16.     paint.setAntiAlias(true);  
  17.     canvas.drawCircle(width / 2.0f, singleHeight / 2, singleHeight / 4, paint);  
  18.     paint.reset();  
  19.   
  20.   
  21.     //写字  
  22.     for (int i = 0; i < indexContent.size(); i++) {  
  23.         if (i == mChoose){  
  24.             paint.setColor(Color.parseColor("#fa7829"));  
  25.         }else {  
  26.             paint.setColor(Color.parseColor("#888888"));  
  27.         }  
  28.         paint.setTypeface(Typeface.DEFAULT_BOLD);  
  29.         paint.setAntiAlias(true);  
  30.         paint.setTextSize(22);  
  31.         paint.setTextAlign(Paint.Align.CENTER);  
  32.   
  33.         //x源相对坐标设置为中间位置  
  34.         float xPos = width / 2;  
  35.   
  36.         //给点中间线的位置,计算出baseline位置  
  37.         Paint.FontMetricsInt fm = paint.getFontMetricsInt();  
  38.         int center = singleHeight * (i + 1) + singleHeight / 2;  
  39.         int baseline = center + (fm.bottom - fm.top) / 2 - fm.bottom;  
  40.         canvas.drawText(indexContent.get(i), xPos, baseline, paint);  
  41.   
  42.         paint.reset();// 重置画笔  
  43.     }  
  44.   
  45. }  
在这里,我们做在两个地方做了改变: 
(1)、画圆圈时
[java] view plain copy
  1. //画圆圈  
  2. if (-1 == mChoose){  
  3.     paint.setColor(Color.parseColor("#888888"));  
  4. }else {  
  5.     paint.setColor(Color.parseColor("#fa7829"));  
  6. }  
  7. paint.setStyle(Paint.Style.FILL);  
  8. paint.setAntiAlias(true);  
  9. canvas.drawCircle(width / 2.0f, singleHeight / 2, singleHeight / 4, paint);  
在画圆圈时,根据当前mChoose是否等于-1,即是否有选中字符来设置圆圈的填充色。
(2)写字时
在写字时,根据当前要画的字符的索引是否是选中的字符索引来设置文字的颜色,代码如下:
[java] view plain copy
  1.   for (int i = 0; i < indexContent.size(); i++) {  
  2.       if (i == mChoose){  
  3.           paint.setColor(Color.parseColor("#fa7829"));  
  4.       }else {  
  5.           paint.setColor(Color.parseColor("#888888"));  
  6.       }  
  7.       paint.setTypeface(Typeface.DEFAULT_BOLD);  
  8.       paint.setAntiAlias(true);  
  9.       paint.setTextSize(22);  
  10.       paint.setTextAlign(Paint.Align.CENTER);  
  11.   
  12. …………  
到这里,设置选中字体字体颜色的代码也就结束了。下面我们就来看看在选中一个字符时,使用放大器将基显示出来是如何做出来的。

2、使用放大器

我们先看看效果吧: 

从效果图中可以看出来,这里我们要实现两个效果:

  • 当我们选中一个字母时,使用一个放大器把它显示出来
  • 在我们松开手指时,放大器消失
其实实现方法很简单,放大器其实就是一个TextView,在我们选中一个字母时,把它设置给这个TextView; 
大家知道,IndexSideBar是派生自View的,这说明它只一个控件,不是派生自ViewGroup的!所以放大器的TextView必然不能放在IndexSideBar中去画了。所以必须放在主布局中去布局放大器对应的TextView了。 
所以主布局此时代码应当为:(main.xml)

[java] view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.                 android:orientation="vertical"  
  4.                 android:layout_width="fill_parent"  
  5.                 android:layout_height="fill_parent"  
  6.                 android:background="#ffffff">  
  7.   
  8.     <com.example.BlogSideBar.IndexSideBar  
  9.             android:id="@+id/index_slide_bar"  
  10.             android:layout_width="23dp"  
  11.             android:layout_height="match_parent"  
  12.             android:layout_alignParentRight="true"  
  13.             android:layout_marginRight="10dp"  
  14.             android:layout_marginTop="10dp"  
  15.             android:layout_marginBottom="10dp"  
  16.             android:background="@drawable/index_letter_bg"/>  
  17.   
  18.   
  19.     <TextView  
  20.             android:id="@+id/index_slide_dialog"  
  21.             android:layout_width="wrap_content"  
  22.             android:layout_height="wrap_content"  
  23.             android:layout_toLeftOf="@+id/index_slide_bar"  
  24.             android:background="@drawable/list_lable_screen"  
  25.             android:gravity="center"  
  26.             android:textColor="#ff5e00"  
  27.             android:textSize="30dp"  
  28.             android:visibility="invisible"  
  29.             android:layout_marginRight="6dp"/>  
  30. </RelativeLayout>  
在这里没什么是注意的,就是给放大器放了一个背景:

其它没什么讲的了。下面让我们看看代码 
IndexSideBar 
我们先看看完整添加的代码:

[java] view plain copy
  1. /** 
  2.  * 设置放大器 
  3.  * @param tv textview 
  4.  */  
  5. public void setIndicatorTv(TextView tv){  
  6.     mIndicatorTv = tv;  
  7. }  
  8.   
  9. public boolean onTouchEvent(MotionEvent event) {  
  10.   
  11.     final float y = event.getY();// 点击y坐标  
  12.     int pos = (int)(y / getHeight() * (indexContent.size()+1));  
  13.     mChoose = pos -1 ;  
  14.   
  15.     String text = indexContent.get(mChoose);  
  16.     if (null != mIndicatorTv) {  
  17.         mIndicatorTv.setVisibility(VISIBLE);  
  18.         mIndicatorTv.setText(text);  
  19.     }  
  20.     switch (event.getAction()) {  
  21.         case MotionEvent.ACTION_CANCEL:  
  22.         case MotionEvent.ACTION_UP:{  
  23.             mChoose = -1;  
  24.             if (null != mIndicatorTv) {  
  25.                 mIndicatorTv.setVisibility(GONE);  
  26.             }  
  27.         }  
  28.         break;  
  29.         default:  
  30.         break;  
  31.     }  
  32.     return true;  
  33. }  
这里的代码总共分为三部分:设置放大器对应TextView,设置选中字符,手指离开时隐藏放大器
我们知道,放大器的TextView必须是从外部传过来的,所以我们设置一个函数来接收放大器对应的TextView:
[java] view plain copy
  1. public void setIndicatorTv(TextView tv){  
  2.     mIndicatorTv = tv;  
  3. }  
然后,当手指选中一个字符时,我们需要将其设置给放大器:
[java] view plain copy
  1. String text = indexContent.get(mChoose);  
  2. if (null != mIndicatorTv) {  
  3.     mIndicatorTv.setVisibility(VISIBLE);  
  4.     mIndicatorTv.setText(text);  
  5. }  
最后,当手指离开时,需要将放大器隐藏:
[java] view plain copy
  1. switch (event.getAction()) {  
  2.     case MotionEvent.ACTION_CANCEL:  
  3.     case MotionEvent.ACTION_UP:{  
  4.         mChoose = -1;  
  5.         if (null != mIndicatorTv) {  
  6.             mIndicatorTv.setVisibility(GONE);  
  7.         }  
  8.     }  
  9.     break;  
  10.     default:  
  11.     break;  
  12. }  
最后是在MyActivity中设置放大器对应的TextView:
[java] view plain copy
  1. public void onCreate(Bundle savedInstanceState) {  
  2.     super.onCreate(savedInstanceState);  
  3.     setContentView(R.layout.main);  
  4.   
  5.     mIndexSideBar = (IndexSideBar)findViewById(R.id.index_slide_bar);  
  6.     mIndexBlockDialog = (TextView)findViewById(R.id.index_slide_dialog);  
  7.     mIndexSideBar.setIndicatorTv(mIndexBlockDialog);  
  8. }  
到这里放大器的设置与使用就讲完了,下面就讲讲如何将选中的字符外露给使用者吧。

3、选中字符外露

由于我们写的是一个控件,所以我们必须将它封装的好一些,我们需要把用户在这个使用这个控件时所关心的结果暴露给使用者。 
外露的办法一般而言是使用接口来实现的,所以我们在IndexSideBar里定义一个静态接口:
[java] view plain copy
  1. public static interface ChooseListner{  
  2.     void onChoosed(int pos,String text);  
  3. }  
  4.   
  5. /** 
  6.  * 设置监听器 
  7.  * @param listener 
  8.  */  
  9. public void setChoosedListener(ChooseListner listener){  
  10.     mListener = listener;  
  11. }  
在这里,我们定义了一个接口,并且定义了一个函数setChoosedListener,让使用者传进来监听器的实例。
然后是在onTouchEvent中当用户点击某个字符时,就需要使用mListener.onChoosed(mChoose,text);将选中的字符传出去了。
所以此时完整的onTouchEvent的代码如下:
[java] view plain copy
  1. public boolean onTouchEvent(MotionEvent event) {  
  2.   
  3.     final float y = event.getY();// 点击y坐标  
  4.     int pos = (int)(y / getHeight() * (indexContent.size()+1));  
  5.     mChoose = pos -1 ;  
  6.   
  7.     String text = indexContent.get(mChoose);  
  8.     if (null != mIndicatorTv) {  
  9.         mIndicatorTv.setVisibility(VISIBLE);  
  10.         mIndicatorTv.setText(text);  
  11.     }  
  12.     if (null != mListener){  
  13.         mListener.onChoosed(mChoose,text);  
  14.     }  
  15.   
  16.     switch (event.getAction()) {  
  17.         case MotionEvent.ACTION_CANCEL:  
  18.         case MotionEvent.ACTION_UP:{  
  19.             mChoose = -1;  
  20.             if (null != mIndicatorTv) {  
  21.                 mIndicatorTv.setVisibility(GONE);  
  22.             }  
  23.         }  
  24.         break;  
  25.         default:  
  26.         break;  
  27.     }  
  28.   
  29.     invalidate();  
  30.     //一定要返回true,因为,只有拦截了down事件以后,其它的事件才会再次传到这个控件来,  
  31.     // 不然就再也不会传到这个控件里来  
  32.     return true;  
  33. }  
最后是在MainActivity中使用监听器了:
[java] view plain copy
  1. public void onCreate(Bundle savedInstanceState) {  
  2.     super.onCreate(savedInstanceState);  
  3.     setContentView(R.layout.main);  
  4.   
  5.     mIndexSideBar = (IndexSideBar)findViewById(R.id.index_slide_bar);  
  6.     mIndexBlockDialog = (TextView)findViewById(R.id.index_slide_dialog);  
  7.     mIndexSideBar.setIndicatorTv(mIndexBlockDialog);  
  8.     mIndexSideBar.setChoosedListener(new IndexSideBar.ChooseListner() {  
  9.         @Override  
  10.         public void onChoosed(int pos,String text) {  
  11.             Log.e("qijian","pos:"+pos+"  choosed:"+ text);  
  12.         }  
  13.     });  
  14. }  
结果如下:

好了,这篇文章就结束了,下面给大家讲讲实现快速索引的雏形,在这篇文章的底部给大家稍微讲一下有关九宫格拉伸区域与显示区域的知识。 

源码在文章底部给出

三、扩展:九宫格拉伸区域与显示区域

很久以前有写过一篇文章《九宫格》,在看下面内容之前,大家需要先看一下这篇文章,最起码应该知道九宫格是什么,图片上四周的黑点是怎么加上去的。有关四周四点的作用,我们下面会细讲。 
我们就以IndexSideBar的背景图为例:


标记A和B的两个点色线条(这里是两个点),是填充线条,标记A表示当横向拉伸时,用来填充拉伸位置的颜色源。同理,标记B表示当纵向拉伸时,拉伸处的颜色也是从标记B中取得的。
标记C和标记D就有点难理解了,他们表示的是内容的显示位置。
如标记C:


红色框标识的位置就是内容在纵向位置的显示区域。
显示区域的意思就是指,显示内容的区域!如果这是一个TextView控件的背景,那么TextView中的文字只能在C标识的区域显示,C区域以上和以下的区域都不能显示文字。
当拉伸时,显示区域是怎样的呢?如下图:


这张图显示的是纵向拉伸后的结果,在纵向拉伸后,我们用1,2,3标识了三个区域,其中区域1是未拉伸时,红色框上部的部分,区域3标识在未拉伸时红色框下部的部分;区域2表示拉伸后,.9图中C黑色块所对应的显示区域。
对于横向拉伸而言:


只有中间黑色块的拉伸区域才能用来显示内容,而区域A和区域B是不能显示内容的。同样,如果这是一个TextView控件的背景,那么文字就是在黑色框区域显示的。
那么问题来了,对于同时横向拉伸和纵向拉伸的图像,那显示区域是哪些呢?
当然是在横向显示区域与纵向显示区域的交集。
**但是注意一点:点九图的显示区域对于系统控件才有用,对于自定义控件无效!!!!!因为,我们利用绘图函数在给图时,整个区域都是可以画的,我们想画哪里画哪里,没有任何限制!**


如果本文有帮到你,记得加关注哦

源码下载地址:http://download.csdn.net/detail/harvic880925/9390218

请大家尊重原创者版权,转载请标明出处:http://blog.csdn.net/harvic880925/article/details/50458830 谢谢

0 0