Android联系人中的ListView是做得比较独特的,但是源码写得比较复制,当我们想使用他的时候再从源码中提取,实属不易啊,而且容易出错,这几天,我把他提取出来了,写成一个简单的例子,一是给自己备忘,而是跟大家分享一下,好了,先来看看效果图: 
 首先是封装好的带头部的PinnedHeaderListView: 
 
public class PinnedHeaderListView extends ListView { 
public interface PinnedHeaderAdapter { 
public static final int PINNED_HEADER_GONE = 0; 
public static final int PINNED_HEADER_VISIBLE = 1; 
public static final int PINNED_HEADER_PUSHED_UP = 2; 
int getPinnedHeaderState(int position); 
void configurePinnedHeader(View header, int position, int alpha); 
} 
private static final int MAX_ALPHA = 255; 
private PinnedHeaderAdapter mAdapter; 
private View mHeaderView; 
private boolean mHeaderViewVisible; 
private int mHeaderViewWidth; 
private int mHeaderViewHeight; 
public PinnedHeaderListView(Context context) { 
super(context); 
} 
public PinnedHeaderListView(Context context, AttributeSet attrs) { 
super(context, attrs); 
} 
public PinnedHeaderListView(Context context, AttributeSet attrs, 
int defStyle) { 
super(context, attrs, defStyle); 
} 
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 
super.onLayout(changed, left, top, right, bottom); 
if (mHeaderView != null) { 
mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight); 
configureHeaderView(getFirstVisiblePosition()); 
} 
} 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
if (mHeaderView != null) { 
measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec); 
mHeaderViewWidth = mHeaderView.getMeasuredWidth(); 
mHeaderViewHeight = mHeaderView.getMeasuredHeight(); 
} 
} 
public void setPinnedHeaderView(View view) { 
mHeaderView = view; 
if (mHeaderView != null) { 
setFadingEdgeLength(0); 
} 
requestLayout(); 
} 
public void setAdapter(ListAdapter adapter) { 
super.setAdapter(adapter); 
mAdapter = (PinnedHeaderAdapter)adapter; 
} 
public void configureHeaderView(int position) { 
if (mHeaderView == null) { 
return; 
} 
int state = mAdapter.getPinnedHeaderState(position); 
switch (state) { 
case PinnedHeaderAdapter.PINNED_HEADER_GONE: { 
mHeaderViewVisible = false; 
break; 
} 
case PinnedHeaderAdapter.PINNED_HEADER_VISIBLE: { 
mAdapter.configurePinnedHeader(mHeaderView, position, MAX_ALPHA); 
if (mHeaderView.getTop() != 0) { 
mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight); 
} 
mHeaderViewVisible = true; 
break; 
} 
case PinnedHeaderAdapter.PINNED_HEADER_PUSHED_UP: { 
View firstView = getChildAt(0); 
int bottom = firstView.getBottom(); 
int headerHeight = mHeaderView.getHeight(); 
int y; 
int alpha; 
if (bottom < headerHeight) { 
y = (bottom - headerHeight); 
alpha = MAX_ALPHA * (headerHeight + y) / headerHeight; 
} else { 
y = 0; 
alpha = MAX_ALPHA; 
} 
mAdapter.configurePinnedHeader(mHeaderView, position, alpha); 
if (mHeaderView.getTop() != y) { 
mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight 
+ y); 
} 
mHeaderViewVisible = true; 
break; 
} 
} 
} 
protected void dispatchDraw(Canvas canvas) { 
super.dispatchDraw(canvas); 
if (mHeaderViewVisible) { 
drawChild(canvas, mHeaderView, getDrawingTime()); 
} 
} 
} 
 然后是旁边那个快速导航BladeView(刀锋): 
 
