浅谈android中手机联系人字母索引表的实现

本文转载自:http://blog.csdn.net/u013064109/article/details/52013744
标签: android手机联系人字母索引表

实际上字母索引表的效果,可以说在现在的众多APP中使用的非常流行,比如支付宝,微信中的联系人,还有购物,买票的APP中选择全国城市,切换城市的时候,这时候的城市也就是按照一个字母索引的顺序来显示,看起来是很方便的.其实这种字母索引表的效果最开始是出现在微信的联系人中.因为觉得这种效果功能在今后的项目中可以说是非常常见,可能会用的上,所以准备来波博客讲述一下实现的原理,一来方便以后自己复习,二来如果能够帮助一些android路上奋斗小伙伴也是蛮有意义的.
下面我们先来看下效果图,


看完效果图后我们可以来分析一下这个看似很复杂的功能怎么分解成一个个小功能来实现.要想实现如下的demo效果,主要关注在四个大的方面:
        1,实现右侧浮动字母索引项的列表:
      问题分析:
                     右侧浮在表面的字母竖向排列的view的实现,并且点击view中相应的字母,会弹出一个自定义的对话框,并且对话框只有一个TextView用于显示过那个点击后的字母,并且仔细观察这个弹出的字母对话框还会延迟一段时间才会消失,还有一个很重要也是很明显的效果:就是点击相应右边的竖向字母列表中的字母的时候并与中间的显示联系人的ListView中的字母item中的字母相等的时候,才会弹出字母对话框并且会将联系人列表中的对应的字母item顶到界面的顶部显示,最后还有一点就是这个竖向的view实际上在上下滑动的时候会不断改变弹出的内容以及联系人列表中的字母项不断跳动.
      实现方法:
                     通过自定义一个view,来准确绘制出字母项索引,因为还需要实现当我们点击浮动字母列表时,弹出被点击的字母text,所以很容易就想到
                     在这个自定义的右侧浮动字母索引项的列表中应该还有一个我们自定义的监听器,监听字母索引表的点击和滑动事件,利用监听器中的回调方法中的参数返回我们的点击或者滑动到字母,如果我们点击或者滑动的字母正好与我们联系人列表中的字母索引相等,才会去弹出字母显示框


        2,实现联系人list列表效果:
        问题分析:
                        第二点就是联系人那种已经按照字母表排好序的列表ListView的实现,这种ListView是怎么实现的呢?实现这种相同首字母的联系人放在一起显示,并在这些相同首字母的联系人子列表的最前面加上一个字母索引项.
         解决办法:这个我是在我以前自己封装的CommonAdapter进行扩展的,很是方便,至于CommonAdapter(即打造ListView的通用适配器封装)的封装个人灵感和取经于android大神hyman,不过自己CommAdapter有点自己见解,最近一直在整理,希望出一期有关listView和GridView的博客
      
        3,获取联系人数据: 联系人数据从哪来?因为是读取手机中的联系人,所以很简单用我们非常熟悉的android中的四大组件之一ContentProvider
        来获得手机中自带应用中的数据库.很开心的就是拿数据的时候我们可以看到在raw_contact表中的phonebook_label字段中保存了联系人中文第一个字的首字母.所以我们就省去了取得每一个联系人的中文第一个字拼音的首字母,并且还得借助pinyin4.jar获得每个中文汉字的拼音,从而可以拿到首字拼音的首字母,然后将这些字母以及相应联系人的信息作为一个Bean类保存起来,最后可以使用一个Bean类的集合对象来存储手机联系人的信息.即使我们拿到相应的首字拼音的首字母 放入到相应的集合中去,此时的集合时不合格的,我们需要将集合的数据对象,按照其对应的首字拼音的首字母排序,从a-z
      排序主要用到了Collections.sort(list,compator)以及定义一个Compator比较对象.这样最后就取得拿到的数据并且按照联系人首字的拼音的首字母从a到z的排序好的集合.


      4,整合建立关系: 
      做到这里就是各个方面的工作都完成了,但是三件事貌似没什么关系,但是有一种关系千万别忽略了就是点击了竖向字母表中的字母的时候此时联系人列表中的字母索引项会跟着点击的字母变化而跳到顶部位置.所以需要建立关联就是通过一个viewpager.setSelection()方法就行了


    以上就是整个自定义view实现手机联系人的字母索引表的实现大致思路逻辑.


    那么我们就开始吧

第一,首先我们来解决自定义View实现浮动的字母索引项的列表.

  1. package com.mikyou.contactdemo.myview;  
  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;  
  8. import android.util.AttributeSet;  
  9. import android.view.MotionEvent;  
  10. import android.view.View;  
  11.   
  12. public class MikyouLetterListView extends View {  
  13.   
  14.     private OnTouchingLetterChangedListener listener;  
  15.     //定义了显示在最右边的浮动的索引项的列表,当然这个是固定的,所以可以直接初始化,如果需要变动的话则可以通过自定义属性来指定  
  16.     String[] b = {  "#","A""B""C""D""E""F""G""H""I""J""K",  
  17.             "L""M""N""O""P""Q""R""S""T""U""V""W""X",  
  18.             "Y""Z"};  
  19.     int choose = -1;//用于标记点击存放字母数组中的下标  
  20.     Paint paint = new Paint();  
  21.     boolean showBkg = false;//这个boolean变量主要是控制当我们点击索引列表中整个索引列表的背景有个变化,为false表示开始没点击背景为正常显示时候的背景  
  22.   
  23.     public MikyouLetterListView(Context context, AttributeSet attrs, int defStyle) {  
  24.         super(context, attrs, defStyle);  
  25.     }  
  26.   
  27.     public MikyouLetterListView(Context context, AttributeSet attrs) {  
  28.         super(context, attrs);  
  29.     }  
  30.   
  31.     public MikyouLetterListView(Context context) {  
  32.         super(context);  
  33.     }  
  34.   
  35.   
  36.     @Override  
  37.     protected void onDraw(Canvas canvas) {  
  38.         super.onDraw(canvas);  
  39.         if (showBkg) {//如果此时为true的话则表示需要改变整个canvas背景也即是索引项的背景  
  40.             canvas.drawColor(Color.parseColor("#10000000"));  
  41.         }  
  42.         /** 
  43.          * 注意:在自定义view中的如果不需要设置wrap_content属性就不需要自己重写onMeasure方法 
  44.          * 因为在onMeasure方法中系统默认会自动测量两种模式:一个是match_parent另一个则是自己指定明确尺寸大小 
  45.          * 这两种方法对应着这一种MeasureSpec.AT_MOST测量模式,由于我们设置这个自定义浮动的字母索引表宽度是指定明确大小 
  46.          * 高度是match_parent模式,所以这里就不要手动测量了直接通过getHeight和getWidth直接拿到系统自动测量好高度和宽度 
  47.          * */  
  48.         int height = getHeight();  
  49.         int width = getWidth();  
  50.         //让整个显示的每个字母均分整个屏幕高度尺寸,这个singleHeight就是每个字母占据的高度  
  51.         int singleHeight = height / b.length;  
  52.         //遍历循环绘制每一个字母text  
  53.         for (int i = 0; i < b.length; i++) {  
  54.   
  55.             //绘制字母text的颜色  
  56.             paint.setColor(Color.parseColor("#515151"));  
  57.             //绘制字母text的字体大小  
  58.             paint.setTextSize(25);  
  59.             //绘制字母text的字体样式  
  60.             paint.setTypeface(Typeface.DEFAULT_BOLD);  
  61.             //设置抗锯齿样式  
  62.             paint.setAntiAlias(true);  
  63.             if (i == choose) {//判断如果点击字母的下标等于i,那么就会设置绘制点击字母的样式用于高亮显示  
  64.                 paint.setColor(Color.parseColor("#3399ff"));  
  65.                 paint.setFakeBoldText(true);  
  66.             }  
  67.             /** 
  68.              * 注意:canvas在绘制text的时候,他绘制的起点不是text的左上角而是它的左下角 
  69.              * (xPos,yPos)表示每个字母左下角的位置的坐标 
  70.              *xPos = width / 2 - paint.measureText(b[i]) / 2:意思很容易理解,就是用 
  71.              * (总的view的宽度(可能还包括leftPadding和rightPadding的大小)-每个字母宽度)/2得到就是每个字母的左下角的X坐标, 
  72.              * 仔细想下每个text的起点的x坐标都是一样的.paint.measureText(b[i])得到每一个字母宽度大小 
  73.              * 由于是左下角,所以它们的Y坐标:应该如下设置 yPos = singleHeight * i + singleHeight; 
  74.              * */  
  75.             float xPos = width / 2 - paint.measureText(b[i]) / 2;//得到绘制字母text的起点的X坐标  
  76.             float yPos = singleHeight * i + singleHeight;//得到绘制字母text的起点的Y坐标  
  77.             canvas.drawText(b[i], xPos, yPos, paint);//开始绘制每个字母  
  78.             paint.reset();//绘制完一个字母需要重置一下画笔对象  
  79.         }  
  80.   
  81.     }  
  82.   
  83.     @Override  
  84.     public boolean dispatchTouchEvent(MotionEvent event) {//重写view的触摸事件分发方法  
  85.         final int action = event.getAction();  
  86.         final float y = event.getY();//由于只涉及到Y轴坐标,只获取y坐标  
  87.         final int oldChoose = choose;//oldChoose用于记录上一次点击字母所在字母数组中的下标  
  88.         final int c = (int) (y / getHeight() * b.length);//得到点击或触摸的位置从而确定对应点击或触摸的字母所在字母数组中的下标  
  89.         switch (action) {  
  90.             case MotionEvent.ACTION_DOWN://监听按下事件  
  91.                 showBkg = true;//按下后整个view的背景变色,showBkg为true  
  92.                 if (oldChoose != c && listener != null) {//如果此次点击的字母数组下标不等于上一次的且已经注册了监听事件的,  
  93.                     if (c >= 0 && c <= b.length) {//并且点击得到数组下标在字母数组范围内的,我们就将此时的字母回调出去  
  94.                         listener.onTouchingLetterChanged(b[c]);//我们就将此时的对应在字母数组中的字母回调出去  
  95.                         choose = c;//并且更新当前选中的字母下标存储在choose变量中  
  96.                         invalidate();//最后通知canvas重新绘制  
  97.                     }  
  98.                 }  
  99.   
  100.                 break;  
  101.             case MotionEvent.ACTION_MOVE://监听移动事件,因为按下的时候已经把背景showBkg设置true,这里就不需要重新设置,其他操作与按下的事件一致  
  102.                 if (oldChoose != c && listener != null) {  
  103.                     if (c >= 0 && c <= b.length) {  
  104.                         listener.onTouchingLetterChanged(b[c]);  
  105.                         choose = c;  
  106.                         invalidate();  
  107.                     }  
  108.                 }  
  109.                 break;  
  110.             case MotionEvent.ACTION_UP://监听手指抬起的动作  
  111.                 showBkg = false;//此时的背景将会恢复到初始状态,showBkg=false  
  112.                 choose = -1;//此时记录下标的变量也需要重置  
  113.                 invalidate();//并且重绘整个view  
  114.                 break;  
  115.         }  
  116.         return true;  
  117.     }  
  118.   
  119.     @Override  
  120.     public boolean onTouchEvent(MotionEvent event) {  
  121.         return super.onTouchEvent(event);  
  122.     }  
  123.     /** 
  124.      * 注册自定义监听器 
  125.      * */  
  126.     public void setOnTouchingLetterChangedListener(  
  127.             OnTouchingLetterChangedListener listener) {  
  128.         this.listener = listener;  
  129.     }  
  130.     /** 
  131.      * 定义一个接口,用于回调出点击后的字母,显示在弹出的字母对话框中 
  132.      * */  
  133.     public interface OnTouchingLetterChangedListener {  
  134.         public void onTouchingLetterChanged(String s);  
  135.     }  
  136.   
  137. }  
package com.mikyou.contactdemo.myview;

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

public class MikyouLetterListView extends View {

	private OnTouchingLetterChangedListener listener;
	//定义了显示在最右边的浮动的索引项的列表,当然这个是固定的,所以可以直接初始化,如果需要变动的话则可以通过自定义属性来指定
	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"};
	int choose = -1;//用于标记点击存放字母数组中的下标
	Paint paint = new Paint();
	boolean showBkg = false;//这个boolean变量主要是控制当我们点击索引列表中整个索引列表的背景有个变化,为false表示开始没点击背景为正常显示时候的背景

	public MikyouLetterListView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	public MikyouLetterListView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public MikyouLetterListView(Context context) {
		super(context);
	}


	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		if (showBkg) {//如果此时为true的话则表示需要改变整个canvas背景也即是索引项的背景
			canvas.drawColor(Color.parseColor("#10000000"));
		}
		/**
		 * 注意:在自定义view中的如果不需要设置wrap_content属性就不需要自己重写onMeasure方法
		 * 因为在onMeasure方法中系统默认会自动测量两种模式:一个是match_parent另一个则是自己指定明确尺寸大小
		 * 这两种方法对应着这一种MeasureSpec.AT_MOST测量模式,由于我们设置这个自定义浮动的字母索引表宽度是指定明确大小
		 * 高度是match_parent模式,所以这里就不要手动测量了直接通过getHeight和getWidth直接拿到系统自动测量好高度和宽度
		 * */
		int height = getHeight();
		int width = getWidth();
		//让整个显示的每个字母均分整个屏幕高度尺寸,这个singleHeight就是每个字母占据的高度
		int singleHeight = height / b.length;
		//遍历循环绘制每一个字母text
		for (int i = 0; i < b.length; i++) {

			//绘制字母text的颜色
			paint.setColor(Color.parseColor("#515151"));
			//绘制字母text的字体大小
			paint.setTextSize(25);
			//绘制字母text的字体样式
			paint.setTypeface(Typeface.DEFAULT_BOLD);
			//设置抗锯齿样式
			paint.setAntiAlias(true);
			if (i == choose) {//判断如果点击字母的下标等于i,那么就会设置绘制点击字母的样式用于高亮显示
				paint.setColor(Color.parseColor("#3399ff"));
				paint.setFakeBoldText(true);
			}
			/**
			 * 注意:canvas在绘制text的时候,他绘制的起点不是text的左上角而是它的左下角
			 * (xPos,yPos)表示每个字母左下角的位置的坐标
			 *xPos = width / 2 - paint.measureText(b[i]) / 2:意思很容易理解,就是用
			 * (总的view的宽度(可能还包括leftPadding和rightPadding的大小)-每个字母宽度)/2得到就是每个字母的左下角的X坐标,
			 * 仔细想下每个text的起点的x坐标都是一样的.paint.measureText(b[i])得到每一个字母宽度大小
			 * 由于是左下角,所以它们的Y坐标:应该如下设置 yPos = singleHeight * i + singleHeight;
			 * */
			float xPos = width / 2 - paint.measureText(b[i]) / 2;//得到绘制字母text的起点的X坐标
			float yPos = singleHeight * i + singleHeight;//得到绘制字母text的起点的Y坐标
			canvas.drawText(b[i], xPos, yPos, paint);//开始绘制每个字母
			paint.reset();//绘制完一个字母需要重置一下画笔对象
		}

	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {//重写view的触摸事件分发方法
		final int action = event.getAction();
		final float y = event.getY();//由于只涉及到Y轴坐标,只获取y坐标
		final int oldChoose = choose;//oldChoose用于记录上一次点击字母所在字母数组中的下标
		final int c = (int) (y / getHeight() * b.length);//得到点击或触摸的位置从而确定对应点击或触摸的字母所在字母数组中的下标
		switch (action) {
			case MotionEvent.ACTION_DOWN://监听按下事件
				showBkg = true;//按下后整个view的背景变色,showBkg为true
				if (oldChoose != c && listener != null) {//如果此次点击的字母数组下标不等于上一次的且已经注册了监听事件的,
					if (c >= 0 && c <= b.length) {//并且点击得到数组下标在字母数组范围内的,我们就将此时的字母回调出去
						listener.onTouchingLetterChanged(b[c]);//我们就将此时的对应在字母数组中的字母回调出去
						choose = c;//并且更新当前选中的字母下标存储在choose变量中
						invalidate();//最后通知canvas重新绘制
					}
				}

				break;
			case MotionEvent.ACTION_MOVE://监听移动事件,因为按下的时候已经把背景showBkg设置true,这里就不需要重新设置,其他操作与按下的事件一致
				if (oldChoose != c && listener != null) {
					if (c >= 0 && c <= b.length) {
						listener.onTouchingLetterChanged(b[c]);
						choose = c;
						invalidate();
					}
				}
				break;
			case MotionEvent.ACTION_UP://监听手指抬起的动作
				showBkg = false;//此时的背景将会恢复到初始状态,showBkg=false
				choose = -1;//此时记录下标的变量也需要重置
				invalidate();//并且重绘整个view
				break;
		}
		return true;
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		return super.onTouchEvent(event);
	}
	/**
	 * 注册自定义监听器
	 * */
	public void setOnTouchingLetterChangedListener(
			OnTouchingLetterChangedListener listener) {
		this.listener = listener;
	}
	/**
	 * 定义一个接口,用于回调出点击后的字母,显示在弹出的字母对话框中
	 * */
	public interface OnTouchingLetterChangedListener {
		public void onTouchingLetterChanged(String s);
	}

}
第二,点击字母后弹出的字母框的布局和样式的实现(这个比较简单弹出框就是一个TextView控件):

  1. overlay.xml(布局)  
  2. <?xml version="1.0" encoding="utf-8"?>  
  3. <TextView  
  4.     xmlns:android="http://schemas.android.com/apk/res/android"  
  5.     android:layout_width="wrap_content"  
  6.     android:layout_height="warp_content"  
  7.     android:textSize="70sp"  
  8.     android:textColor="#FFFFFF"  
  9.     android:background="@drawable/overlay_bg"    
  10.     android:minWidth="80dip"    
  11.     android:maxWidth="80dip"    
  12.     android:padding="10dp"  
  13.     android:gravity="center"  
  14. />  
  15. overlay_bg.xml(布局样式):  
  16. <?xml version="1.0" encoding="utf-8"?>  
  17. <shape xmlns:android="http://schemas.android.com/apk/res/android"  
  18.     android:shape="rectangle" >  
  19.   
  20.     <solid android:color="#56abe4" />  
  21.   
  22.     <stroke  
  23.         android:width="0.5dp"  
  24.         android:color="#56abe4" />  
  25.   
  26.     <corners android:radius="15dp" />  
  27.   
  28. </shape>  
overlay.xml(布局)
<?xml version="1.0" encoding="utf-8"?>
<TextView
  	xmlns:android="http://schemas.android.com/apk/res/android"
  	android:layout_width="wrap_content"
	android:layout_height="warp_content"
	android:textSize="70sp"
    android:textColor="#FFFFFF"
    android:background="@drawable/overlay_bg"  
    android:minWidth="80dip"  
    android:maxWidth="80dip"  
    android:padding="10dp"
    android:gravity="center"
/>
overlay_bg.xml(布局样式):
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <solid android:color="#56abe4" />

    <stroke
        android:width="0.5dp"
        android:color="#56abe4" />

    <corners android:radius="15dp" />

</shape>

第三,保存联系人对象的Bean类:

  1. package com.mikyou.myguardian.bean;  
  2.   
  3. import java.io.Serializable;  
  4.   
  5. /** 
  6.  * Created by mikyou on 16-7-19. 
  7.  */  
  8. public class ContactBean implements Serializable {  
  9.     private int iconId;  
  10.     private String title;  
  11.     private String phoneNum;  
  12.     private String firstHeadLetter;  
  13.   
  14.     public ContactBean(int iconId, String title, String phoneNum, String firstHeadLetter) {  
  15.         this.iconId = iconId;  
  16.         this.title = title;  
  17.         this.phoneNum = phoneNum;  
  18.         this.firstHeadLetter=firstHeadLetter;  
  19.     }  
  20.   
  21.     public ContactBean() {  
  22.   
  23.     }  
  24.   
  25.     public int getIconId() {  
  26.         return iconId;  
  27.     }  
  28.   
  29.     public String getFirstHeadLetter() {  
  30.         return firstHeadLetter;  
  31.     }  
  32.   
  33.     public void setFirstHeadLetter(String firstHeadLetter) {  
  34.         this.firstHeadLetter = firstHeadLetter;  
  35.     }  
  36.     public void setIconId(int iconId) {  
  37.         this.iconId = iconId;  
  38.     }  
  39.   
  40.     public String getTitle() {  
  41.         return title;  
  42.     }  
  43.   
  44.     public void setTitle(String title) {  
  45.         this.title = title;  
  46.     }  
  47.   
  48.     public String getPhoneNum() {  
  49.         return phoneNum;  
  50.     }  
  51.   
  52.     public void setPhoneNum(String phoneNum) {  
  53.         this.phoneNum = phoneNum;  
  54.     }  
  55.   
  56.       
  57.     @Override  
  58.     public String toString() {  
  59.         return "ContactBean{" +  
  60.                 "iconId=" + iconId +  
  61.                 ", title='" + title + '\'' +  
  62.                 ", phoneNum='" + phoneNum + '\'' +  
  63.                 ", descriptor='" + descriptor + '\'' +  
  64.                 ", firstHeadLetter='" + firstHeadLetter + '\'' +  
  65.                 ", headLetterNum='" + headLetterNum + '\'' +  
  66.                 '}';  
  67.     }  
  68. }  
