package com.lixu.letterlistview; import java.util.ArrayList;
import java.util.List;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import com.lixu.letterlistview.letter.LetterBaseListAdapter;
import com.lixu.letterlistview.letter.LetterListView;
import com.lixu.lianxirenlist.R;
import android.app.Activity;
import android.content.ContentResolver;
import android.database.Cursor;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView; public class MainActivity extends Activity {
private ArrayList<String> dataArray; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 通过获取手机通讯录的姓名
dataArray = new ArrayList<String>(); Uri uri = Uri.parse("content://com.android.contacts/contacts");
ContentResolver resolver = this.getContentResolver();
// 给query传递一个SORT_KEY_PRIMARY,让ContentResolver将获得的数据按照联系人名字首字母排序
Cursor cursor = resolver.query(uri, null, null, null,
android.provider.ContactsContract.Contacts.SORT_KEY_PRIMARY);
while (cursor.moveToNext()) {
// 联系人的id
String id = cursor.getString(cursor.getColumnIndex(android.provider.ContactsContract.Contacts._ID));
// 将联系人按姓名首字母分组
String sort_key_primary = cursor
.getString(cursor.getColumnIndex(android.provider.ContactsContract.Contacts.SORT_KEY_PRIMARY));
// 获取联系人的名字
String name = cursor
.getString(cursor.getColumnIndex(android.provider.ContactsContract.Contacts.DISPLAY_NAME));
dataArray.add(name);
} LetterListView letterListView = (LetterListView) findViewById(R.id.letterListView);
letterListView.setAdapter(new TestAdapter()); } /**
* 这里 使用一个简单的 NameValuePair 对象,做为测试
*
* @Title:
* @Description:
* @Author:Justlcw
* @Since:2014-5-13
* @Version:
*/
class TestAdapter extends LetterBaseListAdapter<NameValuePair> {
/** 字母对应的key,因为字母是要插入到列表中的,为了区别,所有字母的item都使用同一的key. **/
private static final String LETTER_KEY = "letter"; public TestAdapter() {
super(); List<NameValuePair> dataList = new ArrayList<NameValuePair>();
for (int i = 0; i < dataArray.size(); i++) {
NameValuePair pair = new BasicNameValuePair(String.valueOf(i), dataArray.get(i));
dataList.add(pair);
}
setContainerList(dataList);
} @Override
public Object getItem(int position) {
return list.get(position);
} @Override
public long getItemId(int position) {
return position;
} @Override
public String getItemString(NameValuePair t) {
return t.getValue();
} @Override
public NameValuePair create(char letter) {
return new BasicNameValuePair(LETTER_KEY, String.valueOf(letter));
} @Override
public boolean isLetter(NameValuePair t) {
// 判断是不是字母行,通过key比较,这里是NameValuePair对象,其他对象,就由你自己决定怎么判断了.
return t.getName().equals(LETTER_KEY);
} @Override
public View getLetterView(int position, View convertView, ViewGroup parent) {
// 这里是字母的item界面设置.
if (convertView == null) {
convertView = new TextView(MainActivity.this);
((TextView) convertView).setGravity(Gravity.CENTER_VERTICAL);
convertView.setBackgroundColor(getResources().getColor(android.R.color.white));
}
((TextView) convertView).setText(list.get(position).getValue());
((TextView) convertView).setBackgroundColor(Color.GREEN);
((TextView) convertView).setTextSize(25);
return convertView;
} @Override
public View getContainerView(int position, View convertView, ViewGroup parent) {
// 这里是其他正常数据的item界面设置.
if (convertView == null) {
convertView = new TextView(MainActivity.this);
((TextView) convertView).setGravity(Gravity.CENTER_VERTICAL);
}
((TextView) convertView).setText(list.get(position).getValue());
((TextView) convertView).setBackgroundColor(Color.YELLOW);
((TextView) convertView).setTextSize(20); return convertView;
}
}
}
package com.lixu.letterlistview.letter; import android.widget.BaseAdapter; /**
* 带有侧边字母列表的listView适配器
*
*@Title:
*@Description:
*@Author:Justlcw
*@Since:2014-5-8
*@Version:
*/
public abstract class LetterBaseAdapter extends BaseAdapter
{
/** 字母表头部 **/
protected static final char HEADER = '+';
/** 字母表尾部 **/
protected static final char FOOTER = '#'; /**
* 是否需要隐藏没有匹配到的字母
*
* @return true 隐藏, false 不隐藏
* @Description:
* @Author Justlcw
* @Date 2014-5-8
*/
public abstract boolean hideLetterNotMatch(); /**
* 获取字母对应的位置
*
* @return position
* @Description:
* @Author Justlcw
* @Date 2014-5-8
*/
public abstract int getIndex(char letter);
}
package com.lixu.letterlistview.letter; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup; /**
* 通用带有字母列表的泛型对象adapter
*
* @Title:
* @Description:
* @Author:Justlcw
* @Since:2014-5-9
* @Version:
*/
public abstract class LetterBaseListAdapter<T> extends LetterBaseAdapter {
/** log tag. **/
private static final String TAG = "LetterBaseListAdapter"; /** 默认错误头部字母. **/
private static final char ERROR_LETTER = ' '; /** view type的类型总数 **/
private static final int TYPE_COUNT = 2;
/** 字母类型 **/
private static final int TYPE_LETTER = 0;
/** 实体类型 **/
private static final int TYPE_CONTAINER = 1; /** 添加字母之后的list **/
protected final List<T> list;
/** 字母头位置标示map **/
private final Map<Character, Integer> letterMap; /**
* 构造方法
*/
public LetterBaseListAdapter() {
list = new ArrayList<T>();
letterMap = new HashMap<Character, Integer>();
} /**
* 构造方法
*
* @param dataArray
* 内容数组
*/
public LetterBaseListAdapter(T[] dataArray) {
this();
setContainerList(dataArray);
} /**
* 构造方法
*
* @param dataList
* 内容列表
*/
public LetterBaseListAdapter(List<T> dataList) {
this();
setContainerList(dataList);
} /**
* 设置主体内容
*
* @param dataArray
* 实体数组
* @Description:
* @Author Justlcw
* @Date 2014-5-9
*/
protected final void setContainerList(T[] dataArray) {
if (!list.isEmpty()) {
list.clear();
}
if (!letterMap.isEmpty()) {
letterMap.clear();
} char letter = ERROR_LETTER;
int index = 0;
for (int i = 0; i < dataArray.length; i++) {
T t = dataArray[i]; char l = getHeaderLetter(t); if (letter != l && l != ERROR_LETTER) {
// 如果发现这个字母没有添加过,更新一下标示
letter = l;
// 创建一个T类型的字母头放进去
T tl = create(letter);
if (tl != null) {
// 如果创建成功,则插入到列表中
list.add(tl);
}
// 存放最新字母对应的位置
letterMap.put(letter, index);
index++;
}
// 添加原本的填充实体项
list.add(t);
index++;
}
} /**
* 设置主体内容.
*
* @param dataList
* 实体列表
* @Description:
* @Author Justlcw
* @Date 2014-5-9
*/
protected final void setContainerList(List<T> dataList) {
if (!list.isEmpty()) {
list.clear();
}
if (!letterMap.isEmpty()) {
letterMap.clear();
} char letter = ' ';
int index = 0;
for (int i = 0; i < dataList.size(); i++) {
T t = dataList.get(i); char l = getHeaderLetter(t); if (letter != l && l != ERROR_LETTER) {
// 如果发现这个字母没有添加过,更新一下标示
letter = l;
// 创建一个T类型的字母头放进去
T tl = create(letter);
if (tl != null) {
// 如果创建成功,则插入到列表中
list.add(tl);
}
// 存放最新字母对应的位置
letterMap.put(letter, index);
index++;
}
// 添加原本的填充实体项
list.add(t);
index++;
}
} /**
* @param t
* <实体item对象>
*
* @return <实体item对象> 首字母, 获取失败返回 {@link #ERROR_LETTER}
* @Description:
* @Author Justlcw
* @Date 2014-5-12
*/
private char getHeaderLetter(T t) {
// 获取item对应的字符串
String str = getItemString(t);
// 如果为空,跳出继续
if (TextUtils.isEmpty(str)) {
Log.e(TAG, "item string empty in " + t.toString());
return ERROR_LETTER;
}
char l;
// 获取第一个字母
char firstChar = str.charAt(0);
if (firstChar == HEADER || firstChar == FOOTER || LetterUtil.isLetter(firstChar)) {
l = firstChar;// 如果是头,尾,字母,直接赋值
} else {
String[] letterArray = LetterUtil.getFirstPinyin(firstChar);
// 如果是汉字,取拼音首字母
if (letterArray != null && letterArray.length > 0) {
l = letterArray[0].charAt(0);
} else {
// 如果汉字转拼音失败了,跳过
Log.e(TAG, firstChar + " turn to letter fail, " + t.toString());
return ERROR_LETTER;
}
} // 如果是小写字母,转换为大写字母
if (l >= 'a') {
l = (char) (l - 32);
}
return l;
} @Override
public final int getCount() {
return list.size();
} @Override
public final View getView(int position, View convertView, ViewGroup parent) {
if (getItemViewType(position) == TYPE_LETTER) {
return getLetterView(position, convertView, parent);
}
return getContainerView(position, convertView, parent);
} @Override
public final int getItemViewType(int position) {
if (isLetter(list.get(position))) {
return TYPE_LETTER;
}
return TYPE_CONTAINER;
} @Override
public final int getViewTypeCount() {
return TYPE_COUNT;
} @Override
public boolean hideLetterNotMatch() {
return false;
} @Override
public final int getIndex(char letter) {
Integer index = letterMap.get(letter);
if (index == null) {
return -1;
}
return index;
} /**
* @param T
* <实体item对象>
*
* @return <实体item对象>对应的String,用来获取<拼音首字母>
* @Description:
* @Author Justlcw
* @Date 2014-5-9
*/
public abstract String getItemString(T t); /**
* @param letter
* <字母>
*
* @return 根据<字母>创建一个<实体item对象>,用来显示<字母item>
* @Description:
* @Author Justlcw
* @Date 2014-5-9
*/
public abstract T create(char letter); /**
* @param t
* <实体item对象>
*
* @return 根据<实体item对象>,判断是否是<字母item>
* @Description:
* @Author Justlcw
* @Date 2014-5-9
*/
public abstract boolean isLetter(T t); /**
* 返回 <字母item>界面,其他的同
* <P>
* {@link #getView(int, View, ViewGroup)}
*
* @Description:
* @Author Justlcw
* @Date 2014-5-9
*/
public abstract View getLetterView(int position, View convertView, ViewGroup parent); /**
* 返回<实体item>界面,其他的同
* <P>
* {@link #getView(int, View, ViewGroup)}
*
* @Description:
* @Author Justlcw
* @Date 2014-5-9
*/
public abstract View getContainerView(int position, View convertView, ViewGroup parent);
}
package com.lixu.letterlistview.letter; import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List; import com.lixu.lianxirenlist.R;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener; /**
* 带有字母列表的listView
*
* @Title:
* @Description:
* @Author:Justlcw
* @Since:2014-5-7
* @Version:
*/
public class LetterListView extends FrameLayout {
/** 隐藏字母消息 **/
private final int MSG_HIDE_LETTER = 0x0; /** 字母列表的宽度 **/
private final int LETTER_LIST_VIEW_WIDTH = 50;// TODO 这里宽度写死了,按着一定的比例比较好 /** 内容列表 **/
private ListView mListView;
/** 内容列表适配器 **/
private LetterBaseAdapter mAdapter; /** 字母列表 **/
private ListView mLetterListView;
private LetterAdapter mLetterAdapter; private TextView mLetterTextView; /** 字母消息Handler **/
private Handler mLetterhandler; /**
* 构造方法
*
* @param context
*/
public LetterListView(Context context) {
super(context);
initListView(context);
} /**
* 构造方法
*
* @param context
* @param attrs
*/
public LetterListView(Context context, AttributeSet attrs) {
super(context, attrs);
initListView(context);
} /**
* 初始化 内容列表 字母列表
*
* @Description:
* @Author Justlcw
* @Date 2014-5-7
*/
private void initListView(Context context) {
LayoutInflater inflater = LayoutInflater.from(getContext());
// TODO 这里添加内容列表,可以在这里对ListView进行一些你想要的设置
mListView = (ListView) inflater.inflate(R.layout.letter_list_container, null, false);
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
addView(mListView, lp); // TODO 这里添加字母列表,可以在这里对ListView进行一些你想要的设置
mLetterListView = (ListView) inflater.inflate(R.layout.letter_list_letter, null, false);
mLetterListView.setOnTouchListener(mLetterOnTouchListener);
LayoutParams letterListLp = new LayoutParams(LETTER_LIST_VIEW_WIDTH, LayoutParams.MATCH_PARENT, Gravity.RIGHT);
addView(mLetterListView, letterListLp); // TODO 这里对显示的字母进行设置
mLetterTextView = (TextView) inflater.inflate(R.layout.letter_list_position, null, false);
LayoutParams letterLp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER);
addView(mLetterTextView, letterLp);
mLetterTextView.setVisibility(View.INVISIBLE); // 初始化letter消息发送者
mLetterhandler = new LetterHandler(this);
} /**
* 设置内容列表适配器
*
* @param adapter
* {@link LetterBaseAdapter}
* @Description:
* @Author Justlcw
* @Date 2014-5-7
*/
public void setAdapter(LetterBaseAdapter adapter) {
if (adapter != null) {
mAdapter = adapter;
mListView.setAdapter(mAdapter);
}
} /**
* {@link AbsListView#setOnItemClickListener(OnItemClickListener)}
*
* @Description:
* @Author Justlcw
* @Date 2014-5-14
*/
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
mListView.setOnItemClickListener(onItemClickListener);
} @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh); mLetterAdapter = new LetterAdapter(h - getPaddingTop() - getPaddingBottom());
mLetterListView.setAdapter(mLetterAdapter);
} /**
* 显示字母
*
* @Description:
* @Author Justlcw
* @Date 2014-5-8
*/
private void showLetter(String letter) {
if (mLetterTextView.getVisibility() != View.VISIBLE) {
mLetterTextView.setVisibility(View.VISIBLE);
mLetterListView.setBackgroundResource(android.R.color.darker_gray);
}
mLetterTextView.setText(letter); mLetterhandler.removeMessages(MSG_HIDE_LETTER);
mLetterhandler.sendEmptyMessageDelayed(MSG_HIDE_LETTER, 500);
} /**
* 处理消息 {@link LetterHandler#handleMessage(Message)}
*
* @param msg
* 消息
* @Description:
* @Author Justlcw
* @Date 2014-5-8
*/
private void handleLetterMessage(Message msg) {
mLetterTextView.setVisibility(View.INVISIBLE);
mLetterListView.setBackgroundResource(android.R.color.white);
} /** 字母栏touch事件 **/
private View.OnTouchListener mLetterOnTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int height = (int) event.getY() - v.getTop(); int position = mLetterAdapter.getTouchPoistion(height);
if (position >= 0) {
char letter = (Character) mLetterAdapter.getItem(position);
// 显示字母
showLetter(String.valueOf(letter)); // 显示到字母对应的位置
int select = mAdapter.getIndex(letter);
if (select >= 0) {
mListView.setSelection(select);
}
return true;
}
return false;
}
}; /**
* 字母列表设配器
*
* @Title:
* @Description:
* @Author:Justlcw
* @Since:2014-5-7
* @Version:
*/
private class LetterAdapter extends BaseAdapter {
/** 字母表 **/
private static final String LETTER_STR = "+ABCDEFGHIJKLMNOPQRSTUVWXYZ#";
/** 最终显示的字母array **/
private char[] letterArray;
/** 每个字母的高度 **/
private int itemHeight; /**
* 构造方法
*
* @param height
* view height
*/
public LetterAdapter(int height) {
if (mAdapter.hideLetterNotMatch()) {
List<Character> list = new ArrayList<Character>();
char[] allArray = LETTER_STR.toCharArray();
for (int i = 0; i < allArray.length; i++) {
char letter = allArray[i];
int position = mAdapter.getIndex(letter);
if (position >= 0) {
list.add(letter);
}
}
letterArray = new char[list.size()];
for (int i = 0; i < list.size(); i++) {
letterArray[i] = list.get(i);
}
list.clear();
list = null;
} else {
letterArray = LETTER_STR.toCharArray();
}
itemHeight = height / letterArray.length;
} @Override
public int getCount() {
return letterArray.length;
} @Override
public Object getItem(int position) {
return letterArray[position];
} @Override
public long getItemId(int position) {
return position;
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = new TextView(getContext());
((TextView) convertView).setTextColor(getResources().getColor(android.R.color.black));
((TextView) convertView).setGravity(Gravity.CENTER);
AbsListView.LayoutParams lp = new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT,
itemHeight);
convertView.setLayoutParams(lp);
}
((TextView) convertView).setText(String.valueOf(letterArray[position])); return convertView;
} /**
* 获取touch的位置
*
* @return position
* @Description:
* @Author Justlcw
* @Date 2014-5-8
*/
public int getTouchPoistion(int touchHeight) {
int position = touchHeight / itemHeight;
if (position >= 0 && position < getCount()) {
return position;
}
return -1;
}
} /**
* 处理字母显示的handler.
*
* @Title:
* @Description:
* @Author:Justlcw
* @Since:2014-5-8
* @Version:
*/
private static class LetterHandler extends Handler {
/** 弱引用 {@link LetterListView} **/
private SoftReference<LetterListView> srLetterListView; /**
* 构造方法
*
* @param letterListView
* {@link LetterListView}
*/
public LetterHandler(LetterListView letterListView) {
srLetterListView = new SoftReference<LetterListView>(letterListView);
} @Override
public void handleMessage(Message msg) {
LetterListView letterListView = srLetterListView.get();
// 如果view没有被销毁掉,交给view处理这个消息
if (letterListView != null) {
letterListView.handleLetterMessage(msg);
}
}
}
}
package com.lixu.letterlistview.letter; import net.sourceforge.pinyin4j.PinyinHelper; /**
* 字母工具类
*@Title:
*@Description:
*@Author:Justlcw
*@Since:2014-5-8
*@Version:
*/
public class LetterUtil
{
/**
* @param chinese 一个汉字
* @return 拼音首字母
* @Description:
* @Author Justlcw
* @Date 2014-5-8
*/
public static String[] getFirstPinyin(char chinese)
{
return PinyinHelper.toHanyuPinyinStringArray(chinese);
} /**
* 是否是字母
*
* @return true 字母,false 非字母
* @Description:
* @Author Justlcw
* @Date 2014-5-8
*/
public static boolean isLetter(char c)
{
return (c >= 65 && c <= 90) || (c >= 97 && c <= 112);
}
}
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" > <com.lixu.letterlistview.letter.LetterListView
android:id="@+id/letterListView"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</com.lixu.letterlistview.letter.LetterListView> </RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:cacheColorHint="@android:color/transparent"
android:divider="@android:color/transparent"
android:dividerHeight="0dp"
android:scrollbars="none" />
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f44336"
android:cacheColorHint="@android:color/transparent"
android:divider="@android:color/transparent"
android:dividerHeight="0dp"
android:scrollbars="none" />
<?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="wrap_content"
android:background="#f44336"
android:gravity="center"
android:maxWidth="70dip"
android:minWidth="70dip"
android:padding="10dip"
android:textColor="@android:color/black"
android:textSize="50sp" />
运行效果图: