Android自定义控件---联系人列表A-Z排序

这几天在做IM模块,设计图要求做一个类似下图所示的自定义控件。

我百度了一下,发现类似的Ddmo有很多, 但是还不能完全满足设计图的需求。
参考了几个比较有价值的demo琢磨了一天总算做出来了,现在发出来和大家分享。

分析一下这个需求的难点。
1、右边侧滑栏(SideBar)控件绘制。
2、将列表中的中文昵称转化为拼音列表。(这个问题用jpinyin解决。)
3、滑动侧滑栏 (SideBar) 的过程中如何与ListView列表建立对应的联系?
关于jpinyin的相关介绍https://github.com/stuxuhai/jpinyin
如果能够解决这三个问题那么这个需求也就迎刃而解了。


接下来看看整个Demo的结构图


一、SideBar控件的绘制
1、首先创建一个名为SideBar的类并且继承View。在这个类中分别重写onMeasure,onDraw,onTouchEvent三个方法。
(1)onMeasure顾名思义这是一个用来测量SideBar视图的大小的方法。
MeasureSpec.getMode是用来获取测量模式类型。测量模式类型有三种UNSPECIFIED,EXACTLY,AT_MOST。
其中 UNSPECIFIED很少会用到,我们忽略。我们主要理解 EXACTLY和 AT_MOST
当我们在XML文件里面设置SideBar类的layout_width属性的时候,
如果设置为match_parent或者一个精确值(例如30dp)那么 MeasureSpec.getMode得到的测量模式类型就是EXACTLY。
如果设置为warp_content那么MeasureSpec.getMode得到的测量模式类型就是AT_MOST。 (layout_height属性同理)
根据这些获取的类型值,就会进入下面的if条件中计算该视图的大小。在该方法的最后会调用setMeasureDimension方法设置最终的宽度和高度。

  1. @Override  
  2.  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  3.     int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  4.     int heightMode = MeasureSpec. getMode(heightMeasureSpec) ;  
  5.   
  6.     int finalWidth = 0, finalHeight = 0;  
  7.     // 测量单个字符的宽度和高度  
  8.     float charWidthAndHeight = paint.measureText( letter.get(0 )) + letterSpace;  
  9.   
  10.     if (widthMode == MeasureSpec. EXACTLY) {  
  11.         finalWidth = MeasureSpec.getSize (widthMeasureSpec);  
  12.     } else if (widthMode == MeasureSpec.AT_MOST) {  
  13.         finalWidth = (int ) charWidthAndHeight + getPaddingLeft() + getPaddingRight();  
  14.     }  
  15.   
  16.   
  17.     if (heightMode == MeasureSpec.EXACTLY) {  
  18.         finalHeight = MeasureSpec.getSize (heightMeasureSpec);  
  19.     } else if (heightMode == MeasureSpec.AT_MOST) {  
  20.   
  21.         // 注意measureText的值与 paint.setTextSize的值有关  
  22.         finalHeight = ( int) charWidthAndHeight * letter .size() + getPaddingBottom() + getPaddingTop();  
  23.     }  
  24.   
  25.   
  26.     setMeasuredDimension(finalWidth, finalHeight);  
  27. }  
@Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec. getMode(heightMeasureSpec) ;

    int finalWidth = 0, finalHeight = 0;
    // 测量单个字符的宽度和高度
    float charWidthAndHeight = paint.measureText( letter.get(0 )) + letterSpace;

    if (widthMode == MeasureSpec. EXACTLY) {
        finalWidth = MeasureSpec.getSize (widthMeasureSpec);
    } else if (widthMode == MeasureSpec.AT_MOST) {
        finalWidth = (int ) charWidthAndHeight + getPaddingLeft() + getPaddingRight();
    }


    if (heightMode == MeasureSpec.EXACTLY) {
        finalHeight = MeasureSpec.getSize (heightMeasureSpec);
    } else if (heightMode == MeasureSpec.AT_MOST) {

        // 注意measureText的值与 paint.setTextSize的值有关
        finalHeight = ( int) charWidthAndHeight * letter .size() + getPaddingBottom() + getPaddingTop();
    }


    setMeasuredDimension(finalWidth, finalHeight);
}

(2)在测量好这个视图的大小之后,接下来就是在onDraw方法中绘制自己想要的视图。

  1.  @Override  
  2.    protected void onDraw(Canvas canvas) {  
  3.        //27 个字符(包含 #)均分整个视图的高度,例如视图高度为 270,270/27 均分之后,每个字符的y坐标为10。  
  4.        y = getHeight() / letter.size() ;  
  5.   
  6.        for ( int i = 0 ; i < letter .size(); i++) {  
  7.            Paint.FontMetricsInt fontMetricsInt = paint.getFontMetricsInt();  
  8.   
  9.            // 计算绘制字符 X的坐标,整个视图宽度的一半减去字符宽度的一半  
  10.            x = getWidth() / 2 - ( int) paint.measureText(letter .get(i)) / 2;  
  11. //            int correctY=y*i+y;  
  12. /            int correctY = y * i + y / 2;  
  13.            int correctY = ((y * i + y) + (y * i) - fontMetricsInt.bottom - fontMetricsInt.top) / 2;  
  14.            String tempString = isLetterUpper ? letter.get(i).toUpperCase() : letter .get(i).toLowerCase();  
  15.            canvas.drawText(tempString , x, correctY, paint );  
  16. /            canvas.drawLine(0, y * i + y, 100, y * i + y, paint);  
  17.        }  
  18.    }  
  @Override
    protected void onDraw(Canvas canvas) {
        //27 个字符(包含 #)均分整个视图的高度,例如视图高度为 270,270/27 均分之后,每个字符的y坐标为10。
        y = getHeight() / letter.size() ;

        for ( int i = 0 ; i < letter .size(); i++) {
            Paint.FontMetricsInt fontMetricsInt = paint.getFontMetricsInt();

            // 计算绘制字符 X的坐标,整个视图宽度的一半减去字符宽度的一半
            x = getWidth() / 2 - ( int) paint.measureText(letter .get(i)) / 2;
 //            int correctY=y*i+y;
//            int correctY = y * i + y / 2;
            int correctY = ((y * i + y) + (y * i) - fontMetricsInt.bottom - fontMetricsInt.top) / 2;
            String tempString = isLetterUpper ? letter.get(i).toUpperCase() : letter .get(i).toLowerCase();
            canvas.drawText(tempString , x, correctY, paint );
//            canvas.drawLine(0, y * i + y, 100, y * i + y, paint);
        }
    }

关于绘制字符X坐标的求法比较简单,注释已经解释得很清楚就不再分析。
主要分析y坐标的求法。我百度了一些demo总结发现有两种关于字符y坐标的求法。
(为了便于观察,我暂时将SideBar的背景改为了浅蓝色,并且每个字符的都用横线分隔)

第一种是int correctY=y*i+y。运行一下程序发现字符的绘制明显向下偏移(如下图所示)


第二种是int correctY=y*i+y/2。再运行程序,发现还是有点偏移。我是处女座有强迫症,果断不能忍啊。



于是在网上搜索了不少资料,研究了一番。发现这位前辈的资料解决了我的问题http://blog.csdn.net/hursing/article/details/18703599
我把计算公式改为int correctY = ((y * i + y) + (y * i) - fontMetricsInt.bottom - fontMetricsInt.top) / 2; 运行程序,完美解决问题。


