手机通讯录实现A-Z字母排序的ListView检索
首先看下效果图,分析下效果图,
整体上来说分为两个部分,第一部分是最右侧的自定义View,第二部分就是ListView了。看效果图发现它的主要功能有:①展示通讯录 ②将通讯录按照首字母分类排序 ③滑动右边的字母的同时左边的ListView会跳到相应的位置等。通过这个demo将学到以下的知识点:
- 自定义控件的相关知识
- pinyin4j.jar的用法
- BaseAdapter的使用
废话不多说,下面我们来一步一步实现上面的效果。
1、右侧自定义View的实现
实现这样一个效果就是滑动最右边自定义的View,然后界面中间展示所滑到的字母。首先分析一下,我的想法是:
① 需要用canvas的draw方法”A-Z”画上去,怎么画上去呢?先计算每个字符所占的高度,然后通过改变Y坐标的值,沿竖直方向依次将字母绘画上去,在自定义View中通过这样的代码实现:
//计算每个字符所占的高度
float singleHeight = getHeight() / letter.length;
int width = getWidth();
for (int i=0;i<letter.length;i++)
{
String text = letter[i];
float xPosition = width/2-mPaint.measureText(text)/2;
//不断的改变Y坐标值
float yPosition = singleHeight*i+singleHeight;
//通过不断的改变yPosition将数组中的数据一个一个绘制到自定义的View中去
canvas.drawText(text,xPosition,yPosition,mPaint);
}
② 在滑动时如何通知Activity滑动到哪儿了?通过一个mOnSlidingListener监听,在滑动时将当前滑到的字母通过回调方式即用mOnSlidingListener.sliding ( letter[position] ) 传递给Activity,下面附上自定义View的代码:
/**重写了onTouchEvent方法,然后通过监听down,move,up事件来执行相关的操作,
* 当down时首先会改变整个view的背景色,
* 然后将当前滑到的字母通过回调的方式即调用mOnSlidingListener.sliding(letter[position])(
* 这里的mOnSlidingListener就是在activity中的setOnSlidingListener所注册的监听器)传递到Activity中*/
/**
* Created by WuTing on 2016/8/16.
*/
public class MyCharView extends View {
private Paint mPaint;
private boolean isShowBg = false;//用于区分是否显示view的背景
private OnSlidingListener mOnSlidingListener;//滑动此view的监听器
private int choose = -1;//用于标记当前所选中的位置
private TextView mTvDialog;//用于接收从activity中传过来的,中间用于展示字母的textView
//需要展示的数据
private String[] letter = { "A", "B", "C", "D",
"E", "F", "G", "H","I","J", "K", "L", "M", "N","O","P", "Q",
"R", "S", "T","W", "X", "Y", "Z" ,"#"};
/**构造方法*/
public MyCharView(Context context) {
this(context,null);//继承参数两个的方法
}
public MyCharView(Context context, AttributeSet attrs) {
this(context,attrs,0);//继承参数三个的方法
}
public MyCharView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaint();
}
private void initPaint() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextSize(26);
mPaint.setColor(Color.parseColor("#8c8c8c"));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//当此View被按下时所显示的背景颜色
if (isShowBg)
{
canvas.drawColor(Color.parseColor("#40000000"));
}
//计算每个字符所占的高度
float singleHeight = getHeight() / letter.length;
int width = getWidth();
for (int i=0;i<letter.length;i++)
{
String text = letter[i];
float xPosition = width/2-mPaint.measureText(text)/2;
//不断的改变Y坐标值
float yPosition = singleHeight*i+singleHeight;
//通过不断的改变yPosition将数组中的数据一个一个绘制到自定义的View中去
canvas.drawText(text,xPosition,yPosition,mPaint);
}
}
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
int position = (int) (event.getY()/getHeight()*letter.length);
int oldChoose = choose;
switch (action)
{
case MotionEvent.ACTION_DOWN:
isShowBg = true;
if (oldChoose != position && mOnSlidingListener!=null)
{
if (position > 0 && position < letter.length)
{
//将点中的字母变色
//将滑动的字母传递到activity中
mOnSlidingListener.sliding(letter[position]);
choose = position;
if (mTvDialog != null)
{
mTvDialog.setVisibility(VISIBLE);
mTvDialog.setText(letter[position]);
}
}
/** 请求重新绘制View,即重新调用draw()方法 */
invalidate();
}
break;
case MotionEvent.ACTION_MOVE:
isShowBg = true;
if (oldChoose != position && mOnSlidingListener != null) {
if (position >=0 && position < letter.length) {
mOnSlidingListener.sliding(letter[position]);
choose=position;
if(mTvDialog!=null){
mTvDialog.setVisibility(View.VISIBLE);
mTvDialog.setText(letter[position]);
}
}
invalidate();
}
break;
case MotionEvent.ACTION_UP:
isShowBg = false;
choose = -1;
if (mTvDialog!=null)
{
mTvDialog.setVisibility(GONE);
}
/** 请求重新绘制View,即重新调用draw()方法 */
invalidate();
break;
}
return true;
}
//MyLetterView的一个滑动的监听
public void setOnSlidingListener(OnSlidingListener mOnSlidingListener) {
this.mOnSlidingListener = mOnSlidingListener;
}
public interface OnSlidingListener {
public void sliding(String str);
}
public void setTextView(TextView tvDialog) {
mTvDialog=tvDialog;
}
}
布局文件 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<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">
<TextView
android:id="@+id/tv_dialog"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_centerInParent="true"
android:background="@mipmap/circle"
android:gravity="center"
android:textColor="#ffffffff"
android:textSize="30dp"
android:visibility="gone" />
<myWidget.MyCharView
android:id="@+id/my_letterview"
android:layout_width="25dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_marginRight="2dp"
android:layout_marginTop="7dp"/>
<ListView
android:id="@+id/ltv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/my_letterview"
android:scrollbars="none"
android:divider="#c1c1c1"
android:dividerHeight="0.5dp">
</ListView>
</RelativeLayout>
MainActivity代码:
public class MainActivity extends AppCompatActivity {
private MyCharView mMyCharView;
private TextView tvDialog;
private ListView ltv;
private ContactsAdapter mContactsAdapter;
private List<Contacts> allContactsList = new ArrayList<Contacts>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// getContacts();
initView();
initEvents();
}
private void initEvents() {
//将中间展示字母的TextView传递到myLetterView中,并在其中控制他的显示与隐藏
mMyCharView.setTextView(tvDialog);
//注册mMyLetterView中监听(跟setOnClickListener这种系统默认写好的监听一样只不过这里是我们自己写的)
//mContactsAdapter = new ContactsAdapter(this,allContactsList);
//Log.e("all Length",allContactsList.size()+"人");
//ltv.setAdapter(mContactsAdapter);
mMyCharView.setOnSlidingListener(new MyCharView.OnSlidingListener() {
@Override
public void sliding(String str) {
//if (mContactsAdapter.alphaIndexer.get(str) != null)
//{
//根据MyLetterView滑动到的数据获得ListView应该展示的位置
//int position = mContactsAdapter.alphaIndexer.get(str);
//将listView展示到相应的位置
//ltv.setSelection(position);
//}
tvDialog.setText(str);
}
});
}
private void initView() {
tvDialog = (TextView) findViewById(R.id.tv_dialog);
ltv = (ListView) findViewById(R.id.ltv);
mMyCharView = (MyCharView) findViewById(R.id.my_letterview);
}
在MAinActivity中可以看到MyCharView注册了在MyCharView中的监听,通过回调的方式将MyCharView中滑动到的文字传递给MainAcitivity中,并通过tvDialog显示在屏幕中间,它的效果图如下:
2、ListView数据的展示
数据展示,我的想法是:首先的到手机通讯录的内容,再得到每个通讯人名字的拼音,然后根据拼音将通讯录中的人名进行排序,排序后显示在ListView中,判断相邻的两个数据的拼音首字母是否相同,不同的话就显示tv_char(即起分隔作用的大写字母)。下面来一步一步实现listView的数据展示:
① 得到手机通讯录的内容,通过context.getContentResolver()就可以获取手机中的通讯录信息。GetContacts具体实现代码如下:
public class GetContacts{
public static List<Contacts> getContacts(Context context)
{
List<Contacts> mList = new ArrayList<Contacts>();
List<String> tokenList = new ArrayList<String>();
//context.getContentResolver()获取手机中的通讯录信息
Cursor cursor = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
String phoneNumber;
String phoneName;
while (cursor.moveToNext())
{
phoneNumber = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
phoneName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.SORT_KEY_ALTERNATIVE));
Contacts contacts = new Contacts();
contacts.setName(phoneName);
contacts.setNumber(phoneNumber);
//添加每个联系人信息到mList中
mList.add(contacts);
}
return mList;
}
}
需要构造一个实体类Contacts来存储联系人信息,Contacts的代码如下:
Contacts 实体类:
/**
*Contacts 联系人实体类
**/
public class Contacts {
String name;
String number;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
}
② 得到每个联系人的全拼和拼音首字母,这里我用的是pinyin4j.jar。(pinyin4j是一个支持将简体和繁体中文转换到成拼音的Java开源类库,pinyin4j的官方下载地址:http://sourceforge.net/projects/pinyin4j/files/ ,下载解压后lib中的pinyin4j-2.5.0.jar就是要用的pinyin4j的jar包)不多说,上代码:
GetHeadPin工具类:
import net.sourceforge.pinyin4j.PinyinHelper;
import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType;
import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat;
import net.sourceforge.pinyin4j.format.HanyuPinyinToneType;
import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType;
import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination;
/**
* Created by WuTing on 2016/8/17.
*/
public class GetHeadPin {
/**将汉字转换为全拼
* */
public static String getPinYin(String src)
{
char[] t1 = null;
t1 = src.toCharArray();
String[] t2 = new String[t1.length];
//设置汉字拼音输出的格式
HanyuPinyinOutputFormat t3 = new HanyuPinyinOutputFormat();
t3.setCaseType(HanyuPinyinCaseType.UPPERCASE);
t3.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
t3.setVCharType(HanyuPinyinVCharType.WITH_V);
String t4 = "";
int t0 = t1.length;
try {
for (int i=0;i<t0;i++)
{
//判断能否为汉字字符
System.out.println(t1[i]);
if (Character.toString(t1[i]).matches("[\\u4E00-\\u9FA5]+"))
{
t2 = PinyinHelper.toHanyuPinyinStringArray(t1[i], t3);// 将汉字的几种全拼都存到t2数组中
t4 += t2[0];// 取出该汉字全拼的第一种读音并连接到字符串t4后
} else {
// 如果不是汉字字符,间接取出字符并连接到字符串t4后
t4 += Character.toString(t1[i]);
}
}
}catch (BadHanyuPinyinOutputFormatCombination e)
{
e.printStackTrace();
}
return t4;
}
/**
* 提取每个汉字拼音的首字母
*
*/
public static String getPinYinHeadChar(String str)
{
String convert = "";
for (int j = 0;j<str.length();j++)
{
char word = str.charAt(0);
//提取汉字的首字母
String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(word);
if (pinyinArray != null)
{
convert = pinyinArray[0].charAt(0)+"";
}
else
{
convert = word+"";
}
}
return convert;
}
}
③ 按照拼音字母排序,其实就是用Java中的一个Collections.sort(allContactsList,comparator)排序函数的用法,comparator的代码如下:
/**
* 自定义的排序规则,按照A-Z进行排序
*/
@SuppressWarnings("rawtypes")
Comparator comparator = new Comparator<Contacts>() {
@Override
public int compare(Contacts lhs, Contacts rhs) {
String a = new GetHeadPin().getPinYin(lhs.getName()).substring(0, 1);
String b = new GetHeadPin().getPinYin(rhs.getName()).substring(0, 1);
int flag = a.compareTo(b);
if (flag == 0) {
return a.compareTo(b);
} else {
return flag;
}
}
};
④ 在ContactsAdapter适配器中判断相邻联系人拼音首字母是否相同,如果不相同的话,将布局中的展示字母的TextView展示出来,ContactsAdapter代码如下:
ContactsAdapter适配器:
public class ContactsAdapter extends BaseAdapter {
private Context mContext;
private List<Contacts> mNameList;//通讯录姓名列表集合
public HashMap<String,Integer> alphaIndexer = new HashMap<String,Integer>();//存放存在的汉语拼音首字母和与之对应的列表位置
private String[] sections;//存放存在的汉语拼音首字母
private ViewHolder mViewHolder;
public ContactsAdapter(Context context,List<Contacts> nameList) {
mNameList = nameList;
mContext = context;
sections = new String[mNameList.size()];
for (int i=0;i<mNameList.size();i++)
{
//当前汉语拼音首字母
String currentStr = new GetHeadPin().getPinYinHeadChar(new GetHeadPin().getPinYin(mNameList.get(i).getName()));
//上一个汉语拼音首字母,如果不存在为""
String previewStr = (i - 1)>= 0? new GetHeadPin().getPinYinHeadChar(new GetHeadPin().getPinYin(mNameList.get(i-1).getName())):"";
if (!previewStr.equals(currentStr))
{
String name = new GetHeadPin().getPinYinHeadChar(new GetHeadPin().getPinYin(mNameList.get(i).getName()));
alphaIndexer.put(name,i);
sections[i] = name;
}
}
}
@Override
public int getCount() {
return mNameList.size();
}
@Override
public Object getItem(int position) {
return mNameList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null)
{
mViewHolder = new ViewHolder();
convertView = View.inflate(mContext, R.layout.contacts_item,null);
mViewHolder.tv_alpha = (TextView) convertView.findViewById(R.id.tv_char);
mViewHolder.view_devide = (View) convertView.findViewById(R.id.view_devide);
mViewHolder.mNameTextView = (TextView) convertView.findViewById(R.id.txt_contact_name);
mViewHolder.mNumberTextView = (TextView) convertView.findViewById(R.id.txt_contact_number);
mViewHolder.ll_main = (LinearLayout) convertView.findViewById(R.id.ll_main);
convertView.setTag(mViewHolder);
}
else
{
mViewHolder = (ViewHolder) convertView.getTag();
}
if (position >= 1)
{
mViewHolder.mNameTextView.setText(mNameList.get(position).getName().toString());
mViewHolder.mNumberTextView.setText(mNameList.get(position).getNumber().toString());
mViewHolder.ll_main.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext,mNameList.get(position).toString(), Toast.LENGTH_SHORT).show();
}
});
//当前汉语拼音首字母
String currentStr = new GetHeadPin().getPinYinHeadChar(new GetHeadPin().getPinYin(mNameList.get(position).getName()));
//上一个汉语拼音首字母,如果不存在为""
String previewStr = (position - 1)>= 0? new GetHeadPin().getPinYinHeadChar(new GetHeadPin().getPinYin(mNameList.get(position-1).getName())):"";
//如果当前的条目的通讯录名字的拼音首字母 和 其前一条条目的名字首字母不相同,
// 则将布局中的展示字母的TextView展示出来
if (!previewStr.equals(currentStr))
{
mViewHolder.tv_alpha.setText(currentStr);
mViewHolder.tv_alpha.setVisibility(View.VISIBLE);
mViewHolder.view_devide.setVisibility(View.VISIBLE);
}
else
{
mViewHolder.tv_alpha.setVisibility(View.GONE);
mViewHolder.view_devide.setVisibility(View.GONE);
}
}
return convertView;
}
private class ViewHolder {
TextView tv_alpha;
View view_devide;
TextView mNameTextView;
TextView mNumberTextView;
LinearLayout ll_main;
}
}
最终的MainActivity代码:
public class MainActivity extends AppCompatActivity {
private MyCharView mMyCharView;
private TextView tvDialog;
private ListView ltv;
private ContactsAdapter mContactsAdapter;
private List<Contacts> allContactsList = new ArrayList<Contacts>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getAllContacts();
initView();
initEvents();
}
private void initEvents() {
//将中间展示字母的TextView传递到myLetterView中,并在其中控制他的显示与隐藏
mMyCharView.setTextView(tvDialog);
//注册mMyLetterView中监听(跟setOnClickListener这种系统默认写好的监听一样只不过这里是我们自己写的)
mContactsAdapter = new ContactsAdapter(this,allContactsList);
ltv.setAdapter(mContactsAdapter);
mMyCharView.setOnSlidingListener(new MyCharView.OnSlidingListener() {
@Override
public void sliding(String str) {
if (mContactsAdapter.alphaIndexer.get(str) != null)
{
//根据MyLetterView滑动到的数据获得ListView应该展示的位置
int position = mContactsAdapter.alphaIndexer.get(str);
//将listView展示到相应的位置
ltv.setSelection(position);
}
tvDialog.setText(str);
}
});
}
private void initView() {
tvDialog = (TextView) findViewById(R.id.tv_dialog);
ltv = (ListView) findViewById(R.id.ltv);
mMyCharView = (MyCharView) findViewById(R.id.my_letterview);
}
private void getAllContacts() {
allContactsList = GetContacts.getContacts(this);
Collections.sort(allContactsList,comparator);
}
/**
* 自定义的排序规则,按照A-Z进行排序
*/
@SuppressWarnings("rawtypes")
Comparator comparator = new Comparator<Contacts>() {
@Override
public int compare(Contacts lhs, Contacts rhs) {
String a = new GetHeadPin().getPinYin(lhs.getName()).substring(0, 1);
String b = new GetHeadPin().getPinYin(rhs.getName()).substring(0, 1);
int flag = a.compareTo(b);
if (flag == 0) {
return a.compareTo(b);
} else {
return flag;
}
}
};
}
源码地址
一只刚入门小菜鸟的第一篇文章,难免会有疏漏或者不恰当的地方。如果你有什么疑问,或者发现文章中的错误,欢迎批评指正,谢谢!