public class BladeView extends View { 
private OnItemClickListener mOnItemClickListener; 
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; 
private PopupWindow mPopupWindow; 
private TextView mPopupText; 
private Handler handler = new Handler(); 
public BladeView(Context context, AttributeSet attrs, int defStyle) { 
super(context, attrs, defStyle); 
} 
public BladeView(Context context, AttributeSet attrs) { 
super(context, attrs); 
} 
public BladeView(Context context) { 
super(context); 
} 
@Override 
protected void onDraw(Canvas canvas) { 
super.onDraw(canvas); 
if (showBkg) { 
canvas.drawColor(Color.parseColor("#00000000")); 
} 
int height = getHeight(); 
int width = getWidth(); 
int singleHeight = height / b.length; 
for (int i = 0; i < b.length; i++) { 
paint.setColor(Color.BLACK); 
paint.setTypeface(Typeface.DEFAULT_BOLD); 
paint.setFakeBoldText(true); 
paint.setAntiAlias(true); 
if (i == choose) { 
paint.setColor(Color.parseColor("#3399ff")); 
} 
float xPos = width / 2 - paint.measureText(b[i]) / 2; 
float yPos = singleHeight * i + singleHeight; 
canvas.drawText(b[i], xPos, yPos, paint); 
paint.reset(); 
} 
} 
@Override 
public boolean dispatchTouchEvent(MotionEvent event) { 
final int action = event.getAction(); 
final float y = event.getY(); 
final int oldChoose = choose; 
final int c = (int) (y / getHeight() * b.length); 
switch (action) { 
case MotionEvent.ACTION_DOWN: 
showBkg = true; 
if (oldChoose != c) { 
if (c > 0 && c < b.length) { 
performItemClicked(c); 
choose = c; 
invalidate(); 
} 
} 
break; 
case MotionEvent.ACTION_MOVE: 
if (oldChoose != c) { 
if (c > 0 && c < b.length) { 
performItemClicked(c); 
choose = c; 
invalidate(); 
} 
} 
break; 
case MotionEvent.ACTION_UP: 
showBkg = false; 
choose = -1; 
dismissPopup(); 
invalidate(); 
break; 
} 
return true; 
} 
private void showPopup(int item) { 
if (mPopupWindow == null) { 
handler.removeCallbacks(dismissRunnable); 
mPopupText = new TextView(getContext()); 
mPopupText.setBackgroundColor(Color.GRAY); 
mPopupText.setTextColor(Color.CYAN); 
mPopupText.setTextSize(50); 
mPopupText.setGravity(Gravity.CENTER_HORIZONTAL 
| Gravity.CENTER_VERTICAL); 
mPopupWindow = new PopupWindow(mPopupText, 100, 100); 
} 
String text = ""; 
if (item == 0) { 
text = "#"; 
} else { 
text = Character.toString((char) ('A' + item - 1)); 
} 
mPopupText.setText(text); 
if (mPopupWindow.isShowing()) { 
mPopupWindow.update(); 
} else { 
mPopupWindow.showAtLocation(getRootView(), 
Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL, 0, 0); 
} 
} 
private void dismissPopup() { 
handler.postDelayed(dismissRunnable, 800); 
} 
Runnable dismissRunnable = new Runnable() { 
@Override 
public void run() { 
// TODO Auto-generated method stub 
if (mPopupWindow != null) { 
mPopupWindow.dismiss(); 
} 
} 
}; 
public boolean onTouchEvent(MotionEvent event) { 
return super.onTouchEvent(event); 
} 
public void setOnItemClickListener(OnItemClickListener listener) { 
mOnItemClickListener = listener; 
} 
private void performItemClicked(int item) { 
if (mOnItemClickListener != null) { 
mOnItemClickListener.onItemClick(b[item]); 
showPopup(item); 
} 
} 
public interface OnItemClickListener { 
void onItemClick(String s); 
} 
} 
 接下来就是使用了,先在布局文件中声明activity_main.xml: 
 
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
xmlns:tools="http://schemas.android.com/tools" 
android:layout_width="match_parent" 
android:layout_height="match_parent" 
tools:context=".MainActivity" > 
<com.way.view.PinnedHeaderListView 
android:id="@+id/friends_display" 
android:layout_width="fill_parent" 
android:layout_height="fill_parent" 
android:cacheColorHint="#00000000" 
android:divider="@null" 
android:footerDividersEnabled="false" 
android:headerDividersEnabled="false" /> 
<com.way.view.BladeView 
android:id="@+id/friends_myletterlistview" 
android:layout_width="30dip" 
android:layout_height="fill_parent" 
android:layout_alignParentRight="true" 
android:background="#00000000" /> 
</RelativeLayout> 
 然后是一个独立Adapter,这次我没有作为内部类放在MainActivity中: 
 