刚看到这个式子可能不是很理解。我给大家解释一下,假设我们要绘制D这个字符。如图所示

这时包含D字符的这个矩形上下边的纵坐标分别是y*i,y*i+y。那么这个矩形的中间纵坐标怎么算呢?答案就是((y*i)+(y*i+y))/2
得到这个中间值之后,我们直接调用绘制方法canvas.drawText(tempString, x, ((y*i)+(y*i+y))/2 , paint);发现还是出问题了
字符还是画在矩形偏上位置(如下图)

为什么会这样?这是因为canvas.drawText方法在绘制文本的时候并不是从文本的中间(下图的黄线)开始
,而且是从文本的中间偏下位置,即图中绿色的baseLine这条线开始绘制。

明白了这些知识点之后就好处理了。(FontMetricInt.top+FontMetricInt.bottom)/2就是这边黄线的纵坐标(注意 FontMetricInt.top 是负值)。
综合起来:矩形纵坐标的值减去黄线纵坐标的值除以2整理得到
((y * i + y) + (y * i) - fontMetricsInt.bottom - fontMetricsInt.top) / 2
再调用Canvas.drawText方法绘制字符。


(3)整个视图都 测量, 绘制好之后。我们还需要添加控件的触摸事件。所以还需要重写onTouchEvent方法。
首先设置回调接口,向用户提供两个方法,分别是showCurrentLetter和hideCurrentLetter。
  1. /** 
  2. * 回调接口。 
  3.  */  
  4. public interface OnCurrentLetterListener {  
  5.     void showCurrentLetter(String currentLetter);  
  6.   
  7.     void hideCurrentLetter ();  
  8. }  
/**
* 回调接口。
 */
public interface OnCurrentLetterListener {
    void showCurrentLetter(String currentLetter);

    void hideCurrentLetter ();
}

重写onTouchEvent方法,首先获取用户在点击,移动SideBar视图时的y坐标。
y/getHeight()*letter.size()表示y坐标占整个视图高度的比例(例如50%)
乘以字符数组(长度为27)的长度等于下标(约等于13,)
switch(event.getAction())是用来判断用户触摸手机屏幕的手势。
当用户在手机屏幕按 压,移动时 MotionEvent.ACTION_DOWN(按压手势),
MotionEvent.ACTION_MOVE(移动手势) 回调上面回调接口的showCurrentLetter方法。
当用户在手机屏幕抬起时MotionEvent.ACTION_UP(抬起手势)回调接口中的hideCurrentLetter方法。
  1. @Override  
  2.  public boolean onTouchEvent(MotionEvent event) {  
  3.     float y = event.getY();  
  4.   
  5.     // 获取当前侧滑栏字母的下标  
  6.     float currentLetterIndex = y / getHeight() * letter.size();  
  7.   
  8.     switch (event.getAction()) {  
  9.         case MotionEvent.ACTION_DOWN:  
  10.         case MotionEvent.ACTION_MOVE:  
  11.   
  12.             if (onCurrentLetterListener != null) {  
  13.                 //对上下边界对限制  
  14.                 if (currentLetterIndex >= letter.size()) {  
  15.                     currentLetterIndex = letter.size() - 1 ;  
  16.                 } else if (currentLetterIndex < 0) {  
  17.                     currentLetterIndex = 0;  
  18.                 }  
  19.                 onCurrentLetterListener .showCurrentLetter(letter.get(( int) currentLetterIndex));  
  20.   
  21.             }  
  22.             return true;  
  23.   
  24.         case MotionEvent. ACTION_UP:  
  25.             if (onCurrentLetterListener != null) {  
  26.                 onCurrentLetterListener.hideCurrentLetter() ;  
  27.             }  
  28.             return true;  
  29.   
  30.         default:  
  31.             return true;  
  32.     }  
  33. }  
@Override
 public boolean onTouchEvent(MotionEvent event) {
    float y = event.getY();

    // 获取当前侧滑栏字母的下标
    float currentLetterIndex = y / getHeight() * letter.size();

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_MOVE:

            if (onCurrentLetterListener != null) {
                //对上下边界对限制
                if (currentLetterIndex >= letter.size()) {
                    currentLetterIndex = letter.size() - 1 ;
                } else if (currentLetterIndex < 0) {
                    currentLetterIndex = 0;
                }
                onCurrentLetterListener .showCurrentLetter(letter.get(( int) currentLetterIndex));

            }
            return true;

        case MotionEvent. ACTION_UP:
            if (onCurrentLetterListener != null) {
                onCurrentLetterListener.hideCurrentLetter() ;
            }
            return true;

        default:
            return true;
    }
}
最后整个方法return true;表示这个触摸事件被SideBar视图消费了,不再向其它地方传递事件。


2、最后贴个SideBar类的代码
  1. package per.edward.ui;  
  2.   
  3. import android.content.Context ;  
  4. import android.content.res.TypedArray ;  
  5. import android.graphics.Canvas ;  
  6. import android.graphics.Color ;  
  7. import android.graphics.Paint ;  
  8. import android.util.AttributeSet ;  
  9. import android.view.MotionEvent ;  
  10. import android.view.View ;  
  11.   
  12. import java.util.ArrayList ;  
  13. import java.util.Arrays ;  
  14. import java.util.List ;  
  15.   
  16.   
  17. /** 
  18. * 侧滑栏视图 
  19.  * Created by Edward on 2016/4/27. 
  20. */  
  21. public class SideBar extends View {  
  22.     private String[] letterStrings = { "#""A" , "B""C""D" , "E""F" , "G""H""I" , "J""K""L" , "M""N""O" , "P""Q""R""S" , "T""U""V" , "W""S""Y" , "Z"} ;  
  23.     private Paint paint;  
  24.     // 字母列表  
  25.     private List<String> letter;  
  26.     // 绘制字母的 x,y坐标  
  27.     private int x, y ;  
  28.     // 字母的间距  
  29.     private int letterSpace = 0 ;  
  30.     // 字母是否大写  
  31.     private boolean isLetterUpper = true;  
  32.   
  33.     private OnCurrentLetterListener onCurrentLetterListener;  
  34.   
  35.     public void setLetter (List<String> letter) {  
  36.         this .letter = letter ;  
  37.     }  
  38.   
  39.     // 设置回调接口  
  40.     public void setOnCurrentLetterListener(OnCurrentLetterListener onCurrentLetterListener) {  
  41.         this .onCurrentLetterListener = onCurrentLetterListener ;  
  42.     }  
  43.   
  44.     public SideBar(Context context) {  
  45.         this (context, null) ;  
  46.     }  
  47.   
  48.     public SideBar(Context context, AttributeSet attrs) {  
  49.         super (context, attrs) ;  
  50.   
  51.         if ( letter == null ) {  
  52.             letter = new ArrayList<>() ;  
  53.             letter = Arrays.asList(letterStrings );  
  54.         }  
  55.   
  56.         TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SideBar) ;  
  57.         letterSpace = typedArray.getInt(R.styleable.SideBar_SB_Letter_Space, 0);  
  58.         isLetterUpper = typedArray.getBoolean(R.styleable.SideBar_SB_Is_Letter_Upper, true);  
  59.         typedArray.recycle() ;  
  60.   
  61.         paint = new Paint() ;  
  62.         paint .setColor(Color.BLACK) ;  
  63.         paint .setAntiAlias(true) ;  
  64.         paint .setTextSize(30) ;//3CAC48  
  65.         paint .setColor(Color.parseColor("#ffffff" ));  
  66.     }  
  67.   
  68.     @Override  
  69.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  70.         int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  71.         int heightMode = MeasureSpec. getMode(heightMeasureSpec) ;  
  72.   
  73.         int finalWidth = 0, finalHeight = 0;  
  74.         // 测量单个字符的宽度和高度  
  75.         float charWidthAndHeight = paint.measureText( letter.get(0 )) + letterSpace;  
  76.   
  77.         if (widthMode == MeasureSpec. EXACTLY) {  
  78.             finalWidth = MeasureSpec.getSize(widthMeasureSpec);  
  79.         } else if (widthMode == MeasureSpec.AT_MOST) {  
  80.             finalWidth = (int) charWidthAndHeight + getPaddingLeft() + getPaddingRight();  
  81.         }  
  82.   
  83.   
  84.         if (heightMode == MeasureSpec.EXACTLY) {  
  85.             finalHeight = MeasureSpec.getSize(heightMeasureSpec) ;  
  86.         } else if (heightMode == MeasureSpec.AT_MOST) {  
  87.   
  88.             // 注意measureText的值与 paint.setTextSize的值有关  
  89.             finalHeight = ( int) charWidthAndHeight * letter .size() + getPaddingBottom() + getPaddingTop();  
  90.         }  
  91.   
  92.  //        Log.e("--------------->", MeasureSpec.getSize(widthMeasureSpec) + "    " + MeasureSpec.getSize(heightMeasureSpec));  
  93.         setMeasuredDimension(finalWidth , finalHeight);  
  94.     }  
  95.   
  96.     @Override  
  97.     protected void onDraw(Canvas canvas) {  
  98.         setBackgroundColor(Color.parseColor ("#31b2f7" ));  
  99.         //27 个字符(包含 #)均分整个视图的高度,例如视图高度为 270,270/27 均分之后,每个字符的 y坐标为10  
  100.         y = getHeight() / letter.size() ;  
  101.   
  102.         Paint.FontMetricsInt fontMetricsInt = paint.getFontMetricsInt();  
  103.   
  104.         for ( int i = 0 ; i < letter .size(); i++) {  
  105.             // 计算绘制字符 X的坐标,整个视图宽度的一半减去字符宽度的一半  
  106.             x = getWidth() / 2 - ( int) paint.measureText(letter .get(i)) / 2;  
  107.  //            int correctY=y*i+y;  
  108. //            int correctY = y * i + y / 2;  
  109.             int correctY = ((y * i + y) + (y * i) - fontMetricsInt.bottom - fontMetricsInt.top) / 2;  
  110.             String tempString = isLetterUpper ? letter.get(i).toUpperCase() : letter .get(i).toLowerCase();  
  111.             canvas.drawText(tempString , x, ((y * i) + ( y * i + y)) / 2 , paint) ;  
  112.             canvas.drawLine( 0, y * i + y, 100, y * i + y, paint);  
  113.         }  
  114.     }  
  115.   
  116.     @Override  
  117.     public boolean onTouchEvent(MotionEvent event) {  
  118.         float y = event.getY();  
  119.   
  120.         // 获取当前侧滑栏字母的下标  
  121.         float currentLetterIndex = y / getHeight() * letter.size();  
  122.   
  123.         switch (event.getAction()) {  
  124.             case MotionEvent.ACTION_DOWN:  
  125.             case MotionEvent.ACTION_MOVE:  
  126.   
  127.                 if ( onCurrentLetterListener != null ) {  
  128.                     //对上下边界对限制  
  129.                     if (currentLetterIndex >= letter.size()) {  
  130.                         currentLetterIndex = letter.size() - 1 ;  
  131.                     } else if (currentLetterIndex < 0) {  
  132.                         currentLetterIndex = 0;  
  133.                     }  
  134.                     onCurrentLetterListener .showCurrentLetter(letter.get(( int) currentLetterIndex));  
  135.   
  136.                 }  
  137.                 return true;  
  138.   
  139.             case MotionEvent. ACTION_UP:  
  140.                 if ( onCurrentLetterListener != null ) {  
  141.                     onCurrentLetterListener.hideCurrentLetter() ;  
  142.                 }  
  143.                 return true;  
  144.   
  145.             default:  
  146.                 return true;  
  147.         }  
  148.     }  
  149.   
  150.     /** 
  151.      * 回调接口。 
  152.      */  
  153.     public interface OnCurrentLetterListener {  
  154.         void showCurrentLetter(String currentLetter);  
  155.   
  156.         void hideCurrentLetter ();  
  157.     }  
  158.   
  159.   
  160. }  
package per.edward.ui;

import android.content.Context ;
import android.content.res.TypedArray ;
import android.graphics.Canvas ;
import android.graphics.Color ;
import android.graphics.Paint ;
import android.util.AttributeSet ;
import android.view.MotionEvent ;
import android.view.View ;

import java.util.ArrayList ;
import java.util.Arrays ;
import java.util.List ;


/**
* 侧滑栏视图
 * Created by Edward on 2016/4/27.
*/
public class SideBar extends View {
    private String[] letterStrings = { "#", "A" , "B", "C", "D" , "E", "F" , "G", "H", "I" , "J", "K", "L" , "M", "N", "O" , "P", "Q", "R", "S" , "T", "U", "V" , "W", "S", "Y" , "Z"} ;
    private Paint paint;
    // 字母列表
    private List<String> letter;
    // 绘制字母的 x,y坐标
    private int x, y ;
    // 字母的间距
    private int letterSpace = 0 ;
    // 字母是否大写
    private boolean isLetterUpper = true;

    private OnCurrentLetterListener onCurrentLetterListener;

    public void setLetter (List<String> letter) {
        this .letter = letter ;
    }

    // 设置回调接口
    public void setOnCurrentLetterListener(OnCurrentLetterListener onCurrentLetterListener) {
        this .onCurrentLetterListener = onCurrentLetterListener ;
    }

    public SideBar(Context context) {
        this (context, null) ;
    }

    public SideBar(Context context, AttributeSet attrs) {
        super (context, attrs) ;

        if ( letter == null ) {
            letter = new ArrayList<>() ;
            letter = Arrays.asList(letterStrings );
        }

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SideBar) ;
        letterSpace = typedArray.getInt(R.styleable.SideBar_SB_Letter_Space, 0);
        isLetterUpper = typedArray.getBoolean(R.styleable.SideBar_SB_Is_Letter_Upper, true);
        typedArray.recycle() ;

        paint = new Paint() ;
        paint .setColor(Color.BLACK) ;
        paint .setAntiAlias(true) ;
        paint .setTextSize(30) ;//3CAC48
        paint .setColor(Color.parseColor("#ffffff" ));
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec. getMode(heightMeasureSpec) ;

        int finalWidth = 0, finalHeight = 0;
        // 测量单个字符的宽度和高度
        float charWidthAndHeight = paint.measureText( letter.get(0 )) + letterSpace;

