1. 程式人生 > >淺談android中手機聯絡人字母索引表的實現

淺談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實現浮動的字母索引項的列表.

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控制元件):
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類:
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:
<?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。使用起來很方便):
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集合就顯得尤為關鍵了,它可謂是浮動列表點選的字母與聯絡人列表實現聯動的核心橋樑和媒介。

具體看該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佈局:
<?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的實現:
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手機聯絡人字母索引實現

實際上字母索引表的效果,可以說在現在的眾多APP中使用的非常流行,比如支付寶,微信中的聯絡人,還有購物,買票的APP中選擇全國城市,切換城市的時候,這時候的城市也就是按照一個字母索引的順序來顯示,看起來是很方便的.其實這種字母索引表的效果最開始是出現在微信的聯絡人中.因為覺

Android的組播(多播)

-1 ip協議 strong 多個 接受 端口 ui線程 nbsp 數據 組播使用UDP對一定範圍內的地址發送相同的一組Packet,即一次可以向多個接受者發出信息,其與單播的主要區別是地址的形式。IP協議分配了一定範圍的地址空間給多播(多播只能使用這個範圍內

Android幸運快三平臺出租的meta-data及其應用

key 引用 平臺 name 如何 Coding pri sch xxxxx 在日常幸運快三平臺出租 haozbbs.com Q1446595067 的Android開發中,AndroidManifest中總會出現一些標簽,或是第三方SDK配置信息,或是系統配置,不禁讓人

Android獲取手機聯絡人匹配使用者並按字母A-Z排序展示

1、前言 最近在做公司專案的時候遇到一個新增手機聯絡人的需求,主要有以下幾個功能點: 讀取聯絡人:讀取使用者手機上的通訊錄裡的聯絡人列表 好友排序:按照拼音順序對好友進行排序,相容英文數字符號等 字母索引:右側字母導航條,既可拖動也可點選,聯動ListVi

AndroidAndroid的MVP

個人開發的微信小程式,目前功能是書籍推薦,後續會完善一些新功能,希望大家多多支援! 前言 為什麼使用MVP,網上有很多說法,最主要就是減輕了Activity的責任,相比於MVC中的Activity承擔的責任太多,因此有必要講講MVP。 MVP入門 在MVC框

Android的 Fragment、生命週期回撥方法 以及使用

        4onActivityCreated()              當Activity中的onCreate方法執行完後呼叫。 注意了:從這句官方的話可以看出:當執行onActivityCreated()的時候 activity的onCreate才剛完成。所以在onActivityCrea

Android的Handler機制

Handler是Android中提供的一種非同步回撥機制,也可以理解為執行緒間的訊息機制。為了避免ANR,我們通常會把一些耗時操作(比如:網路請求、I/O操作、複雜計算等)放到子執行緒中去執行,而當子執行緒需要修改UI時則子執行緒需要通知主執行緒去完成修改UI的

AndroidSerializable和Parcelable使用區別

Android中序列化有兩種方式:Serializable以及Parcelable。其中Serializable是Java自帶的,而Parcelable是安卓專有的。 一、Serializable序列化 serializable使用比較簡單,只需要對某個類實現Serializable 介面即可。 Ser

AndroidCallback(回撥)的使用

今天專案的Bug基本修改完成了,於是就對自己還未了解的回撥函式進行了學習。回撥其實就是在一定的時間裡做“一件事”,至於“這件事”具體做的是什麼不會管,只管做“這件事“,比如Boss叫員工去吃飯,但每個員工可能吃不同的食物。只不過,回撥是對介面而言。簡單來說就是,A物件呼叫

android載入高清大圖及圖片壓縮方式(二)

  這一講就是本系列的第二篇,一起來聊下關於android中載入高清大圖的問題,我們都知道如果我們直接載入原圖的話,一個是非常慢,需要等待一定時間,如果沒有在一定的時間內給使用者響應的話,將會極大影響使用者的體驗。另一個是如果你的手機記憶體小的話,可能會直接崩潰。這也就是直

Android的LOG檢視ANR(一)

轉自:http://yinger-fei.iteye.com/blog/1533788 手機中pull處理trace.txt 進去 data/anr 再pull 一:什麼是ANR                   ANR:Application Not Resp

android圖片處理之圖形變換特效Matrix(四)

今天,我們就來談下android中圖片的變形的特效,在上講部落格中我們談到android中圖片中的色彩特效來實現的。改變它的顏色主要通過ColorMatrix類來實現。 現在今天所講的圖片變形的特效主要就是通過Matrix類來實現,我們通過上篇部落格知道,改變色彩特效,主要

Android的MVVM模式

大家好啊,我是kele。眾所周知,Android的設計模式主要有三個:MVC,MVP,MVVM。今天主要來談一下MVVM模式,簡單說明它的好處以及它和MVP在實現方面的區別。 DataBinding android { ....

Android的非同步載入之ListView圖片的快取及優化三

     隔了很久沒寫部落格,現在必須快速脈動回來。今天我還是接著上一個多執行緒中的非同步載入系列中的最後一個使用非同步載入實現ListView中的圖片快取及其優化。具體來說這次是一個綜合Demo.但是個人覺得裡面還算有點價值的就是裡面的圖片的快取的實現。因為老實說它確實能

android圖片處理之色彩特效處理ColorMatrix(三)

在android開發中對圖片處理很是頻繁,其中對圖片的顏色處理就是很常見的一種。我們經常看到一些類似美圖秀秀,美顏相機的app,為什麼那麼黑的人拍出來是確實那麼地白呢?長的那麼那個(醜)的人,用美顏相機拍出來的看起來也有那麼回事(拍出來就感覺挺漂亮)。就像網上有個段子,有錢

android僅僅使用一個TextView實現高仿京東,淘寶各種倒計時

  今天給大家帶來的是僅僅使用一個TextView實現一個高仿京東、淘寶、唯品會等各種電商APP的活動倒計時。最近公司一直加班也沒來得及時間去整理,今天難得休息想把這個分享給大家,只求共同學習,以及自己後續的複習。為什麼會想到使用一個TextView來實現呢?因為最近公司在

AndroidMVP模式與MVC模式的區別

一、概述 對於MVP(Model View Presenter),大多數人都能說出一二:“MVC的演化版本”,“讓Model和View完全解耦”等等。本篇博文僅是為了做下記錄,提出一些自己的看法,和幫助大家如何針對一個Activity頁面去編寫針對MVP風

android的ListView之解決ScrollView和ListView巢狀衝突(實際上一切都是浮雲,閒的蛋疼)(一)

     相信大家都已經可以熟練使用ListView和GridView,大神們估計都在使用RecyclerView了。如果還在使用ListView,你肯定有這樣的一個深刻的感受,那就是在做一個APP的時候使用ListView和GridView很頻繁,並且經常會遇到一個頁面中

Mybatissession的一級快取的實現原理

最近由於受工作中業務需要和現有工程中dao層非orm思想的影響,覺得在有些業務場景下,並不一定非要去使用ORM框架,畢竟寫大量的實體類也是一件麻煩的事,於是著手編寫一個非ORM框架。初步完成後,底層的session並沒能像mybatis那樣能支援session的一級快取