package com.mikyou.myguardian.bean;

import java.io.Serializable;

/**
 * Created by mikyou on 16-7-19.
 */
public class ContactBean implements Serializable {
    private int iconId;
    private String title;
    private String phoneNum;
    private String firstHeadLetter;

    public ContactBean(int iconId, String title, String phoneNum, String firstHeadLetter) {
        this.iconId = iconId;
        this.title = title;
        this.phoneNum = phoneNum;
        this.firstHeadLetter=firstHeadLetter;
    }

    public ContactBean() {

    }

    public int getIconId() {
        return iconId;
    }

    public String getFirstHeadLetter() {
        return firstHeadLetter;
    }

    public void setFirstHeadLetter(String firstHeadLetter) {
        this.firstHeadLetter = firstHeadLetter;
    }
    public void setIconId(int iconId) {
        this.iconId = iconId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getPhoneNum() {
        return phoneNum;
    }

    public void setPhoneNum(String phoneNum) {
        this.phoneNum = phoneNum;
    }

    
    @Override
    public String toString() {
        return "ContactBean{" +
                "iconId=" + iconId +
                ", title='" + title + '\'' +
                ", phoneNum='" + phoneNum + '\'' +
                ", descriptor='" + descriptor + '\'' +
                ", firstHeadLetter='" + firstHeadLetter + '\'' +
                ", headLetterNum='" + headLetterNum + '\'' +
                '}';
    }
}
第四整个布局activity_main.xml:

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout  
  3.     xmlns:android="http://schemas.android.com/apk/res/android"  
  4.     xmlns:app="http://schemas.android.com/apk/res-auto"  
  5.     android:layout_width="match_parent"  
  6.     android:layout_height="match_parent"  
  7.     android:orientation="vertical"  
  8.     >  
  9.     <android.support.v7.widget.Toolbar  
  10.         android:id="@+id/id_toolbar"  
  11.         android:layout_width="match_parent"  
  12.         android:layout_height="wrap_content"  
  13.         app:title="手机联系人"  
  14.         app:navigationIcon="@mipmap/more"  
  15.         android:background="@color/colorPrimary"  
  16.         app:titleTextColor="#FFFFFF"  
  17.         >  
  18.     </android.support.v7.widget.Toolbar>  
  19.     <RelativeLayout  
  20.         android:layout_width="match_parent"  
  21.         android:layout_height="match_parent">  
  22.         <ListView  
  23.             android:id="@+id/id_listview"  
  24.             android:layout_width="match_parent"  
  25.             android:layout_height="wrap_content"  
  26.             android:divider="#22000000"  
  27.             android:dividerHeight="0.1dp"  
  28.             ></ListView>  
  29.         <com.mikyou.contactdemo.myview.MikyouLetterListView  
  30.             android:id="@+id/id_letterview"  
  31.             android:layout_width="30dp"  
  32.             android:layout_height="match_parent"  
  33.             android:layout_alignParentRight="true"  
  34.             />  
  35.     </RelativeLayout>  
  36. </LinearLayout>  
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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:orientation="vertical"
    >
    <android.support.v7.widget.Toolbar
        android:id="@+id/id_toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:title="手机联系人"
        app:navigationIcon="@mipmap/more"
        android:background="@color/colorPrimary"
        app:titleTextColor="#FFFFFF"
        >
    </android.support.v7.widget.Toolbar>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <ListView
            android:id="@+id/id_listview"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:divider="#22000000"
            android:dividerHeight="0.1dp"
            ></ListView>
        <com.mikyou.contactdemo.myview.MikyouLetterListView
            android:id="@+id/id_letterview"
            android:layout_width="30dp"
            android:layout_height="match_parent"
            android:layout_alignParentRight="true"
            />
    </RelativeLayout>
</LinearLayout>

第五如何获取手机中的联系人信息:
主要思想就是通过android的中的四大组件之一ContentProvider来获取,这个很简单就不多做说明直接上代码,最后别忘记加上两个权限分别是读、写手机联系人的权限:
权限:
  <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
 获取手机联系人的代码(我将它封装成一个类中的方法并且直接返回为我们的ContactBean类集合,可以直接用来装载adapter。使用起来很方便):

  1. package com.mikyou.myguardian.service;  
  2.   
  3. import android.content.ContentResolver;  
  4. import android.content.Context;  
  5. import android.database.Cursor;  
  6. import android.net.Uri;  
  7.   
  8. import com.mikyou.myguardian.bean.ContactBean;  
  9.   
  10. import java.util.ArrayList;  
  11. import java.util.List;  
  12.   
  13. /** 
  14.  * Created by mikyou on 16-7-19. 
  15.  * 用于返回读取到联系人的集合 
  16.  */  
  17. public class ContactInfoService {  
  18.     private Context context;  
  19.   
  20.     public ContactInfoService(Context context) {  
  21.         this.context = context;  
  22.     }  
  23.     public List<ContactBean> getContactList(){  
  24.   
  25.         List<ContactBean> mContactBeanList=new ArrayList<>();  
  26.         ContactBean mContactBean=null;  
  27.         ContentResolver mContentResolver=context.getContentResolver();  
  28.         Uri uri=Uri.parse("content://com.android.contacts/raw_contacts");  
  29.         Uri dataUri=Uri.parse("content://com.android.contacts/data");  
  30.   
  31.         Cursor cursor =mContentResolver.query(uri,null,null,null,null);  
  32.         while (cursor.moveToNext()){  
  33.           mContactBean=new ContactBean();  
  34.             String id=cursor.getString(cursor.getColumnIndex("_id"));  
  35.             String title=cursor.getString(cursor.getColumnIndex("display_name"));//获取联系人姓名  
  36.             String firstHeadLetter=cursor.getString(cursor.getColumnIndex("phonebook_label"));//这个字段保存了每个联系人首字的拼音的首字母  
  37.             mContactBean.setTitle(title);  
  38.             mContactBean.setFirstHeadLetter(firstHeadLetter);  
  39.   
  40.             Cursor dataCursor=mContentResolver.query(dataUri,null,"raw_contact_id= ?",new String[]{id},null);  
  41.             while(dataCursor.moveToNext()){  
  42.                 String type=dataCursor.getString(dataCursor.getColumnIndex("mimetype"));  
  43.                 if (type.equals("vnd.android.cursor.item/phone_v2")){//如果得到的mimeType类型为手机号码类型才去接收  
  44.                     String phoneNum=dataCursor.getString(dataCursor.getColumnIndex("data1"));//获取手机号码  
  45.                     mContactBean.setPhoneNum(phoneNum);  
  46.                 }  
  47.             }  
  48.             dataCursor.close();  
  49.             if (mContactBean.getTitle()!=null&&mContactBean.getPhoneNum()!=null){  
  50.                 mContactBeanList.add(mContactBean);  
  51.             }  
  52.   
  53.         }  
  54.         cursor.close();  
  55.         return mContactBeanList;  
  56.     }  
  57. }  
package com.mikyou.myguardian.service;

import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;

import com.mikyou.myguardian.bean.ContactBean;

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

/**
 * Created by mikyou on 16-7-19.
 * 用于返回读取到联系人的集合
 */
public class ContactInfoService {
    private Context context;

    public ContactInfoService(Context context) {
        this.context = context;
    }
    public List<ContactBean> getContactList(){

        List<ContactBean> mContactBeanList=new ArrayList<>();
        ContactBean mContactBean=null;
        ContentResolver mContentResolver=context.getContentResolver();
        Uri uri=Uri.parse("content://com.android.contacts/raw_contacts");
        Uri dataUri=Uri.parse("content://com.android.contacts/data");

        Cursor cursor =mContentResolver.query(uri,null,null,null,null);
        while (cursor.moveToNext()){
          mContactBean=new ContactBean();
            String id=cursor.getString(cursor.getColumnIndex("_id"));
            String title=cursor.getString(cursor.getColumnIndex("display_name"));//获取联系人姓名
            String firstHeadLetter=cursor.getString(cursor.getColumnIndex("phonebook_label"));//这个字段保存了每个联系人首字的拼音的首字母
            mContactBean.setTitle(title);
            mContactBean.setFirstHeadLetter(firstHeadLetter);

            Cursor dataCursor=mContentResolver.query(dataUri,null,"raw_contact_id= ?",new String[]{id},null);
            while(dataCursor.moveToNext()){
                String type=dataCursor.getString(dataCursor.getColumnIndex("mimetype"));
                if (type.equals("vnd.android.cursor.item/phone_v2")){//如果得到的mimeType类型为手机号码类型才去接收
                    String phoneNum=dataCursor.getString(dataCursor.getColumnIndex("data1"));//获取手机号码
                    mContactBean.setPhoneNum(phoneNum);
                }
            }
            dataCursor.close();
            if (mContactBean.getTitle()!=null&&mContactBean.getPhoneNum()!=null){
                mContactBeanList.add(mContactBean);
            }

        }
        cursor.close();
        return mContactBeanList;
    }
}
第六,就是实现联系人的ListView大家可能会看到这个和我们平时的看到的ListView有些不一样,因为在此次联系人的ListView中还有"A","B","C","D"...这小的字母item这个主要是将相同联系人的第一个字的拼音的首字母放在一起。那怎么去实现这样的一个ListView呢?
这里:我想了一个的办法就是每个ListView的item项目的布局中都包含一个字母索引项目,也就是每一个联系人的顶部都有一个字母索引项目用于显示该联系人
的首字的拼音的首字母,然后将我们集合中的对象按照首字母来排序,那么集合中的联系人对象将会是按照A到Z排序并且为A的联系人放在一起,为B放在一起。可是由于我们的每个item都包含一个字母索引项,所以我们需要将相同字母的索引项去掉并且只保留一个且为第一个的该类字母的索引项即可。如果做到这一点呢???其实仔细想下也不难,由于我们的集合是已经按照字母大小排好顺序的,并且首字母为A为一堆,首字母为B为一堆,首字母为C的为一堆...
那么我们就去遍历直接整个集合,用当前的item中的首字母去匹配上一个item中的首字母如果相同则表示是同一堆字母,就可以直接把该item中的顶部的标示的字母索引项中的内容设置为""空字符,并且把该索引项的Visibilty设置为GONE,如果不等就说明将有新的字母堆产生,而且是该堆中的第一个字母项,这时候我们就需要用一个Map集合alphaIndexer将他们保存起来,key为字母,value为该字母在集合中的下标,并且把该索引项的Visibilty设置为Visible。并且还得另外用一个集合selections保存该字母,为什么需要用一个集合保存该字母呢?主要用于这么一个需求就是:当我们去点击右边浮动的索引项列表中的字母时候,如果点击的字母不在我们sleections集合中的话,就不会触发弹出字母显示框,这也很容易理解就是我的联系人列表中根本就没有以我点击的字母为首字母的拼音的联系人。所以这个selections集合就显得尤为关键了,它可谓是浮动列表点击的字母与联系人列表实现联动的核心桥梁和媒介。

  1. 具体看该ListView的Adapter:  
  2. package com.mikyou.myguardian.adapter;  
  3.   
  4. import android.content.Context;  
  5. import android.view.View;  
  6. import android.widget.ImageView;  
  7. import android.widget.TextView;  
  8.   
  9. import com.mikyou.adapter.CommonAdapter;  
  10. import com.mikyou.myguardian.R;  
  11. import com.mikyou.myguardian.bean.ContactBean;  
  12. import com.mikyou.tools.ViewHolder;  
  13.   
  14. import java.util.ArrayList;  
  15. import java.util.HashMap;  
  16. import java.util.List;  
  17. import java.util.Map;  
  18.   
  19. /** 
  20.  * Created by mikyou on 16-7-19. 
  21.  */  
  22. public class TestContactListAdapter extends CommonAdapter<ContactBean> {  
  23.     private final int VIEW_TYPE=3;  
  24.     private Map<String,Integer> alphaIndexer;  
  25.     private List<String> sections;  
  26.     private List<ContactBean> listBeans;  
  27.     private OnGetAlphaIndexerAndSectionsListener listener;  
  28.     private boolean flag;//标志用于只执行一次代码  
  29.     public TestContactListAdapter(Context context, List<ContactBean> listBeans, int layoutId) {  
  30.         super(context, listBeans, layoutId);  
  31.         this.listBeans=listBeans;  
  32.         alphaIndexer=new HashMap<>();  
  33.         sections=new ArrayList<>();  
  34.         for (int i = 0; i <listBeans.size();i++) {  
  35.              //当前汉语拼音的首字母  
  36.             String currentAlpha=listBeans.get(i).getFirstHeadLetter();  
  37.             //上一个拼音的首字母,如果不存在则为""  
  38.             String previewAlpha=(i-1)>=0?listBeans.get(i-1).getFirstHeadLetter():"";  
  39.             if (!previewAlpha.equals(currentAlpha)){  
  40.                 String firstAlpha=listBeans.get(i).getFirstHeadLetter();  
  41.                 alphaIndexer.put(firstAlpha,i);  
  42.                 sections.add(firstAlpha);  
  43.             }  
  44.   
  45.         }  
  46.   
  47.     }  
  48.   
  49.     @Override  
  50.     public int getItemViewType(int position) {  
  51.          int type=0;  
  52.         if (position==0){  
  53.             type=2;  
  54.         }else if (position==1){  
  55.             type=1;  
  56.         }  
  57.         return type;  
  58.     }  
  59.   
  60.     @Override  
  61.     public int getCount() {  
  62.         //注意:为什么没有直接把回调方法的调用写在构造器中呢?因为构造器只会调用一次,当第一次调用listener的时候是为空的  
  63.         //而要初始化listener对象,则需要先去创建对象再去通过对象调用set方法来初始化这个listener对象,再去new对象的时候又要去用到listener产生了矛盾  
  64.         //所以放在getCount中调用,只会调用一次,符合需求  
  65.         if (!flag){  
  66.             if (listener!=null){  
  67.                 listener.getAlphaIndexerAndSectionsListner(alphaIndexer,sections);  
  68.             }  
  69.             flag=true;  
  70.         }  
  71.   
  72.         return listBeans.size();  
  73.   
  74.     }  
  75.   
  76.     @Override  
  77.     public int getViewTypeCount() {  
  78.         return VIEW_TYPE;  
  79.     }  
  80.   
  81.     @Override  
  82.     public void convert(ViewHolder viewHolder, ContactBean contactBean) {  
  83.   
  84.         int viewType=getItemViewType(viewHolder.getmPosition());  
  85.         ImageView iv=viewHolder.getView(R.id.contact_icon_id);  
  86.         iv.setImageResource(R.mipmap.contact_user);  
  87.         viewHolder.setText(R.id.contact_title,contactBean.getTitle()).setText(R.id.contact_phone_num,contactBean.getPhoneNum());  
  88.   
  89.   
  90.   
  91.         if (viewHolder.getmPosition()>=1){  
  92.             String currentAlpha=listBeans.get(viewHolder.getmPosition()).getFirstHeadLetter();  
  93.             String previewAlpha=listBeans.get(viewHolder.getmPosition()-1).getFirstHeadLetter();  
  94.             if (!previewAlpha.equals(currentAlpha)){//不相等表示有新的字母项产生且为该类字母堆中的第一个字母索引项  
  95.                 viewHolder.getView(R.id.first_alpha).setVisibility(View.VISIBLE);//把新的字母列表项设置VISIBlE  
  96.                 TextView tv= viewHolder.getView(R.id.first_alpha);  
  97.                 tv.setText(currentAlpha);  
  98.             }else {//表示没有新的字母堆出现,也就说明该item是属于同类字母堆中且不是第一个,那么就需要将这个索引项设置GONE  
  99.                 viewHolder.getView(R.id.first_alpha).setVisibility(View.GONE);  
  100.             }  
  101.         }  
  102.   
  103.   
  104.     }  
  105.     public void setOnGetAlphaIndeserAndSectionListener(OnGetAlphaIndexerAndSectionsListener listener){  
  106.         this.listener=listener;  
  107.     }  
  108.   
  109.     public interface OnGetAlphaIndexerAndSectionsListener{  
  110.         public void getAlphaIndexerAndSectionsListner(Map<String,Integer>alphaIndexer,List<String>sections);  
  111.   
  112.     }  
  113. }  
具体看该ListView的Adapter:
package com.mikyou.myguardian.adapter;

import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import com.mikyou.adapter.CommonAdapter;
import com.mikyou.myguardian.R;
import com.mikyou.myguardian.bean.ContactBean;
import com.mikyou.tools.ViewHolder;

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

/**
 * Created by mikyou on 16-7-19.
 */
public class TestContactListAdapter extends CommonAdapter<ContactBean> {
    private final int VIEW_TYPE=3;
    private Map<String,Integer> alphaIndexer;
    private List<String> sections;
    private List<ContactBean> listBeans;
    private OnGetAlphaIndexerAndSectionsListener listener;
    private boolean flag;//标志用于只执行一次代码
    public TestContactListAdapter(Context context, List<ContactBean> listBeans, int layoutId) {
        super(context, listBeans, layoutId);
        this.listBeans=listBeans;
        alphaIndexer=new HashMap<>();
        sections=new ArrayList<>();
        for (int i = 0; i <listBeans.size();i++) {
             //当前汉语拼音的首字母
            String currentAlpha=listBeans.get(i).getFirstHeadLetter();
            //上一个拼音的首字母,如果不存在则为""
            String previewAlpha=(i-1)>=0?listBeans.get(i-1).getFirstHeadLetter():"";
            if (!previewAlpha.equals(currentAlpha)){
                String firstAlpha=listBeans.get(i).getFirstHeadLetter();
                alphaIndexer.put(firstAlpha,i);
                sections.add(firstAlpha);
            }

        }

    }

    @Override
    public int getItemViewType(int position) {
         int type=0;
        if (position==0){
            type=2;
        }else if (position==1){
            type=1;
        }
        return type;
    }

    @Override
    public int getCount() {
        //注意:为什么没有直接把回调方法的调用写在构造器中呢?因为构造器只会调用一次,当第一次调用listener的时候是为空的
        //而要初始化listener对象,则需要先去创建对象再去通过对象调用set方法来初始化这个listener对象,再去new对象的时候又要去用到listener产生了矛盾
        //所以放在getCount中调用,只会调用一次,符合需求
        if (!flag){
            if (listener!=null){
                listener.getAlphaIndexerAndSectionsListner(alphaIndexer,sections);
            }
            flag=true;
        }

        return listBeans.size();

    }

    @Override
    public int getViewTypeCount() {
        return VIEW_TYPE;
    }

    @Override
    public void convert(ViewHolder viewHolder, ContactBean contactBean) {

        int viewType=getItemViewType(viewHolder.getmPosition());
        ImageView iv=viewHolder.getView(R.id.contact_icon_id);
        iv.setImageResource(R.mipmap.contact_user);
        viewHolder.setText(R.id.contact_title,contactBean.getTitle()).setText(R.id.contact_phone_num,contactBean.getPhoneNum());



        if (viewHolder.getmPosition()>=1){
            String currentAlpha=listBeans.get(viewHolder.getmPosition()).getFirstHeadLetter();
            String previewAlpha=listBeans.get(viewHolder.getmPosition()-1).getFirstHeadLetter();
            if (!previewAlpha.equals(currentAlpha)){//不相等表示有新的字母项产生且为该类字母堆中的第一个字母索引项
                viewHolder.getView(R.id.first_alpha).setVisibility(View.VISIBLE);//把新的字母列表项设置VISIBlE
                TextView tv= viewHolder.getView(R.id.first_alpha);
                tv.setText(currentAlpha);
            }else {//表示没有新的字母堆出现,也就说明该item是属于同类字母堆中且不是第一个,那么就需要将这个索引项设置GONE
                viewHolder.getView(R.id.first_alpha).setVisibility(View.GONE);
            }
        }


    }
    public void setOnGetAlphaIndeserAndSectionListener(OnGetAlphaIndexerAndSectionsListener listener){
        this.listener=listener;
    }