        if (widthMode == MeasureSpec. EXACTLY) {
            finalWidth = MeasureSpec.getSize(widthMeasureSpec);
        } else if (widthMode == MeasureSpec.AT_MOST) {
            finalWidth = (int) charWidthAndHeight + getPaddingLeft() + getPaddingRight();
        }


        if (heightMode == MeasureSpec.EXACTLY) {
            finalHeight = MeasureSpec.getSize(heightMeasureSpec) ;
        } else if (heightMode == MeasureSpec.AT_MOST) {

            // 注意measureText的值与 paint.setTextSize的值有关
            finalHeight = ( int) charWidthAndHeight * letter .size() + getPaddingBottom() + getPaddingTop();
        }

 //        Log.e("--------------->", MeasureSpec.getSize(widthMeasureSpec) + "    " + MeasureSpec.getSize(heightMeasureSpec));
        setMeasuredDimension(finalWidth , finalHeight);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        setBackgroundColor(Color.parseColor ("#31b2f7" ));
        //27 个字符(包含 #)均分整个视图的高度,例如视图高度为 270,270/27 均分之后,每个字符的 y坐标为10
        y = getHeight() / letter.size() ;

        Paint.FontMetricsInt fontMetricsInt = paint.getFontMetricsInt();

        for ( int i = 0 ; i < letter .size(); i++) {
            // 计算绘制字符 X的坐标,整个视图宽度的一半减去字符宽度的一半
            x = getWidth() / 2 - ( int) paint.measureText(letter .get(i)) / 2;
 //            int correctY=y*i+y;
//            int correctY = y * i + y / 2;
            int correctY = ((y * i + y) + (y * i) - fontMetricsInt.bottom - fontMetricsInt.top) / 2;
            String tempString = isLetterUpper ? letter.get(i).toUpperCase() : letter .get(i).toLowerCase();
            canvas.drawText(tempString , x, ((y * i) + ( y * i + y)) / 2 , paint) ;
            canvas.drawLine( 0, y * i + y, 100, y * i + y, paint);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float y = event.getY();

        // 获取当前侧滑栏字母的下标
        float currentLetterIndex = y / getHeight() * letter.size();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:

                if ( onCurrentLetterListener != null ) {
                    //对上下边界对限制
                    if (currentLetterIndex >= letter.size()) {
                        currentLetterIndex = letter.size() - 1 ;
                    } else if (currentLetterIndex < 0) {
                        currentLetterIndex = 0;
                    }
                    onCurrentLetterListener .showCurrentLetter(letter.get(( int) currentLetterIndex));

                }
                return true;

            case MotionEvent. ACTION_UP:
                if ( onCurrentLetterListener != null ) {
                    onCurrentLetterListener.hideCurrentLetter() ;
                }
                return true;

            default:
                return true;
        }
    }

    /**
     * 回调接口。
     */
    public interface OnCurrentLetterListener {
        void showCurrentLetter(String currentLetter);

        void hideCurrentLetter ();
    }


}


3、SideBar类写完之后,我们就可以创建一个XML文件命名为activity_main,
在里面用上我们刚才写的自定义控件。整个XML代码布局很简单。
最外层是一个RelativeLayout容器。
中间放了一个TextView控件,默认为隐藏状态。
将SideBar右置垂直居中。
  1. <? xml version="1.0" encoding"utf-8"?>  
  2. <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"  
  3.     xmlns: app="http://schemas.android.com/apk/res-auto"  
  4.     android :layout_width="match_parent"  
  5.     android :layout_height="match_parent">  
  6.   
  7.     <TextView  
  8.         android :id="@+id/txt_show_current_letter"  
  9.         android :layout_width="100dp"  
  10.         android :layout_height="wrap_content"  
  11.         android :layout_centerInParent="true"  
  12.         android :background="#3CAC48"  
  13.         android :gravity="center_vertical|center_horizontal"  
  14.         android :text="A"  
  15.         android :textColor="#ffffff"  
  16.         android :textSize="50dp"  
  17.         android :visibility="gone" />  
  18.   
  19.     <per.edward.ui.SideBar  
  20.         android :id="@+id/side_bar"  
  21.         android :layout_width="30dp"  
  22.         android :layout_height="150dp"  
  23.         android :layout_alignParentRight="true"  
  24.         android :layout_centerVertical="true" />  
  25. </RelativeLayout>  
<? xml version="1.0" encoding= "utf-8"?>
<RelativeLayout xmlns:android= "http://schemas.android.com/apk/res/android"
    xmlns: app="http://schemas.android.com/apk/res-auto"
    android :layout_width="match_parent"
    android :layout_height="match_parent">

    <TextView
        android :id="@+id/txt_show_current_letter"
        android :layout_width="100dp"
        android :layout_height="wrap_content"
        android :layout_centerInParent="true"
        android :background="#3CAC48"
        android :gravity="center_vertical|center_horizontal"
        android :text="A"
        android :textColor="#ffffff"
        android :textSize="50dp"
        android :visibility="gone" />

    <per.edward.ui.SideBar
        android :id="@+id/side_bar"
        android :layout_width="30dp"
        android :layout_height="150dp"
        android :layout_alignParentRight="true"
        android :layout_centerVertical="true" />
</RelativeLayout>


4、最后再创建一个主体类,命名为MainActivity并且继承Activity。 首先实例化TextView和SideBar两个控件。
之后用SideBar的实例。设置setOnCurrentLetterListener的回调接口,在这个接口需要实现两个方法分别是showCurrentLetter,hideCurrentLetter。前者用来显示TextView,并且设置当前字母值。后者用来隐藏TextView。具体操作看下面代码。简单吧?
  1. package per.edward.ui;  
  2.   
  3. import android.app.Activity ;  
  4. import android.os.Bundle ;  
  5. import android.view.View ;  
  6. import android.widget.TextView ;  
  7.   
  8. /** 
  9. * author:Edward 
  10. */  
  11. public class MainActivity extends Activity {  
  12.     private TextView txtShowCurrentLetter;  
  13.     private SideBar sideBar;  
  14.   
  15.     @Override  
  16.     protected void onCreate(Bundle savedInstanceState) {  
  17.         super .onCreate(savedInstanceState) ;  
  18.         setContentView(R.layout. activity_main);  
  19.         txtShowCurrentLetter = (TextView) findViewById(R.id.txt_show_current_letter );  
  20.         sideBar = (SideBar) findViewById(R.id. side_bar);  
  21.   
  22.         setCallbackInterface() ;  
  23.     }  
  24.   
  25.     /** 
  26.      * 设置回调接口 
  27.      */  
  28.     public void setCallbackInterface() {  
  29.         // 回调接口  
  30.         sideBar .setOnCurrentLetterListener(new SideBar.OnCurrentLetterListener() {  
  31.             @Override  
  32.             public void showCurrentLetter(String currentLetter) {  
  33.                 txtShowCurrentLetter .setVisibility(View.VISIBLE) ;  
  34.                 txtShowCurrentLetter.setText(currentLetter) ;  
  35.             }  
  36.   
  37.             @Override  
  38.             public void hideCurrentLetter() {  
  39.                 txtShowCurrentLetter.setVisibility(View. GONE);  
  40.             }  
  41.         });  
  42.     }  
  43. }  