public class FriendsAdapter extends BaseAdapter implements SectionIndexer, 
PinnedHeaderAdapter, OnScrollListener { 
private int mLocationPosition = -1; 
private String[] mDatas; 
// 首字母集 
private List<String> mFriendsSections; 
private List<Integer> mFriendsPositions; 
private LayoutInflater inflater; 
public FriendsAdapter(Context context,String[] datas, List<String> friendsSections, 
List<Integer> friendsPositions) { 
// TODO Auto-generated constructor stub 
inflater = LayoutInflater.from(context); 
mDatas = datas; 
mFriendsSections = friendsSections; 
mFriendsPositions = friendsPositions; 
} 
@Override 
public int getCount() { 
// TODO Auto-generated method stub 
return mDatas.length; 
} 
@Override 
public Object getItem(int position) { 
// TODO Auto-generated method stub 
return mDatas[position]; 
} 
@Override 
public long getItemId(int position) { 
// TODO Auto-generated method stub 
return position; 
} 
@Override 
public View getView(int position, View convertView, ViewGroup parent) { 
// TODO Auto-generated method stub 
int section = getSectionForPosition(position); 
if (convertView == null) { 
convertView = inflater.inflate(R.layout.listview_item, null); 
} 
LinearLayout mHeaderParent = (LinearLayout) convertView 
.findViewById(R.id.friends_item_header_parent); 
TextView mHeaderText = (TextView) convertView 
.findViewById(R.id.friends_item_header_text); 
if (getPositionForSection(section) == position) { 
mHeaderParent.setVisibility(View.VISIBLE); 
mHeaderText.setText(mFriendsSections.get(section)); 
} else { 
mHeaderParent.setVisibility(View.GONE); 
} 
TextView textView = (TextView) convertView 
.findViewById(R.id.friends_item); 
textView.setText(mDatas[position]); 
return convertView; 
} 
@Override 
public void onScrollStateChanged(AbsListView view, int scrollState) { 
// TODO Auto-generated method stub 
} 
@Override 
public void onScroll(AbsListView view, int firstVisibleItem, 
int visibleItemCount, int totalItemCount) { 
// TODO Auto-generated method stub 
if (view instanceof PinnedHeaderListView) { 
((PinnedHeaderListView) view).configureHeaderView(firstVisibleItem); 
} 
} 
@Override 
public int getPinnedHeaderState(int position) { 
int realPosition = position; 
if (realPosition < 0 
|| (mLocationPosition != -1 && mLocationPosition == realPosition)) { 
return PINNED_HEADER_GONE; 
} 
mLocationPosition = -1; 
int section = getSectionForPosition(realPosition); 
int nextSectionPosition = getPositionForSection(section + 1); 
if (nextSectionPosition != -1 
&& realPosition == nextSectionPosition - 1) { 
return PINNED_HEADER_PUSHED_UP; 
} 
return PINNED_HEADER_VISIBLE; 
} 
@Override 
public void configurePinnedHeader(View header, int position, int alpha) { 
// TODO Auto-generated method stub 
int realPosition = position; 
int section = getSectionForPosition(realPosition); 
String title = (String) getSections()[section]; 
((TextView) header.findViewById(R.id.friends_list_header_text)) 
.setText(title); 
} 
@Override 
public Object[] getSections() { 
// TODO Auto-generated method stub 
return mFriendsSections.toArray(); 
} 
@Override 
public int getPositionForSection(int section) { 
if (section < 0 || section >= mFriendsSections.size()) { 
return -1; 
} 
return mFriendsPositions.get(section); 
} 
@Override 
public int getSectionForPosition(int position) { 
// TODO Auto-generated method stub 
if (position < 0 || position >= getCount()) { 
return -1; 
} 
int index = Arrays.binarySearch(mFriendsPositions.toArray(), position); 
return index >= 0 ? index : -index - 2; 
} 
} 
 最后就是MainActivity中的处理了: 
 
public class MainActivity extends Activity { 
private static final String FORMAT = "^[a-z,A-Z].*$"; 
private PinnedHeaderListView mListView; 
private BladeView mLetter; 
private FriendsAdapter mAdapter; 
private String[] datas; 
// 首字母集 
private List<String> mSections; 
// 根据首字母存放数据 
private Map<String, List<String>> mMap; 
// 首字母位置集 
private List<Integer> mPositions; 
// 首字母对应的位置 
private Map<String, Integer> mIndexer; 
@Override 
protected void onCreate(Bundle savedInstanceState) { 
super.onCreate(savedInstanceState); 
setContentView(R.layout.activity_main); 
initData(); 
initView(); 
} 
private void initData() { 
datas = getResources().getStringArray(R.array.countries); 
mSections = new ArrayList<String>(); 
mMap = new HashMap<String, List<String>>(); 
mPositions = new ArrayList<Integer>(); 
mIndexer = new HashMap<String, Integer>(); 
for (int i = 0; i < datas.length; i++) { 
String firstName = datas[i].substring(0, 1); 
if (firstName.matches(FORMAT)) { 
if (mSections.contains(firstName)) { 
mMap.get(firstName).add(datas[i]); 
} else { 
mSections.add(firstName); 
List<String> list = new ArrayList<String>(); 
list.add(datas[i]); 
mMap.put(firstName, list); 
} 
} else { 
if (mSections.contains("#")) { 
mMap.get("#").add(datas[i]); 
} else { 
mSections.add("#"); 
List<String> list = new ArrayList<String>(); 
list.add(datas[i]); 
mMap.put("#", list); 
} 
} 
} 
Collections.sort(mSections); 
int position = 0; 
for (int i = 0; i < mSections.size(); i++) { 
mIndexer.put(mSections.get(i), position);// 存入map中,key为首字母字符串,value为首字母在listview中位置 
mPositions.add(position);// 首字母在listview中位置,存入list中 
position += mMap.get(mSections.get(i)).size();// 计算下一个首字母在listview的位置 
} 
} 
private void initView() { 
// TODO Auto-generated method stub 
mListView = (PinnedHeaderListView) findViewById(R.id.friends_display); 
mLetter = (BladeView) findViewById(R.id.friends_myletterlistview); 
mLetter.setOnItemClickListener(new OnItemClickListener() { 
@Override 
public void onItemClick(String s) { 
if (mIndexer.get(s) != null) { 
mListView.setSelection(mIndexer.get(s)); 
} 
} 
}); 
mAdapter = new FriendsAdapter(this, datas, mSections, mPositions); 
mListView.setAdapter(mAdapter); 
mListView.setOnScrollListener(mAdapter); 
mListView.setPinnedHeaderView(LayoutInflater.from(this).inflate( 
R.layout.listview_head, mListView, false)); 
} 
} 
 还有一个数据arrays.xml,我就不贴出来了,有兴趣的朋友可以
下载源码