    public interface OnGetAlphaIndexerAndSectionsListener{
        public void getAlphaIndexerAndSectionsListner(Map<String,Integer>alphaIndexer,List<String>sections);

    }
}
第七该ListView的Item布局:
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout android:layout_width="match_parent"  
  3.     android:layout_height="wrap_content"  
  4.     android:orientation="vertical"  
  5.     xmlns:android="http://schemas.android.com/apk/res/android">  
  6.     <TextView  
  7.         android:id="@+id/first_alpha"  
  8.         android:layout_width="match_parent"  
  9.         android:layout_height="wrap_content"  
  10.         android:textSize="14sp"  
  11.         android:padding="5dp"  
  12.         android:background="#cccccc"  
  13.         android:text="Z"  
  14.         android:gravity="center_vertical"  
  15.         android:paddingLeft="10dp"  
  16.         />  
  17.     <RelativeLayout  
  18.         android:layout_width="match_parent"  
  19.         android:layout_height="wrap_content"  
  20.         android:padding="8dp"  
  21.         >  
  22.   
  23.         <ImageView  
  24.             android:id="@+id/contact_icon_id"  
  25.             android:layout_width="45dp"  
  26.             android:layout_height="45dp"  
  27.             android:src="@mipmap/contact_user"  
  28.             android:layout_centerVertical="true"  
  29.             />  
  30.         <TextView  
  31.             android:id="@+id/contact_title"  
  32.             android:layout_width="wrap_content"  
  33.             android:layout_height="wrap_content"  
  34.             android:text="张三"  
  35.             android:textSize="18sp"  
  36.             android:textColor="#9d55b8"  
  37.             android:layout_toRightOf="@id/contact_icon_id"  
  38.             android:layout_marginLeft="20dp"  
  39.             />  
  40.         <TextView  
  41.             android:id="@+id/contact_phone_num"  
  42.             android:layout_width="wrap_content"  
  43.             android:layout_height="wrap_content"  
  44.             android:text="123456789"  
  45.             android:textColor="#ea8010"  
  46.             android:textSize="14sp"  
  47.             android:layout_below="@id/contact_title"  
  48.             android:layout_alignLeft="@id/contact_title"  
  49.             />  
  50.     </RelativeLayout>  
  51. </LinearLayout>  
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <TextView
        android:id="@+id/first_alpha"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="14sp"
        android:padding="5dp"
        android:background="#cccccc"
        android:text="Z"
        android:gravity="center_vertical"
        android:paddingLeft="10dp"
        />
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="8dp"
        >

        <ImageView
            android:id="@+id/contact_icon_id"
            android:layout_width="45dp"
            android:layout_height="45dp"
            android:src="@mipmap/contact_user"
            android:layout_centerVertical="true"
            />
        <TextView
            android:id="@+id/contact_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="张三"
            android:textSize="18sp"
            android:textColor="#9d55b8"
            android:layout_toRightOf="@id/contact_icon_id"
            android:layout_marginLeft="20dp"
            />
        <TextView
            android:id="@+id/contact_phone_num"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="123456789"
            android:textColor="#ea8010"
            android:textSize="14sp"
            android:layout_below="@id/contact_title"
            android:layout_alignLeft="@id/contact_title"
            />
    </RelativeLayout>
</LinearLayout>

第八整个Activity的实现:

  1. package com.mikyou.contactdemo;  
  2.   
  3. import android.content.Context;  
  4. import android.graphics.PixelFormat;  
  5. import android.os.Bundle;  
  6. import android.os.Handler;  
  7. import android.support.v7.app.AppCompatActivity;  
  8. import android.util.Log;  
  9. import android.view.LayoutInflater;  
  10. import android.view.View;  
  11. import android.view.ViewGroup;  
  12. import android.view.WindowManager;  
  13. import android.widget.ListView;  
  14. import android.widget.TextView;  
  15.   
  16. import com.mikyou.contactdemo.adapter.TestContactListAdapter;  
  17. import com.mikyou.contactdemo.bean.ContactBean;  
  18. import com.mikyou.contactdemo.myview.MikyouLetterListView;  
  19. import com.mikyou.contactdemo.service.ContactInfoService;  
  20.   
  21. import java.util.Collections;  
  22. import java.util.Comparator;  
  23. import java.util.List;  
  24. import java.util.Map;  
  25.   
  26. public class MainActivity extends AppCompatActivity implements TestContactListAdapter.OnGetAlphaIndexerAndSectionsListener{  
  27.     private List<ContactBean> mContactBeanList;//所有联系人集合  
  28.     private ListView mContactListView;//联系人ListView  
  29.     private MikyouLetterListView mLetterListView;//字母表  
  30.     private TextView overLayout;//弹出对话框  
  31.     private OverlayThread overlayThread;  
  32.     private Map<String, Integer> alphaIndexer;// 存放存在的汉语拼音首字母和与之对应的列表位置  
  33.     private Handler handler;  
  34.     private TestContactListAdapter adapter;  
  35.     private List<String> sections;// 存放存在的汉语拼音首字母  
  36.     @Override  
  37.     protected void onCreate(Bundle savedInstanceState) {  
  38.         super.onCreate(savedInstanceState);  
  39.         setContentView(R.layout.activity_main);  
  40.         initData();  
  41.         initView();  
  42.         initOverlay();  
  43.     }  
  44.   
  45.     private void initView() {  
  46.         registerAllViewIds();  
  47.   
  48.   
  49.         registerAllViewAdapters();  
  50.   
  51.   
  52.         registerAllViewEvents();  
  53.     }  
  54.   
  55.     private void registerAllViewIds() {  
  56.   mContactListView= (ListView) findViewById(R.id.id_listview);  
  57.         mLetterListView= (MikyouLetterListView) findViewById(R.id.id_letterview);  
  58.     }  
  59.   
  60.     private void registerAllViewAdapters() {  
  61.         adapter=new TestContactListAdapter(this,mContactBeanList,R.layout.contact_list_item);  
  62.         adapter.setOnGetAlphaIndeserAndSectionListener(this);  
  63.   
  64.         mContactListView.setAdapter(adapter);  
  65.   
  66.     }  
  67.   
  68.     private void registerAllViewEvents() {  
  69.         mLetterListView.setOnTouchingLetterChangedListener(new LetterListViewListener());  
  70.     }  
  71.   
  72.     private void initData() {  
  73.         //alphaIndexer=new HashMap<>();  
  74.         handler=new Handler();  
  75.         overlayThread=new OverlayThread();  
  76.         ContactInfoService mContactInfoService=new ContactInfoService(this);  
  77.         mContactBeanList=mContactInfoService.getContactList();//返回手机联系人对象集合  
  78.         //按拼音首字母表排序  
  79.         Collections.sort(mContactBeanList,comparator);  
  80.     }  
  81.   
  82.     @Override  
  83.     public void getAlphaIndexerAndSectionsListner(Map<String, Integer> alphaIndexer, List<String> sections) {  
  84.         this.alphaIndexer=alphaIndexer;  
  85.         this.sections=sections;  
  86.         Log.d("list",alphaIndexer.toString()+"\n"+sections.toString());  
  87.     }  
  88.   
  89.     /** 
  90.      * @Mikyou 
  91.      * 字母列表点击滑动监听器事件 
  92.      * */  
  93.     private class LetterListViewListener implements  
  94.             MikyouLetterListView.OnTouchingLetterChangedListener {  
  95.   
  96.         @Override  
  97.         public void onTouchingLetterChanged(final String s) {  
  98.             if (alphaIndexer.get(s) != null) {//判断当前选中的字母是否存在集合中  
  99.                 int position = alphaIndexer.get(s);//如果存在集合中则取出集合中该字母对应所在的位置,再利用对应的setSelection,就可以实现点击选中相应字母,然后联系人就会定位到相应的位置  
  100.                 mContactListView.setSelection(position);  
  101.                 overLayout.setText(s);  
  102.                 overLayout.setVisibility(View.VISIBLE);  
  103.                 handler.removeCallbacks(overlayThread);  
  104.                 // 延迟一秒后执行,让overlay为不可见  
  105.                 handler.postDelayed(overlayThread, 1500);  
  106.             }  
  107.         }  
  108.   
  109.     }  
  110.     /** 
  111.      * @mikyou 
  112.      * 初始化汉语拼音首字母弹出提示框 
  113.      * */  
  114.     private void initOverlay() {  
  115.         LayoutInflater inflater = LayoutInflater.from(this);  
  116.         overLayout = (TextView) inflater.inflate(R.layout.overlay, null);  
  117.         overLayout.setVisibility(View.INVISIBLE);  
  118.         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(  
  119.                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT,  
  120.                 WindowManager.LayoutParams.TYPE_APPLICATION,  
  121.                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE  
  122.                         | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,  
  123.                 PixelFormat.TRANSLUCENT);  
  124.         WindowManager windowManager =  
  125.                 (WindowManager) getSystemService(Context.WINDOW_SERVICE);  
  126.         windowManager.addView(overLayout, lp);  
  127.     }  
  128.     /** 
  129.      * @Mikyou 
  130.      * 首字母按a-z排序 
  131.      * */  
  132.     Comparator<ContactBean> comparator=new Comparator<ContactBean>() {  
  133.         @Override  
  134.         public int compare(ContactBean t1, ContactBean t2) {  
  135.             String a=t1.getFirstHeadLetter();  
  136.             String b=t2.getFirstHeadLetter();  
  137.             int flag=a.compareTo(b);  
  138.             if (flag==0){  
  139.                 return a.compareTo(b);  
  140.             }else{  
  141.                 return flag;  
  142.             }  
  143.         }  
  144.     };  
  145.     /** 
  146.      * @Mikyou 
  147.      * 设置overlay不可见 
  148.      * */  
  149.     private class OverlayThread implements Runnable{  
  150.   
  151.         @Override  
  152.         public void run() {  
  153.             overLayout.setVisibility(View.GONE);  
  154.         }  
  155.     }  
  156. }  
package com.mikyou.contactdemo;

import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ListView;
import android.widget.TextView;

import com.mikyou.contactdemo.adapter.TestContactListAdapter;
import com.mikyou.contactdemo.bean.ContactBean;
import com.mikyou.contactdemo.myview.MikyouLetterListView;
import com.mikyou.contactdemo.service.ContactInfoService;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

public class MainActivity extends AppCompatActivity implements TestContactListAdapter.OnGetAlphaIndexerAndSectionsListener{
    private List<ContactBean> mContactBeanList;//所有联系人集合
    private ListView mContactListView;//联系人ListView
    private MikyouLetterListView mLetterListView;//字母表
    private TextView overLayout;//弹出对话框
    private OverlayThread overlayThread;
    private Map<String, Integer> alphaIndexer;// 存放存在的汉语拼音首字母和与之对应的列表位置
    private Handler handler;
    private TestContactListAdapter adapter;
    private List<String> sections;// 存放存在的汉语拼音首字母
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initData();
        initView();
        initOverlay();
    }