package per.edward.ui;

import android.app.Activity ;
import android.os.Bundle ;
import android.view.View ;
import android.widget.TextView ;

/**
* author:Edward
*/
public class MainActivity extends Activity {
    private TextView txtShowCurrentLetter;
    private SideBar sideBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super .onCreate(savedInstanceState) ;
        setContentView(R.layout. activity_main);
        txtShowCurrentLetter = (TextView) findViewById(R.id.txt_show_current_letter );
        sideBar = (SideBar) findViewById(R.id. side_bar);

        setCallbackInterface() ;
    }

    /**
     * 设置回调接口
     */
    public void setCallbackInterface() {
        // 回调接口
        sideBar .setOnCurrentLetterListener(new SideBar.OnCurrentLetterListener() {
            @Override
            public void showCurrentLetter(String currentLetter) {
                txtShowCurrentLetter .setVisibility(View.VISIBLE) ;
                txtShowCurrentLetter.setText(currentLetter) ;
            }

            @Override
            public void hideCurrentLetter() {
                txtShowCurrentLetter.setVisibility(View. GONE);
            }
        });
    }
}

5、最后实现的效果图如下。

分享到这里整个demo的流程已经走到一半了。
下面会讲讲ListView与SideBar控件的联动处理。


二、建立侧滑栏SideBar与ListView的对应关系
1、首先创建一个联系人的实体类,在这个实体类中,定义了两个变量firstLetter(用来存储联系人拼音的第一个字母)和name(联系人的名字)。
  1. package per.edward.ui;  
  2.   
  3. /** 
  4. * 联系人列表实体类 
  5.  * Created by Edward on 2016/4/26. 
  6. */  
  7. public class ContactsModel {  
  8.     private String firstLetter;  
  9.     private String name;  
  10.   
  11.     public String getFirstLetter() {  
  12.         return firstLetter;  
  13.     }  
  14.   
  15.     public void setFirstLetter(String firstLetter) {  
  16.         this .firstLetter = firstLetter ;  
  17.     }  
  18.   
  19.     public String getName() {  
  20.         return name;  
  21.     }  
  22.   
  23.     public void setName(String name) {  
  24.         this .name = name ;  
  25.     }  
  26.   
  27.   
  28. }  
package per.edward.ui;

/**
* 联系人列表实体类
 * Created by Edward on 2016/4/26.
*/
public class ContactsModel {
    private String firstLetter;
    private String name;

    public String getFirstLetter() {
        return firstLetter;
    }

    public void setFirstLetter(String firstLetter) {
        this .firstLetter = firstLetter ;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this .name = name ;
    }


}

2、创建一个名为adapter_side_bar的XML布局。
这个布局将在MainActivity类中通过实例化 myAdapter = new SideBarAdapter(this, list, R.layout.adapter_side_bar);
的方法传递给SideBarAdapter类。
  1. <? xml version="1.0" encoding"utf-8"?>  
  2. <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"  
  3.     android :layout_width="match_parent"  
  4.     android :layout_height="match_parent"  
  5.     android :orientation="vertical">  
  6.   
  7.     <TextView  
  8.         android :id="@+id/txt_letter_category"  
  9.         android :layout_width="match_parent"  
  10.         android :layout_height="30dp"  
  11.         android :background="#d7d6d6"  
  12.         android :padding="5dp"  
  13.         android :text="A"  
  14.         android :textColor="#000000"  
  15.         android :textSize="16dp" />  
  16.   
  17.     <LinearLayout  
  18.         android :layout_width="match_parent"  
  19.         android :layout_height="wrap_content"  
  20.         android :background="#ffffff"  
  21.         android :orientation="horizontal">  
  22.   
  23.         <ImageView  
  24.             android :id="@+id/image"  
  25.             android :layout_width="50dp"  
  26.             android :layout_height="50dp"  
  27.             android :padding="5dp"  
  28.             android :src="@mipmap/ic_launcher" />  
  29.   
  30.         <TextView  
  31.             android :id="@+id/txt_name"  
  32.             android :layout_width="wrap_content"  
  33.             android :layout_height="wrap_content"  
  34.             android :layout_gravity="center_vertical"  
  35.             android :text="姓名 "  
  36.             android :textSize="14dp" />  
  37.     </LinearLayout>  
  38.   
  39. </LinearLayout>  
<? xml version="1.0" encoding= "utf-8"?>
<LinearLayout xmlns:android= "http://schemas.android.com/apk/res/android"
    android :layout_width="match_parent"
    android :layout_height="match_parent"
    android :orientation="vertical">

    <TextView
        android :id="@+id/txt_letter_category"
        android :layout_width="match_parent"
        android :layout_height="30dp"
        android :background="#d7d6d6"
        android :padding="5dp"
        android :text="A"
        android :textColor="#000000"
        android :textSize="16dp" />

    <LinearLayout
        android :layout_width="match_parent"
        android :layout_height="wrap_content"
        android :background="#ffffff"
        android :orientation="horizontal">

        <ImageView
            android :id="@+id/image"
            android :layout_width="50dp"
            android :layout_height="50dp"
            android :padding="5dp"
            android :src="@mipmap/ic_launcher" />

        <TextView
            android :id="@+id/txt_name"
            android :layout_width="wrap_content"
            android :layout_height="wrap_content"
            android :layout_gravity="center_vertical"
            android :text="姓名 "
            android :textSize="14dp" />
    </LinearLayout>

</LinearLayout>

这个布局的预览图是这样滴。

SideBarAdapter接收到此布局之后,会通过convertView = LayoutInflater.from(context).inflate(mItemLayoutId, null);加载布局。
并且在getView方法中动态在控制字母标题的显示或隐藏。

3、接着创建一个名为 SideBarAdapter类的 适配器 并且继承BaseAdapter。
(1)构造方法中调用traverseList方法。这个方法的算法是这样的。此时mDatas列表所有的getFirstLetter  字段已经排序成
# # # ABCDEFLLLLMMMQTTZZ。#有三个,for循环。
第一次currentd的值是#把current的值作为键 并且记录它的下标,即map.put(current, i)。
第二次current的值是#不记录,第三次 current的值还是# 不记录。
到第四次此时current更新为A,把current作为键并且记录下标。
第五次此时current更新为B, 把current作为键并且记录下标....一直到for循环结束为止。

这时map的集合已经记录了整个mDatas列表的字母标题和下标。我们只需要在getView方法(这个方法会传进来一个position)中判断当前的position是否等于map的下标值。如果相等就将字母标题显示出来。否则隐藏
  1. package per.edward.ui;  
  2.   
  3. import android.content.Context ;  
  4. import android.view.LayoutInflater ;  
  5. import android.view.View ;  
  6. import android.view.ViewGroup ;  
  7. import android.widget.BaseAdapter ;  
  8. import android.widget.TextView ;  
  9.   
  10. import java.util.HashMap ;  
  11. import java.util.List ;  
  12. import java.util.Map ;  
  13.   
  14. /** 
  15. * 侧栏适配器 
  16.  * Created by Edward on 2016/4/27. 
  17. */  
  18. public class SideBarAdapter extends BaseAdapter {  
  19.     // 是否第一个 Item的  
  20.     private boolean isFirstItemLetter = true;  
  21.     // 记录是否显示字母标题,键为字母,值为下标  
  22.     private Map<String, Integer> map;  
  23.     private List<ContactsModel> mDatas;  
  24.     private int mItemLayoutId ;  
  25.     private Context context;  
  26.   
  27.     public SideBarAdapter(Context mContext , List<ContactsModel> mDatas, int mItemLayoutId) {  
  28.         this .mDatas = mDatas ;  
  29.         map = new HashMap<>() ;  
  30.         this. mItemLayoutId = mItemLayoutId;  
  31.         this. context = mContext;  
  32.   
  33.         traverseList() ;  
  34.     }  
  35.   
  36.     /** 
  37.      * 遍历列表 
  38.      * 由于传进来的 mDatas是一个已排好序的列表,遍历整个列表,每遇到分类的第一个字母就把下标记录下来 
  39.      */  
  40.     public void traverseList() {  
  41.         // 获取初始值  
  42.         String current = mDatas.get(0 ).getFirstLetter();  
  43.   
  44.         for ( int i = 0 ; i < mDatas .size(); i++) {  
  45.             char tempChar = mDatas.get(i).getFirstLetter().charAt(0) ;  
  46.             String tempFirstLetter = mDatas.get(i).getFirstLetter();  
  47.   
  48.             if (tempFirstLetter.equals(current) || (tempChar < 'A' || tempChar > 'Z' )) {  
  49.                 if ( isFirstItemLetter) {  
  50.                     map.put(current , i);  
  51.                 }  
  52.             } else {  
  53.                 //更新初始值  
  54.                 current = mDatas .get(i).getFirstLetter();  
  55.                 map.put(current , i);  
  56.             }  
  57.             isFirstItemLetter = false;  
  58.         }  
  59.     }  
  60.   
  61.   
  62.     /** 
  63.      * 获取当前字母的下标 
  64.      * 
  65.      * @return 
  66.      */  
  67.     public int getCurrentLetterPosition(String currentLetter) {  
  68.         if (map.get(currentLetter) != null) {  
  69.             return map.get(currentLetter);  
  70.         } else  
  71.             return - 1;  
  72.     }  
  73.   
  74.     @Override  
  75.     public int getCount() {  
  76.         return mDatas.size();  
  77.     }  
  78.   
  79.     @Override  
  80.     public Object getItem( int position) {  
  81.         return mDatas.get(position);  
  82.     }  
  83.   
  84.     @Override  
  85.     public long getItemId(int position) {  
  86.         return position;  
  87.     }  
  88.   
  89.     @Override  
  90.     public View getView( int position, View convertView, ViewGroup parent) {  
  91.         ViewHolder viewHolder;  
  92.         if (convertView == null) {  
  93.             viewHolder = new ViewHolder() ;  
  94.             convertView = LayoutInflater.from( context).inflate(mItemLayoutId , null);  
  95.             viewHolder. txtFirstLetter = (TextView) convertView.findViewById(R.id.txt_letter_category) ;  
  96.             viewHolder. txtName = (TextView) convertView.findViewById(R.id.txt_name) ;  
  97.   
  98.             convertView.setTag(viewHolder) ;  
  99.         } else {  
  100.             viewHolder = (ViewHolder) convertView.getTag();  
  101.         }  
  102.   
  103.   
  104.         // 判断是否显示字母标题  
  105.         if (map.get( mDatas.get(position).getFirstLetter()) != null && map.get(mDatas .get(position).getFirstLetter()).equals(position)) {  
  106.             viewHolder.txtFirstLetter .setVisibility(View.VISIBLE) ;  
  107.             viewHolder.txtFirstLetter.setText( mDatas.get(position).getFirstLetter());  
  108.         } else {  
  109.             viewHolder.txtFirstLetter .setVisibility(View.GONE) ;  
  110.         }  
  111.   
  112.         viewHolder.txtName .setText(mDatas.get(position).getName()) ;  
  113.   
  114.         return convertView ;  
  115.     }  
  116.   
  117.     public class ViewHolder {  
  118.         TextView txtFirstLetter , txtName;  
  119.     }  
  120.   
  121.   
  122. }  
package per.edward.ui;

import android.content.Context ;
import android.view.LayoutInflater ;
import android.view.View ;
import android.view.ViewGroup ;
import android.widget.BaseAdapter ;
import android.widget.TextView ;

import java.util.HashMap ;
import java.util.List ;
import java.util.Map ;

/**
* 侧栏适配器
 * Created by Edward on 2016/4/27.
*/
public class SideBarAdapter extends BaseAdapter {
    // 是否第一个 Item的
    private boolean isFirstItemLetter = true;
    // 记录是否显示字母标题,键为字母,值为下标
    private Map<String, Integer> map;
    private List<ContactsModel> mDatas;
    private int mItemLayoutId ;
    private Context context;

    public SideBarAdapter(Context mContext , List<ContactsModel> mDatas, int mItemLayoutId) {
        this .mDatas = mDatas ;
        map = new HashMap<>() ;
        this. mItemLayoutId = mItemLayoutId;
        this. context = mContext;

        traverseList() ;
    }

    /**
     * 遍历列表
     * 由于传进来的 mDatas是一个已排好序的列表,遍历整个列表,每遇到分类的第一个字母就把下标记录下来
     */
    public void traverseList() {
        // 获取初始值
        String current = mDatas.get(0 ).getFirstLetter();

        for ( int i = 0 ; i < mDatas .size(); i++) {
            char tempChar = mDatas.get(i).getFirstLetter().charAt(0) ;
            String tempFirstLetter = mDatas.get(i).getFirstLetter();

            if (tempFirstLetter.equals(current) || (tempChar < 'A' || tempChar > 'Z' )) {
                if ( isFirstItemLetter) {
                    map.put(current , i);
                }
            } else {
                //更新初始值
                current = mDatas .get(i).getFirstLetter();
                map.put(current , i);
            }
            isFirstItemLetter = false;
        }
    }


    /**
     * 获取当前字母的下标
     *
     * @return
     */
    public int getCurrentLetterPosition(String currentLetter) {
        if (map.get(currentLetter) != null) {
            return map.get(currentLetter);
        } else
            return - 1;
    }

    @Override
    public int getCount() {
        return mDatas.size();
    }

