天天看點

淺談android中手機聯系人字母索引表的實作

本文轉載自:http://blog.csdn.net/u013064109/article/details/52013744 标簽: android手機聯系人字母索引表

實際上字母索引表的效果,可以說在現在的衆多APP中使用的非常流行,比如支付寶,微信中的聯系人,還有購物,買票的APP中選擇全國城市,切換城市的時候,這時候的城市也就是按照一個字母索引的順序來顯示,看起來是很友善的.其實這種字母索引表的效果最開始是出現在微信的聯系人中.因為覺得這種效果功能在今後的項目中可以說是非常常見,可能會用的上,是以準備來波部落格講述一下實作的原理,一來友善以後自己複習,二來如果能夠幫助一些android路上奮鬥小夥伴也是蠻有意義的.

下面我們先來看下效果圖,

淺談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實作浮動的字母索引項的清單.

[java] view plain copy print ?

淺談android中手機聯系人字母索引表的實作
淺談android中手機聯系人字母索引表的實作
  1. package com.mikyou.contactdemo.myview;  
  2. import android.content.Context;  
  3. import android.graphics.Canvas;  
  4. import android.graphics.Color;  
  5. import android.graphics.Paint;  
  6. import android.graphics.Typeface;  
  7. import android.util.AttributeSet;  
  8. import android.view.MotionEvent;  
  9. import android.view.View;  
  10. public class MikyouLetterListView extends View {  
  11.     private OnTouchingLetterChangedListener listener;  
  12.     //定義了顯示在最右邊的浮動的索引項的清單,當然這個是固定的,是以可以直接初始化,如果需要變動的話則可以通過自定義屬性來指定  
  13.     String[] b = {  "#","A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K",  
  14.             "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X",  
  15.             "Y", "Z"};  
  16.     int choose = -1;//用于标記點選存放字母數組中的下标  
  17.     Paint paint = new Paint();  
  18.     boolean showBkg = false;//這個boolean變量主要是控制當我們點選索引清單中整個索引清單的背景有個變化,為false表示開始沒點選背景為正常顯示時候的背景  
  19.     public MikyouLetterListView(Context context, AttributeSet attrs, int defStyle) {  
  20.         super(context, attrs, defStyle);  
  21.     }  
  22.     public MikyouLetterListView(Context context, AttributeSet attrs) {  
  23.         super(context, attrs);  
  24.     }  
  25.     public MikyouLetterListView(Context context) {  
  26.         super(context);  
  27.     }  
  28.     @Override  
  29.     protected void onDraw(Canvas canvas) {  
  30.         super.onDraw(canvas);  
  31.         if (showBkg) {//如果此時為true的話則表示需要改變整個canvas背景也即是索引項的背景  
  32.             canvas.drawColor(Color.parseColor("#10000000"));  
  33.         }  
  34.         int height = getHeight();  
  35.         int width = getWidth();  
  36.         //讓整個顯示的每個字母均分整個螢幕高度尺寸,這個singleHeight就是每個字母占據的高度  
  37.         int singleHeight = height / b.length;  
  38.         //周遊循環繪制每一個字母text  
  39.         for (int i = 0; i < b.length; i++) {  
  40.             //繪制字母text的顔色  
  41.             paint.setColor(Color.parseColor("#515151"));  
  42.             //繪制字母text的字型大小  
  43.             paint.setTextSize(25);  
  44.             //繪制字母text的字型樣式  
  45.             paint.setTypeface(Typeface.DEFAULT_BOLD);  
  46.             //設定抗鋸齒樣式  
  47.             paint.setAntiAlias(true);  
  48.             if (i == choose) {//判斷如果點選字母的下标等于i,那麼就會設定繪制點選字母的樣式用于高亮顯示  
  49.                 paint.setColor(Color.parseColor("#3399ff"));  
  50.                 paint.setFakeBoldText(true);  
  51.             }  
  52.             float xPos = width / 2 - paint.measureText(b[i]) / 2;//得到繪制字母text的起點的X坐标  
  53.             float yPos = singleHeight * i + singleHeight;//得到繪制字母text的起點的Y坐标  
  54.             canvas.drawText(b[i], xPos, yPos, paint);//開始繪制每個字母  
  55.             paint.reset();//繪制完一個字母需要重置一下畫筆對象  
  56.         }  
  57.     }  
  58.     @Override  
  59.     public boolean dispatchTouchEvent(MotionEvent event) {//重寫view的觸摸事件分發方法  
  60.         final int action = event.getAction();  
  61.         final float y = event.getY();//由于隻涉及到Y軸坐标,隻擷取y坐标  
  62.         final int oldChoose = choose;//oldChoose用于記錄上一次點選字母所在字母數組中的下标  
  63.         final int c = (int) (y / getHeight() * b.length);//得到點選或觸摸的位置進而确定對應點選或觸摸的字母所在字母數組中的下标  
  64.         switch (action) {  
  65.             case MotionEvent.ACTION_DOWN://監聽按下事件  
  66.                 showBkg = true;//按下後整個view的背景變色,showBkg為true  
  67.                 if (oldChoose != c && listener != null) {//如果此次點選的字母數組下标不等于上一次的且已經注冊了監聽事件的,  
  68.                     if (c >= 0 && c <= b.length) {//并且點選得到數組下标在字母數組範圍内的,我們就将此時的字母回調出去  
  69.                         listener.onTouchingLetterChanged(b[c]);//我們就将此時的對應在字母數組中的字母回調出去  
  70.                         choose = c;//并且更新目前選中的字母下标存儲在choose變量中  
  71.                         invalidate();//最後通知canvas重新繪制  
  72.                     }  
  73.                 }  
  74.                 break;  
  75.             case MotionEvent.ACTION_MOVE://監聽移動事件,因為按下的時候已經把背景showBkg設定true,這裡就不需要重新設定,其他操作與按下的事件一緻  
  76.                 if (oldChoose != c && listener != null) {  
  77.                     if (c >= 0 && c <= b.length) {  
  78.                         listener.onTouchingLetterChanged(b[c]);  
  79.                         choose = c;  
  80.                         invalidate();  
  81.                     }  
  82.                 }  
  83.                 break;  
  84.             case MotionEvent.ACTION_UP://監聽手指擡起的動作  
  85.                 showBkg = false;//此時的背景将會恢複到初始狀态,showBkg=false  
  86.                 choose = -1;//此時記錄下标的變量也需要重置  
  87.                 invalidate();//并且重繪整個view  
  88.                 break;  
  89.         }  
  90.         return true;  
  91.     }  
  92.     @Override  
  93.     public boolean onTouchEvent(MotionEvent event) {  
  94.         return super.onTouchEvent(event);  
  95.     }  
  96.     public void setOnTouchingLetterChangedListener(  
  97.             OnTouchingLetterChangedListener listener) {  
  98.         this.listener = listener;  
  99.     }  
  100.     public interface OnTouchingLetterChangedListener {  
  101.         public void onTouchingLetterChanged(String s);  
  102.     }  
  103. }  
淺談android中手機聯系人字母索引表的實作
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控件):

[html] view plain copy print ?

淺談android中手機聯系人字母索引表的實作
淺談android中手機聯系人字母索引表的實作
  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.     <solid android:color="#56abe4" />  
  20.     <stroke  
  21.         android:width="0.5dp"  
  22.         android:color="#56abe4" />  
  23.     <corners android:radius="15dp" />  
  24. </shape>  
淺談android中手機聯系人字母索引表的實作
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類:

[java] view plain copy print ?

淺談android中手機聯系人字母索引表的實作
淺談android中手機聯系人字母索引表的實作
  1. package com.mikyou.myguardian.bean;  
  2. import java.io.Serializable;  
  3. public class ContactBean implements Serializable {  
  4.     private int iconId;  
  5.     private String title;  
  6.     private String phoneNum;  
  7.     private String firstHeadLetter;  
  8.     public ContactBean(int iconId, String title, String phoneNum, String firstHeadLetter) {  
  9.         this.iconId = iconId;  
  10.         this.title = title;  
  11.         this.phoneNum = phoneNum;  
  12.         this.firstHeadLetter=firstHeadLetter;  
  13.     }  
  14.     public ContactBean() {  
  15.     }  
  16.     public int getIconId() {  
  17.         return iconId;  
  18.     }  
  19.     public String getFirstHeadLetter() {  
  20.         return firstHeadLetter;  
  21.     }  
  22.     public void setFirstHeadLetter(String firstHeadLetter) {  
  23.         this.firstHeadLetter = firstHeadLetter;  
  24.     }  
  25.     public void setIconId(int iconId) {  
  26.         this.iconId = iconId;  
  27.     }  
  28.     public String getTitle() {  
  29.         return title;  
  30.     }  
  31.     public void setTitle(String title) {  
  32.         this.title = title;  
  33.     }  
  34.     public String getPhoneNum() {  
  35.         return phoneNum;  
  36.     }  
  37.     public void setPhoneNum(String phoneNum) {  
  38.         this.phoneNum = phoneNum;  
  39.     }  
  40.     @Override  
  41.     public String toString() {  
  42.         return "ContactBean{" +  
  43.                 "iconId=" + iconId +  
  44.                 ", title='" + title + '\'' +  
  45.                 ", phoneNum='" + phoneNum + '\'' +  
  46.                 ", descriptor='" + descriptor + '\'' +  
  47.                 ", firstHeadLetter='" + firstHeadLetter + '\'' +  
  48.                 ", headLetterNum='" + headLetterNum + '\'' +  
  49.                 '}';  
  50.     }  
  51. }  
淺談android中手機聯系人字母索引表的實作
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:

[html] view plain copy print ?

淺談android中手機聯系人字母索引表的實作
淺談android中手機聯系人字母索引表的實作
  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>  
淺談android中手機聯系人字母索引表的實作
<?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。使用起來很友善):

[java] view plain copy print ?

淺談android中手機聯系人字母索引表的實作
淺談android中手機聯系人字母索引表的實作
  1. package com.mikyou.myguardian.service;  
  2. import android.content.ContentResolver;  
  3. import android.content.Context;  
  4. import android.database.Cursor;  
  5. import android.net.Uri;  
  6. import com.mikyou.myguardian.bean.ContactBean;  
  7. import java.util.ArrayList;  
  8. import java.util.List;  
  9. public class ContactInfoService {  
  10.     private Context context;  
  11.     public ContactInfoService(Context context) {  
  12.         this.context = context;  
  13.     }  
  14.     public List<ContactBean> getContactList(){  
  15.         List<ContactBean> mContactBeanList=new ArrayList<>();  
  16.         ContactBean mContactBean=null;  
  17.         ContentResolver mContentResolver=context.getContentResolver();  
  18.         Uri uri=Uri.parse("content://com.android.contacts/raw_contacts");  
  19.         Uri dataUri=Uri.parse("content://com.android.contacts/data");  
  20.         Cursor cursor =mContentResolver.query(uri,null,null,null,null);  
  21.         while (cursor.moveToNext()){  
  22.           mContactBean=new ContactBean();  
  23.             String id=cursor.getString(cursor.getColumnIndex("_id"));  
  24.             String title=cursor.getString(cursor.getColumnIndex("display_name"));//擷取聯系人姓名  
  25.             String firstHeadLetter=cursor.getString(cursor.getColumnIndex("phonebook_label"));//這個字段儲存了每個聯系人首字的拼音的首字母  
  26.             mContactBean.setTitle(title);  
  27.             mContactBean.setFirstHeadLetter(firstHeadLetter);  
  28.             Cursor dataCursor=mContentResolver.query(dataUri,null,"raw_contact_id= ?",new String[]{id},null);  
  29.             while(dataCursor.moveToNext()){  
  30.                 String type=dataCursor.getString(dataCursor.getColumnIndex("mimetype"));  
  31.                 if (type.equals("vnd.android.cursor.item/phone_v2")){//如果得到的mimeType類型為手機号碼類型才去接收  
  32.                     String phoneNum=dataCursor.getString(dataCursor.getColumnIndex("data1"));//擷取手機号碼  
  33.                     mContactBean.setPhoneNum(phoneNum);  
  34.                 }  
  35.             }  
  36.             dataCursor.close();  
  37.             if (mContactBean.getTitle()!=null&&mContactBean.getPhoneNum()!=null){  
  38.                 mContactBeanList.add(mContactBean);  
  39.             }  
  40.         }  
  41.         cursor.close();  
  42.         return mContactBeanList;  
  43.     }  
  44. }  
淺談android中手機聯系人字母索引表的實作
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集合就顯得尤為關鍵了,它可謂是浮動清單點選的字母與聯系人清單實作關聯的核心橋梁和媒介。

[java] view plain copy print ?

淺談android中手機聯系人字母索引表的實作
淺談android中手機聯系人字母索引表的實作
  1. 具體看該ListView的Adapter:  
  2. package com.mikyou.myguardian.adapter;  
  3. import android.content.Context;  
  4. import android.view.View;  
  5. import android.widget.ImageView;  
  6. import android.widget.TextView;  
  7. import com.mikyou.adapter.CommonAdapter;  
  8. import com.mikyou.myguardian.R;  
  9. import com.mikyou.myguardian.bean.ContactBean;  
  10. import com.mikyou.tools.ViewHolder;  
  11. import java.util.ArrayList;  
  12. import java.util.HashMap;  
  13. import java.util.List;  
  14. import java.util.Map;  
  15. public class TestContactListAdapter extends CommonAdapter<ContactBean> {  
  16.     private final int VIEW_TYPE=3;  
  17.     private Map<String,Integer> alphaIndexer;  
  18.     private List<String> sections;  
  19.     private List<ContactBean> listBeans;  
  20.     private OnGetAlphaIndexerAndSectionsListener listener;  
  21.     private boolean flag;//标志用于隻執行一次代碼  
  22.     public TestContactListAdapter(Context context, List<ContactBean> listBeans, int layoutId) {  
  23.         super(context, listBeans, layoutId);  
  24.         this.listBeans=listBeans;  
  25.         alphaIndexer=new HashMap<>();  
  26.         sections=new ArrayList<>();  
  27.         for (int i = 0; i <listBeans.size();i++) {  
  28.              //目前漢語拼音的首字母  
  29.             String currentAlpha=listBeans.get(i).getFirstHeadLetter();  
  30.             //上一個拼音的首字母,如果不存在則為""  
  31.             String previewAlpha=(i-1)>=0?listBeans.get(i-1).getFirstHeadLetter():"";  
  32.             if (!previewAlpha.equals(currentAlpha)){  
  33.                 String firstAlpha=listBeans.get(i).getFirstHeadLetter();  
  34.                 alphaIndexer.put(firstAlpha,i);  
  35.                 sections.add(firstAlpha);  
  36.             }  
  37.         }  
  38.     }  
  39.     @Override  
  40.     public int getItemViewType(int position) {  
  41.          int type=0;  
  42.         if (position==0){  
  43.             type=2;  
  44.         }else if (position==1){  
  45.             type=1;  
  46.         }  
  47.         return type;  
  48.     }  
  49.     @Override  
  50.     public int getCount() {  
  51.         //注意:為什麼沒有直接把回調方法的調用寫在構造器中呢?因為構造器隻會調用一次,當第一次調用listener的時候是為空的  
  52.         //而要初始化listener對象,則需要先去建立對象再去通過對象調用set方法來初始化這個listener對象,再去new對象的時候又要去用到listener産生了沖突  
  53.         //是以放在getCount中調用,隻會調用一次,符合需求  
  54.         if (!flag){  
  55.             if (listener!=null){  
  56.                 listener.getAlphaIndexerAndSectionsListner(alphaIndexer,sections);  
  57.             }  
  58.             flag=true;  
  59.         }  
  60.         return listBeans.size();  
  61.     }  
  62.     @Override  
  63.     public int getViewTypeCount() {  
  64.         return VIEW_TYPE;  
  65.     }  
  66.     @Override  
  67.     public void convert(ViewHolder viewHolder, ContactBean contactBean) {  
  68.         int viewType=getItemViewType(viewHolder.getmPosition());  
  69.         ImageView iv=viewHolder.getView(R.id.contact_icon_id);  
  70.         iv.setImageResource(R.mipmap.contact_user);  
  71.         viewHolder.setText(R.id.contact_title,contactBean.getTitle()).setText(R.id.contact_phone_num,contactBean.getPhoneNum());  
  72.         if (viewHolder.getmPosition()>=1){  
  73.             String currentAlpha=listBeans.get(viewHolder.getmPosition()).getFirstHeadLetter();  
  74.             String previewAlpha=listBeans.get(viewHolder.getmPosition()-1).getFirstHeadLetter();  
  75.             if (!previewAlpha.equals(currentAlpha)){//不相等表示有新的字母項産生且為該類字母堆中的第一個字母索引項  
  76.                 viewHolder.getView(R.id.first_alpha).setVisibility(View.VISIBLE);//把新的字母清單項設定VISIBlE  
  77.                 TextView tv= viewHolder.getView(R.id.first_alpha);  
  78.                 tv.setText(currentAlpha);  
  79.             }else {//表示沒有新的字母堆出現,也就說明該item是屬于同類字母堆中且不是第一個,那麼就需要将這個索引項設定GONE  
  80.                 viewHolder.getView(R.id.first_alpha).setVisibility(View.GONE);  
  81.             }  
  82.         }  
  83.     }  
  84.     public void setOnGetAlphaIndeserAndSectionListener(OnGetAlphaIndexerAndSectionsListener listener){  
  85.         this.listener=listener;  
  86.     }  
  87.     public interface OnGetAlphaIndexerAndSectionsListener{  
  88.         public void getAlphaIndexerAndSectionsListner(Map<String,Integer>alphaIndexer,List<String>sections);  
  89.     }  
  90. }  
淺談android中手機聯系人字母索引表的實作
具體看該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布局: [html] view plain copy print ?

淺談android中手機聯系人字母索引表的實作
淺談android中手機聯系人字母索引表的實作
  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.         <ImageView  
  23.             android:id="@+id/contact_icon_id"  
  24.             android:layout_width="45dp"  
  25.             android:layout_height="45dp"  
  26.             android:src="@mipmap/contact_user"  
  27.             android:layout_centerVertical="true"  
  28.             />  
  29.         <TextView  
  30.             android:id="@+id/contact_title"  
  31.             android:layout_width="wrap_content"  
  32.             android:layout_height="wrap_content"  
  33.             android:text="張三"  
  34.             android:textSize="18sp"  
  35.             android:textColor="#9d55b8"  
  36.             android:layout_toRightOf="@id/contact_icon_id"  
  37.             android:layout_marginLeft="20dp"  
  38.             />  
  39.         <TextView  
  40.             android:id="@+id/contact_phone_num"  
  41.             android:layout_width="wrap_content"  
  42.             android:layout_height="wrap_content"  
  43.             android:text="123456789"  
  44.             android:textColor="#ea8010"  
  45.             android:textSize="14sp"  
  46.             android:layout_below="@id/contact_title"  
  47.             android:layout_alignLeft="@id/contact_title"  
  48.             />  
  49.     </RelativeLayout>  
  50. </LinearLayout>  
淺談android中手機聯系人字母索引表的實作
<?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的實作:

[java] view plain copy print ?