    private void initView() {
        registerAllViewIds();


        registerAllViewAdapters();


        registerAllViewEvents();
    }

    private void registerAllViewIds() {
  mContactListView= (ListView) findViewById(R.id.id_listview);
        mLetterListView= (MikyouLetterListView) findViewById(R.id.id_letterview);
    }

    private void registerAllViewAdapters() {
        adapter=new TestContactListAdapter(this,mContactBeanList,R.layout.contact_list_item);
        adapter.setOnGetAlphaIndeserAndSectionListener(this);

        mContactListView.setAdapter(adapter);

    }

    private void registerAllViewEvents() {
        mLetterListView.setOnTouchingLetterChangedListener(new LetterListViewListener());
    }

    private void initData() {
        //alphaIndexer=new HashMap<>();
        handler=new Handler();
        overlayThread=new OverlayThread();
        ContactInfoService mContactInfoService=new ContactInfoService(this);
        mContactBeanList=mContactInfoService.getContactList();//返回手机联系人对象集合
        //按拼音首字母表排序
        Collections.sort(mContactBeanList,comparator);
    }

    @Override
    public void getAlphaIndexerAndSectionsListner(Map<String, Integer> alphaIndexer, List<String> sections) {
        this.alphaIndexer=alphaIndexer;
        this.sections=sections;
        Log.d("list",alphaIndexer.toString()+"\n"+sections.toString());
    }

    /**
     * @Mikyou
     * 字母列表点击滑动监听器事件
     * */
    private class LetterListViewListener implements
            MikyouLetterListView.OnTouchingLetterChangedListener {

        @Override
        public void onTouchingLetterChanged(final String s) {
            if (alphaIndexer.get(s) != null) {//判断当前选中的字母是否存在集合中
                int position = alphaIndexer.get(s);//如果存在集合中则取出集合中该字母对应所在的位置,再利用对应的setSelection,就可以实现点击选中相应字母,然后联系人就会定位到相应的位置
                mContactListView.setSelection(position);
                overLayout.setText(s);
                overLayout.setVisibility(View.VISIBLE);
                handler.removeCallbacks(overlayThread);
                // 延迟一秒后执行,让overlay为不可见
                handler.postDelayed(overlayThread, 1500);
            }
        }

    }
    /**
     * @mikyou
     * 初始化汉语拼音首字母弹出提示框
     * */
    private void initOverlay() {
        LayoutInflater inflater = LayoutInflater.from(this);
        overLayout = (TextView) inflater.inflate(R.layout.overlay, null);
        overLayout.setVisibility(View.INVISIBLE);
        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_APPLICATION,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
                PixelFormat.TRANSLUCENT);
        WindowManager windowManager =
                (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        windowManager.addView(overLayout, lp);
    }
    /**
     * @Mikyou
     * 首字母按a-z排序
     * */
    Comparator<ContactBean> comparator=new Comparator<ContactBean>() {
        @Override
        public int compare(ContactBean t1, ContactBean t2) {
            String a=t1.getFirstHeadLetter();
            String b=t2.getFirstHeadLetter();
            int flag=a.compareTo(b);
            if (flag==0){
                return a.compareTo(b);
            }else{
                return flag;
            }
        }
    };
    /**
     * @Mikyou
     * 设置overlay不可见
     * */
    private class OverlayThread implements Runnable{

        @Override
        public void run() {
            overLayout.setVisibility(View.GONE);
        }
    }
}
到这里就谈得差不多了,这个很常用,准备给自己以后的项目中继续用。
最后运行的结果(这个gif可能录不是很清楚,因为用的linux系统听热心网友说用recordmydesktop,但是经本人测试感觉使用起来很麻烦,而且效果不是很好,如果有小伙伴推荐一下在linux下的录制软件,请下面留言,将感激不尽):


Demo下载


备注:在使用过程中,由于自己没有加权限这种小失误,导致多花了一个小时在找出错上,一定要加权限: 

<!-- 读联系人权限 --> <uses-permission android:name="android.permission.READ_CONTACTS"/>

<!-- 写联系人权限 --> <uses-permissionandroid:name="android.permission.WRITE_CONTACTS" />

不然会报错:Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'boolean android.database.Cursor.moveToNext()' on a null object reference at com.example.administrator.customcontacts.ContactInfoService.getContactsList(ContactInfoService.java:36) at com.example.administrator.customcontacts.MainActivity.initData(MainActivity.java:46) at com.example.administrator.customcontacts.MainActivity.onCreate(MainActivity.java:37)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值