    @Override
    public Object getItem( int position) {
        return mDatas.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView( int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder;
        if (convertView == null) {
            viewHolder = new ViewHolder() ;
            convertView = LayoutInflater.from( context).inflate(mItemLayoutId , null);
            viewHolder. txtFirstLetter = (TextView) convertView.findViewById(R.id.txt_letter_category) ;
            viewHolder. txtName = (TextView) convertView.findViewById(R.id.txt_name) ;

            convertView.setTag(viewHolder) ;
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }


        // 判断是否显示字母标题
        if (map.get( mDatas.get(position).getFirstLetter()) != null && map.get(mDatas .get(position).getFirstLetter()).equals(position)) {
            viewHolder.txtFirstLetter .setVisibility(View.VISIBLE) ;
            viewHolder.txtFirstLetter.setText( mDatas.get(position).getFirstLetter());
        } else {
            viewHolder.txtFirstLetter .setVisibility(View.GONE) ;
        }

        viewHolder.txtName .setText(mDatas.get(position).getName()) ;

        return convertView ;
    }

    public class ViewHolder {
        TextView txtFirstLetter , txtName;
    }


}

4、记得在倒回刚才的activity_main的XML文件添加ListView控件
  1. <? xml version="1.0" encoding"utf-8"?>  
  2. <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"  
  3.     xmlns: app="http://schemas.android.com/apk/res-auto"  
  4.     android :layout_width="match_parent"  
  5.     android :layout_height="match_parent"  
  6.     android :background="#ffffff">  
  7.   
  8.   
  9.     <ListView  
  10.         android :id="@+id/list_view"  
  11.         android :layout_width="match_parent"  
  12.         android :layout_height="match_parent" />  
  13.   
  14.     <TextView  
  15.         android :id="@+id/txt_show_current_letter"  
  16.         android :layout_width="100dp"  
  17.         android :layout_height="wrap_content"  
  18.         android :layout_centerInParent="true"  
  19.         android :background="#3CAC48"  
  20.         android :gravity="center_vertical|center_horizontal"  
  21.         android :text="A"  
  22.         android :textColor="#ffffff"  
  23.         android :textSize="50dp"  
  24.         android :visibility="gone" />  
  25.   
  26.     <per.edward.ui.SideBar  
  27.         android :id="@+id/side_bar"  
  28.         android :layout_width="30dp"  
  29.         android :layout_height="match_parent"  
  30.         android :layout_alignParentRight="true"  
  31.         android :layout_centerVertical="true" />  
  32. </RelativeLayout>  
<? xml version="1.0" encoding= "utf-8"?>
<RelativeLayout xmlns:android= "http://schemas.android.com/apk/res/android"
    xmlns: app="http://schemas.android.com/apk/res-auto"
    android :layout_width="match_parent"
    android :layout_height="match_parent"
    android :background="#ffffff">


    <ListView
        android :id="@+id/list_view"
        android :layout_width="match_parent"
        android :layout_height="match_parent" />

    <TextView
        android :id="@+id/txt_show_current_letter"
        android :layout_width="100dp"
        android :layout_height="wrap_content"
        android :layout_centerInParent="true"
        android :background="#3CAC48"
        android :gravity="center_vertical|center_horizontal"
        android :text="A"
        android :textColor="#ffffff"
        android :textSize="50dp"
        android :visibility="gone" />

    <per.edward.ui.SideBar
        android :id="@+id/side_bar"
        android :layout_width="30dp"
        android :layout_height="match_parent"
        android :layout_alignParentRight="true"
        android :layout_centerVertical="true" />
</RelativeLayout>


5、写完了SideBarAdapter,我们再回到刚才写的MainActivity类进行相应的设置,包括初始化值联系人列表,
联系人 列表排序,联系人昵称转化为拼音等。具体代码如下所示,比较简单。就不一一分析了。
  1. package per.edward.ui;  
  2.   
  3. import android.os.Bundle ;  
  4. import android.support.v7.app.AppCompatActivity ;  
  5. import android.view.View ;  
  6. import android.widget.ListView ;  
  7. import android.widget.TextView ;  
  8.   
  9. import java.util.ArrayList ;  
  10. import java.util.Collections ;  
  11. import java.util.Comparator ;  
  12. import java.util.HashSet ;  
  13. import java.util.List ;  
  14. import java.util.Set ;  
  15.   
  16. import opensource.jpinyin.PinyinHelper ;  
  17.   
  18. /** 
  19. * author:Edward 
  20. * 此 demo的博客地址:http://blog.csdn.net/u012814441 
  21. */  
  22. public class MainActivity extends AppCompatActivity {  
  23.     private ListView listView;  
  24.     private TextView txtShowCurrentLetter;  
  25.     private SideBar sideBar;  
  26.     private SideBarAdapter myAdapter;  
  27. //    private List<ContactsModel> list;  
  28.   
  29.     @Override  
  30.     protected void onCreate(Bundle savedInstanceState) {  
  31.         super .onCreate(savedInstanceState) ;  
  32.         setContentView(R.layout. activity_main);  
  33.         txtShowCurrentLetter = (TextView) findViewById(R.id.txt_show_current_letter );  
  34.         sideBar = (SideBar) findViewById(R.id. side_bar);  
  35.         listView = (ListView) findViewById(R.id. list_view);  
  36.   
  37.         setCallbackInterface() ;  
  38.   
  39.         List<ContactsModel> list = initData() ;  
  40.   
  41.         chineseToPinyin(list) ;  
  42.   
  43.         // 将联系人列表的标题字母排序  
  44.         Collections. sort(list , new Comparator<ContactsModel>() {  
  45.             @Override  
  46.             public int compare(ContactsModel lhs, ContactsModel rhs) {  
  47.                 return lhs.getFirstLetter().compareTo(rhs.getFirstLetter()) ;  
  48.             }  
  49.         });  
  50.   
  51.   
  52.         // 将联系人列表的标题字母放到 List<String>列表中,准备数据去重  
  53.         List<String> getLetter = new ArrayList<>();  
  54.         for ( int i = 0 ; i < list.size(); i++) {  
  55.             getLetter.add(list.get(i).getFirstLetter());  
  56.         }  
  57.   
  58.         // 数据去重  
  59.         getLetter = removeDuplicate(getLetter) ;  
  60.   
  61.         // 将联系人列表的字母标题排序  
  62.         Collections. sort(getLetter , new Comparator<String>() {  
  63.             @Override  
  64.             public int compare(String lhs, String rhs) {  
  65.                 return lhs.compareTo(rhs) ;  
  66.             }  
  67.         });  
  68.   
  69.         // 设置已排序好的标题  
  70.         sideBar .setLetter(getLetter);  
  71.   
  72.   
  73.         myAdapter = new SideBarAdapter( this, list, R.layout.adapter_side_bar) ;  
  74.         listView .setAdapter(myAdapter) ;  
  75.   
  76.     }  
  77.   
  78.     /** 
  79.      * 将中文转化为拼音 
  80.      */  
  81.     public void chineseToPinyin(List<ContactsModel> list) {  
  82.         for (int i = 0; i < list.size() ; i++) {  
  83.             ContactsModel contactsModel1 = list.get(i);  
  84.             // 将汉字转换为拼音  
  85.             String pinyinString = PinyinHelper.getShortPinyin(list.get(i).getName()) ;  
  86.             // 将拼音字符串转换为大写拼音  
  87.             String upperCasePinyinString = String.valueOf(pinyinString.charAt( 0)).toUpperCase();  
  88.             // 获取大写拼音字符串的第一个字符  
  89.             char tempChar = upperCasePinyinString.charAt( 0);  
  90.   
  91.             if (tempChar < 'A' || tempChar > 'Z' ) {  
  92.                 contactsModel1.setFirstLetter( "#");  
  93.             } else {  
  94.                 contactsModel1.setFirstLetter(String.valueOf(tempChar)) ;  
  95.             }  
  96.         }  
  97.     }  
  98.   
  99.     /** 
  100.      * 设置回调接口 
  101.      */  
  102.     public void setCallbackInterface() {  
  103.         // 回调接口  
  104.         sideBar .setOnCurrentLetterListener(new SideBar.OnCurrentLetterListener() {  
  105.             @Override  
  106.             public void showCurrentLetter(String currentLetter) {  
  107.                 txtShowCurrentLetter .setVisibility(View.VISIBLE) ;  
  108.                 txtShowCurrentLetter.setText(currentLetter) ;  
  109.   
  110.                 int position = myAdapter.getCurrentItemPosition(currentLetter);  
  111.                 if (position != -1 )  
  112.                     listView.setSelection(position) ;  
  113.             }  
  114.   
  115.             @Override  
  116.             public void hideCurrentLetter() {  
  117.                 txtShowCurrentLetter.setVisibility(View. GONE);  
  118.             }  
  119.         });  
  120.     }  
  121.   
  122.     /** 
  123.      * 初始化数据 
  124.      */  
  125.     public List<ContactsModel> initData() {  
  126.         List<ContactsModel> list = new ArrayList<>();  
  127.   
  128.         ContactsModel contactsModel ;  
  129.         String[] nameStrings = { "覃" , "岑 ""$ 来啊,来互相伤害啊 ""疍姬" , "梵蒂冈 "" 亳州""佟" , "郄 "" 张三""Edward"" 李四""萌萌哒" , "霾耷 "" 离散""赵信" , "啦啦 "" 辣妹子""嗷嗷" , "妹妹 ""']asd" , "%Hello"} ;  
  130.         for ( int i = 0 ; i < nameStrings.length ; i++) {  
  131.             contactsModel = new ContactsModel() ;  
  132.             contactsModel.setName(nameStrings[i]) ;  
  133.             list.add(contactsModel) ;  
  134.         }  
  135.         return list;  
  136.     }  
  137.   
  138.     /** 
  139.      * 去重数据 
  140.      * 
  141.      * @param list 
  142.      * @param <T> 
  143.      * @return 
  144.      */  
  145.     public <T> List< T> removeDuplicate (List<T> list) {  
  146.   
  147.         Set<T > h = new HashSet<>(list) ;  
  148.         list.clear() ;  
  149.         list.addAll(h) ;  
  150.         return list ;  
  151.   
  152.     }  
  153. }  
package per.edward.ui;

import android.os.Bundle ;
import android.support.v7.app.AppCompatActivity ;
import android.view.View ;
import android.widget.ListView ;
import android.widget.TextView ;

import java.util.ArrayList ;
import java.util.Collections ;
import java.util.Comparator ;
import java.util.HashSet ;
import java.util.List ;
import java.util.Set ;

import opensource.jpinyin.PinyinHelper ;

/**
* author:Edward
* 此 demo的博客地址:http://blog.csdn.net/u012814441
*/
public class MainActivity extends AppCompatActivity {
    private ListView listView;
    private TextView txtShowCurrentLetter;
    private SideBar sideBar;
    private SideBarAdapter myAdapter;
//    private List<ContactsModel> list;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super .onCreate(savedInstanceState) ;
        setContentView(R.layout. activity_main);
        txtShowCurrentLetter = (TextView) findViewById(R.id.txt_show_current_letter );
        sideBar = (SideBar) findViewById(R.id. side_bar);
        listView = (ListView) findViewById(R.id. list_view);

        setCallbackInterface() ;

        List<ContactsModel> list = initData() ;

        chineseToPinyin(list) ;

        // 将联系人列表的标题字母排序
        Collections. sort(list , new Comparator<ContactsModel>() {
            @Override
            public int compare(ContactsModel lhs, ContactsModel rhs) {
                return lhs.getFirstLetter().compareTo(rhs.getFirstLetter()) ;
            }
        });


        // 将联系人列表的标题字母放到 List<String>列表中,准备数据去重
        List<String> getLetter = new ArrayList<>();
        for ( int i = 0 ; i < list.size(); i++) {
            getLetter.add(list.get(i).getFirstLetter());
        }

        // 数据去重
        getLetter = removeDuplicate(getLetter) ;

        // 将联系人列表的字母标题排序
        Collections. sort(getLetter , new Comparator<String>() {
            @Override
            public int compare(String lhs, String rhs) {
                return lhs.compareTo(rhs) ;
            }
        });

        // 设置已排序好的标题
        sideBar .setLetter(getLetter);


        myAdapter = new SideBarAdapter( this, list, R.layout.adapter_side_bar) ;
        listView .setAdapter(myAdapter) ;

    }

    /**
     * 将中文转化为拼音
     */
    public void chineseToPinyin(List<ContactsModel> list) {
        for (int i = 0; i < list.size() ; i++) {
            ContactsModel contactsModel1 = list.get(i);
            // 将汉字转换为拼音
            String pinyinString = PinyinHelper.getShortPinyin(list.get(i).getName()) ;
            // 将拼音字符串转换为大写拼音
            String upperCasePinyinString = String.valueOf(pinyinString.charAt( 0)).toUpperCase();
            // 获取大写拼音字符串的第一个字符
            char tempChar = upperCasePinyinString.charAt( 0);

            if (tempChar < 'A' || tempChar > 'Z' ) {
                contactsModel1.setFirstLetter( "#");
            } else {
                contactsModel1.setFirstLetter(String.valueOf(tempChar)) ;
            }
        }
    }

    /**
     * 设置回调接口
     */
    public void setCallbackInterface() {
        // 回调接口
        sideBar .setOnCurrentLetterListener(new SideBar.OnCurrentLetterListener() {
            @Override
            public void showCurrentLetter(String currentLetter) {
                txtShowCurrentLetter .setVisibility(View.VISIBLE) ;
                txtShowCurrentLetter.setText(currentLetter) ;

                int position = myAdapter.getCurrentItemPosition(currentLetter);
                if (position != -1 )
                    listView.setSelection(position) ;
            }

            @Override
            public void hideCurrentLetter() {
                txtShowCurrentLetter.setVisibility(View. GONE);
            }
        });
    }

    /**
     * 初始化数据
     */
    public List<ContactsModel> initData() {
        List<ContactsModel> list = new ArrayList<>();

        ContactsModel contactsModel ;
        String[] nameStrings = { "覃" , "岑 ", "$ 来啊,来互相伤害啊 ", "疍姬" , "梵蒂冈 ", " 亳州", "佟" , "郄 ", " 张三", "Edward", " 李四", "萌萌哒" , "霾耷 ", " 离散", "赵信" , "啦啦 ", " 辣妹子", "嗷嗷" , "妹妹 ", "']asd" , "%Hello"} ;
        for ( int i = 0 ; i < nameStrings.length ; i++) {
            contactsModel = new ContactsModel() ;
            contactsModel.setName(nameStrings[i]) ;
            list.add(contactsModel) ;
        }
        return list;
    }

    /**
     * 去重数据
     *
     * @param list
     * @param <T>
     * @return
     */
    public <T> List< T> removeDuplicate (List<T> list) {

        Set<T > h = new HashSet<>(list) ;
        list.clear() ;
        list.addAll(h) ;
        return list ;

    }
}


6、最后特别注意的是。在项目工程的libs文件夹里面需要添加jpinyin.jar包(这个jar包可以在下面的demo源码中copy出来)。

7、 最终效果图

三、结束
好累啊,终于写完了。这是我目前写过最详细的博客了。鼓励一下呗。如果有不足的地方,欢迎指出。谢谢。
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值