淺談android中手機聯系人字母索引表的實作
淺談android中手機聯系人字母索引表的實作
  1. package com.mikyou.contactdemo;  
  2. import android.content.Context;  
  3. import android.graphics.PixelFormat;  
  4. import android.os.Bundle;  
  5. import android.os.Handler;  
  6. import android.support.v7.app.AppCompatActivity;  
  7. import android.util.Log;  
  8. import android.view.LayoutInflater;  
  9. import android.view.View;  
  10. import android.view.ViewGroup;  
  11. import android.view.WindowManager;  
  12. import android.widget.ListView;  
  13. import android.widget.TextView;  
  14. import com.mikyou.contactdemo.adapter.TestContactListAdapter;  
  15. import com.mikyou.contactdemo.bean.ContactBean;  
  16. import com.mikyou.contactdemo.myview.MikyouLetterListView;  
  17. import com.mikyou.contactdemo.service.ContactInfoService;  
  18. import java.util.Collections;  
  19. import java.util.Comparator;  
  20. import java.util.List;  
  21. import java.util.Map;  
  22. public class MainActivity extends AppCompatActivity implements TestContactListAdapter.OnGetAlphaIndexerAndSectionsListener{  
  23.     private List<ContactBean> mContactBeanList;//所有聯系人集合  
  24.     private ListView mContactListView;//聯系人ListView  
  25.     private MikyouLetterListView mLetterListView;//字母表  
  26.     private TextView overLayout;//彈出對話框  
  27.     private OverlayThread overlayThread;  
  28.     private Map<String, Integer> alphaIndexer;// 存放存在的漢語拼音首字母和與之對應的清單位置  
  29.     private Handler handler;  
  30.     private TestContactListAdapter adapter;  
  31.     private List<String> sections;// 存放存在的漢語拼音首字母  
  32.     @Override  
  33.     protected void onCreate(Bundle savedInstanceState) {  
  34.         super.onCreate(savedInstanceState);  
  35.         setContentView(R.layout.activity_main);  
  36.         initData();  
  37.         initView();  
  38.         initOverlay();  
  39.     }  
  40.     private void initView() {  
  41.         registerAllViewIds();  
  42.         registerAllViewAdapters();  
  43.         registerAllViewEvents();  
  44.     }  
  45.     private void registerAllViewIds() {  
  46.   mContactListView= (ListView) findViewById(R.id.id_listview);  
  47.         mLetterListView= (MikyouLetterListView) findViewById(R.id.id_letterview);  
  48.     }  
  49.     private void registerAllViewAdapters() {  
  50.         adapter=new TestContactListAdapter(this,mContactBeanList,R.layout.contact_list_item);  
  51.         adapter.setOnGetAlphaIndeserAndSectionListener(this);  
  52.         mContactListView.setAdapter(adapter);  
  53.     }  
  54.     private void registerAllViewEvents() {  
  55.         mLetterListView.setOnTouchingLetterChangedListener(new LetterListViewListener());  
  56.     }  
  57.     private void initData() {  
  58.         //alphaIndexer=new HashMap<>();  
  59.         handler=new Handler();  
  60.         overlayThread=new OverlayThread();  
  61.         ContactInfoService mContactInfoService=new ContactInfoService(this);  
  62.         mContactBeanList=mContactInfoService.getContactList();//傳回手機聯系人對象集合  
  63.         //按拼音首字母表排序  
  64.         Collections.sort(mContactBeanList,comparator);  
  65.     }  
  66.     @Override  
  67.     public void getAlphaIndexerAndSectionsListner(Map<String, Integer> alphaIndexer, List<String> sections) {  
  68.         this.alphaIndexer=alphaIndexer;  
  69.         this.sections=sections;  
  70.         Log.d("list",alphaIndexer.toString()+"\n"+sections.toString());  
  71.     }  
  72.     private class LetterListViewListener implements  
  73.             MikyouLetterListView.OnTouchingLetterChangedListener {  
  74.         @Override  
  75.         public void onTouchingLetterChanged(final String s) {  
  76.             if (alphaIndexer.get(s) != null) {//判斷目前選中的字母是否存在集合中  
  77.                 int position = alphaIndexer.get(s);//如果存在集合中則取出集合中該字母對應所在的位置,再利用對應的setSelection,就可以實作點選選中相應字母,然後聯系人就會定位到相應的位置  
  78.                 mContactListView.setSelection(position);  
  79.                 overLayout.setText(s);  
  80.                 overLayout.setVisibility(View.VISIBLE);  
  81.                 handler.removeCallbacks(overlayThread);  
  82.                 // 延遲一秒後執行,讓overlay為不可見  
  83.                 handler.postDelayed(overlayThread, 1500);  
  84.             }  
  85.         }  
  86.     }  
  87.     private void initOverlay() {  
  88.         LayoutInflater inflater = LayoutInflater.from(this);  
  89.         overLayout = (TextView) inflater.inflate(R.layout.overlay, null);  
  90.         overLayout.setVisibility(View.INVISIBLE);  
  91.         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(  
  92.                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT,  
  93.                 WindowManager.LayoutParams.TYPE_APPLICATION,  
  94.                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE  
  95.                         | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,  
  96.                 PixelFormat.TRANSLUCENT);  
  97.         WindowManager windowManager =  
  98.                 (WindowManager) getSystemService(Context.WINDOW_SERVICE);  
  99.         windowManager.addView(overLayout, lp);  
  100.     }  
  101.     Comparator<ContactBean> comparator=new Comparator<ContactBean>() {  
  102.         @Override  
  103.         public int compare(ContactBean t1, ContactBean t2) {  
  104.             String a=t1.getFirstHeadLetter();  
  105.             String b=t2.getFirstHeadLetter();  
  106.             int flag=a.compareTo(b);  
  107.             if (flag==0){  
  108.                 return a.compareTo(b);  
  109.             }else{  
  110.                 return flag;  
  111.             }  
  112.         }  
  113.     };  
  114.     private class OverlayThread implements Runnable{  
  115.         @Override  
  116.         public void run() {  
  117.             overLayout.setVisibility(View.GONE);  
  118.         }  
  119.     }  
  120. }  
淺談android中手機聯系人字母索引表的實作
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下的錄制軟體,請下面留言,将感激不盡):

淺談android中手機聯系人字母索引表的實作

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)