1、概述
最近由于项目需求,需要做一个查看手机通讯录,并且取出相应的数据。类似于下图:
用到的一个主要的知识点:SectionIndexer——能够有效地帮助我们对分组进行控制,由于SectionIndexer是一个接口,你可以自定义一个子类来实现SectionIndexer,
不过自己再写一个SectionIndexer的实现太麻烦了,这里我们直接使用Android提供好
的实现AlphabetIndexer,用它来实现联系人分组功能已经足够了。AlphabetIndexer的构造函数需要传入三个参数,第一个参数是cursor,
第二个参数是sortedColumnIndex整型,第三个参数是alphabet字符串。
其中cursor就是把我们从数据库中查出的游标传进去,
sortedColumnIndex就是指明我们是使用哪一列进行排序的,
而alphabet则是指定字母表排序规则,比如:"ABCDEFGHIJKLMNOPQRSTUVWXYZ"。
有了AlphabetIndexer,我们就可以通过它的getPositionForSection和getSectionForPosition方法,
找出当前位置所在的分组,和当前分组所在的位置,
从而实现类似于系统联系人的分组导航和挤压动画效果。
2、效果图
大家可以清晰的看到,滑动界面,右侧的指示也会随着页面的变换而变换,
按住右侧,是按照字母来查询,由于这里是使用的genymotion,不能输入中文汉字,
所以都是英文的联系人,中文同样的效果。点击查询,字母的显示条就会消失,
这里使用的是模糊查询,只要名字中有你输入的关键字都会显示出来。
3、实现
下面我们就来开始实现,新建一个Android项目,命名为Contacts。首先我们还是先来完成布局文件,打开或新建activity_main.xml作为程序的主布局文件,代码会长一点,由于要实现右侧的A-Z的滑动栏,中间有一大部分的代码是类似的,这里就不贴出来了,感兴趣的朋友可以下载整个项目。
然后新建一个contact_item.xml的布局,这个布局用于在ListView中的每一行进行填充,代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#ffffff">
<LinearLayout
android:id="@+id/sort_key_layout"
android:layout_width="fill_parent"
android:layout_height="18dip"
android:layout_marginRight="15dp"
android:background="#64a300">
<TextView
android:id="@+id/sort_key"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="10dip"
android:textColor="#ffffff"
android:textSize="13sp" />
</LinearLayout>
<LinearLayout
android:id="@+id/name_layout"
android:layout_width="fill_parent"
android:layout_height="60dip"
android:orientation="vertical">
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:textColor="#303030"
android:layout_marginLeft="15dp"
android:text="李三"
android:textSize="20sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:background="#4064a300" />
</LinearLayout>
</LinearLayout>
在这个布局文件中,首先是放入了一个和前面完成一样的分组布局,因为不仅界面头部需要展示分组,在每个分组内的第一个无素之前都需要展示分组布局。然后是加入一个简单的LinearLayout,包含一个TextView用于显示联系人姓名。
这样我们的布局文件就全部写完了,下面开始来真正地实现功能。
先从简单的开始,新建一个PlayerInfo实体类:
package com.example.contactsdemo;
import java.io.Serializable;
/*
* 用户信息
* auth:liyachao
* date:2015/4/6
*/
public class PlayerInfo {
private String playerName;
private String playerPhone;
/**
* 排序字母
*/
private String sortKey;
public String getSortKey() {
return sortKey;
}
public void setSortKey(String sortKey) {
this.sortKey = sortKey;
}
public String getPlayerPhone() {
return playerPhone;
}
public void setPlayerPhone(String playerPhone) {
this.playerPhone = playerPhone;
}
public String getPlayerName() {
return playerName;
}
public void setPlayerName(String playerName) {
this.playerName = playerName;
}
}
这个实体类很简单,只包含了联系人姓名、排序键和联系人电话。
接下来完成联系人列表适配器的编写,新建一个ContactAdapter类继承自ArrayAdapter,加入如下代码:
package com.example.contactsdemo;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.SectionIndexer;
import android.widget.TextView;
import java.util.List;
/**
* 联系人列表适配器。
*
* @author guolin
*/
public class ContactAdapter extends BaseAdapter {
/**
* 需要渲染的item布局文件
*/
private int resource;
private Context context;
private List<PlayerInfo> players;
private boolean flag = true;
/**
* 字母表分组工具
*/
private SectionIndexer mIndexer;
public ContactAdapter(Context context, int textViewResourceId, List<PlayerInfo> players) {
resource = textViewResourceId;
this.context = context;
this.players = players;
}
@Override
public int getCount() {
return players.size();
}
@Override
public PlayerInfo getItem(int position) {
return players.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
public void dataChanged(List<PlayerInfo> players) {
this.players = players;
notifyDataSetChanged();
flag = false;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
PlayerInfo contact = getItem(position);
LinearLayout layout = null;
if (convertView == null) {
layout = (LinearLayout) LayoutInflater.from(context).inflate(resource, null);
} else {
layout = (LinearLayout) convertView;
}
TextView name = (TextView) layout.findViewById(R.id.name);
LinearLayout sortKeyLayout = (LinearLayout) layout.findViewById(R.id.sort_key_layout);
TextView sortKey = (TextView) layout.findViewById(R.id.sort_key);
name.setText(contact.getPlayerName());
if (flag) {
int section = mIndexer.getSectionForPosition(position);
if (position == mIndexer.getPositionForSection(section)) {
sortKey.setText(contact.getSortKey());
sortKeyLayout.setVisibility(View.VISIBLE);
} else {
sortKeyLayout.setVisibility(View.GONE);
}
} else {
sortKeyLayout.setVisibility(View.GONE);
}
return layout;
}
/**
* 给当前适配器传入一个分组工具。
*
* @param indexer
*/
public void setIndexer(SectionIndexer indexer) {
mIndexer = indexer;
flag = true;
}
}
上面的代码中,最重要的就是getView方法,在这个方法中,我们首先判断是查找类型还是滑动页面类型,如果是查找类型,我们将头部的指示条就隐藏,如果不是再做判断,使用SectionIndexer的getSectionForPosition方法,通过当前的position值拿到了对应的section值,然后再反向通过刚刚拿到的section值,调用getPositionForSection方法,取回新的position值。如果当前的position值和新的position值是相等的,那么我们就可以认为当前position的项是某个分组下的第一个元素,我们应该将分组布局显示出来。
最后我们来编写程序的主界面,打开或新建MainActivity作为程序的主界面,代码如下所示:
package com.example.contactsdemo;
import java.util.ArrayList;
import java.util.List;
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.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AlphabetIndexer;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
/**
* chang date:2015/4/5 by liyachao
*/
public class MainActivity extends Activity {
private ListView lv;
private EditText edittext;
/**
* 联系人总人数
*/
private int contactNumber;
/**
* 分组的布局
*/
private LinearLayout titleLayout;
/**
* 分组上显示的字母
*/
private TextView title;
private ArrayList<PlayerInfo> allPlayerInfos;
/**
* 联系人列表适配器
*/
private ContactAdapter adapter;
/**
* 用于进行字母表分组
*/
private AlphabetIndexer indexer;
private RelativeLayout sectionToastLayout;
private TextView sectionToastText;
private LinearLayout alphabetLayout;
/**
* A-Z的集合
*/
private List<TextView> alphabetList;
/**
* 定义字母表的排序规则
*/
private String alphabet = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private int lastFirstVisibleItem = -1;
/**
* 电话号码*
*/
private static final int PHONES_NUMBER_INDEX = 1;
/**
* 联系人显示名称*
*/
private static final int PHONES_DISPLAY_NAME_INDEX = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setControl();
}
private void setControl() {
alphabetList = new ArrayList<TextView>();
setAlphabetData();
titleLayout = (LinearLayout) findViewById(R.id.title_layout1);
title = (TextView) findViewById(R.id.title);
sectionToastLayout = (RelativeLayout) findViewById(R.id.section_toast_layout);
sectionToastText = (TextView) findViewById(R.id.section_toast_text);
alphabetLayout = (LinearLayout) findViewById(R.id.alphabet_layout);
lv = (ListView) findViewById(R.id.lv_select_contact);
edittext = (EditText) findViewById(R.id.edittext);
allPlayerInfos = getContactInfos();
adapter = new ContactAdapter(this, R.layout.contact_item, allPlayerInfos);
edittext.setHint("搜索" + contactNumber + "位联系人");
adapter.setIndexer(indexer);
if (allPlayerInfos.size() > 0) {
setupContactsListView();
setAlpabetListener();
}
/**
* 向listview设置点击事件
*/
lv.setOnItemClickListener(new MyOnItemClickListener());
/**
* 向edittext设置监听事件
*/
edittext.addTextChangedListener(new MyTextWatcher());
}
/**
* 根据填写的关键字在电话簿里寻找相关信息
*
* @param name
* @return
*/
private ArrayList<PlayerInfo> searchItem(String name) {
ArrayList<PlayerInfo> mSearchList = new ArrayList<PlayerInfo>();
for (int i = 0; i < allPlayerInfos.size(); i++) {
int index = allPlayerInfos.get(i).getPlayerName().indexOf(name);
// 存在匹配的数据
if (index != -1) {
mSearchList.add(allPlayerInfos.get(i));
}
}
return mSearchList;
}
/**
* 获取系统的所有的联系人信息.
*
* @return
*/
public ArrayList<PlayerInfo> getContactInfos() {
allPlayerInfos = new ArrayList<PlayerInfo>();
ContentResolver resolver = getContentResolver();
Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
Cursor phoneCursor = resolver.query(uri,
new String[]{Phone.DISPLAY_NAME, Phone.NUMBER, Phone.SORT_KEY_PRIMARY}
, null, null, Phone.SORT_KEY_PRIMARY);
if (phoneCursor.moveToFirst()) {
do {
//得到手机号码
String phoneNumber = phoneCursor.getString(PHONES_NUMBER_INDEX);
String sortKey = getSortKey(phoneCursor.getString(2));
//得到联系人名称
String contactName = phoneCursor.getString(PHONES_DISPLAY_NAME_INDEX);
PlayerInfo playerInfo = new PlayerInfo();
playerInfo.setPlayerName(contactName);
playerInfo.setPlayerPhone(phoneNumber);
playerInfo.setSortKey(sortKey);
allPlayerInfos.add(playerInfo);
} while (phoneCursor.moveToNext());
}
contactNumber = allPlayerInfos.size();
startManagingCursor(phoneCursor);
indexer = new AlphabetIndexer(phoneCursor, 2, alphabet);
return allPlayerInfos;
}
/**
* 获取sort key的首个字符,如果是英文字母就直接返回,否则返回#。
*
* @param sortKeyString 数据库中读取出的sort key
* @return 英文字母或者#
*/
private String getSortKey(String sortKeyString) {
alphabetLayout.getHeight();
String key = sortKeyString.substring(0, 1).toUpperCase();
if (key.matches("[A-Z]")) {
return key;
}
return "#";
}
/**
* 为联系人ListView设置监听事件,
* 根据当前的滑动状态来改变分组的显示位置,
* 从而实现挤压动画的效果。
* auth:liyachao
*/
private void setupContactsListView() {
lv.setAdapter(adapter);
lv.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
int section = indexer.getSectionForPosition(firstVisibleItem);
int nextSecPosition = indexer.getPositionForSection(section + 1);
setSortAlphabet(section);
if (firstVisibleItem != lastFirstVisibleItem) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) titleLayout.getLayoutParams();
params.topMargin = 0;
titleLayout.setLayoutParams(params);
title.setText(String.valueOf(alphabet.charAt(section)));
}
if (nextSecPosition == firstVisibleItem + 1) {
View childView = view.getChildAt(0);
if (childView != null) {
int titleHeight = titleLayout.getHeight();
int bottom = childView.getBottom();
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) titleLayout
.getLayoutParams();
if (bottom < titleHeight) {
float pushedDistance = bottom - titleHeight;
params.topMargin = (int) pushedDistance;
titleLayout.setLayoutParams(params);
} else {
if (params.topMargin != 0) {
params.topMargin = 0;
titleLayout.setLayoutParams(params);
}
}
}
}
lastFirstVisibleItem = firstVisibleItem;
}
});
}
/**
* 设置默认的字幕背景和字体颜色
*/
private void setSortAlphabet(int section) {
TextView tv;
for (int i = 0; i < 27; i++) {
if (section == i) {
tv = alphabetList.get(section);
tv.setTextColor(Color.parseColor("#ffffff"));
tv.setBackgroundResource(R.drawable.text_bg_frame);
} else {
tv = alphabetList.get(i);
tv.setTextColor(Color.parseColor("#303030"));
tv.setBackgroundColor(Color.parseColor("#ffffff"));
}
tv.setGravity(Gravity.CENTER);
}
}
/**
* 设置字母表上的触摸事件,根据当前触摸的位置结合字母表的高度,
* 计算出当前触摸在哪个字母上。
* 当手指按在字母表上时,展示弹出式分组。手指离开字母表时,
* 将弹出式分组隐藏。
* auth:liyachao
*/
private void setAlpabetListener() {
alphabetLayout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
float alphabetHeight = alphabetLayout.getHeight();
float y = event.getY();
int sectionPosition = (int) ((y / alphabetHeight) / (1f / 27f));
if (sectionPosition < 0) {
sectionPosition = 0;
} else if (sectionPosition > 26) {
sectionPosition = 26;
}
String sectionLetter = String.valueOf(alphabet.charAt(sectionPosition));
int position = indexer.getPositionForSection(sectionPosition);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
alphabetList.get(sectionPosition).setTextColor(Color.parseColor("#ffffff"));
alphabetList.get(sectionPosition).setBackgroundResource(R.drawable.text_bg_frame);
sectionToastLayout.setVisibility(View.VISIBLE);
sectionToastText.setText(sectionLetter);
lv.setSelection(position);
break;
case MotionEvent.ACTION_MOVE:
setSortAlphabet(sectionPosition);
sectionToastText.setText(sectionLetter);
lv.setSelection(position);
break;
default:
setSortAlphabet(sectionPosition);
sectionToastLayout.setVisibility(View.GONE);
}
return true;
}
});
}
class MyOnItemClickListener implements OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String name = ((PlayerInfo) parent.getItemAtPosition(position)).getPlayerName() + "";
String phone = ((PlayerInfo) parent.getItemAtPosition(position)).getPlayerPhone() + "";
String str = name + "的电话为: " + phone;
Toast.makeText(MainActivity.this, str, Toast.LENGTH_LONG).show();
}
}
class MyTextWatcher implements TextWatcher {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
String str = s.toString();
if (str.equals("")) {
adapter.dataChanged(allPlayerInfos);
adapter.setIndexer(indexer);
titleLayout.setVisibility(View.VISIBLE);
} else {
Log.i("tag", "name111: " + str);
ArrayList<PlayerInfo> temp;
temp = searchItem(str.trim());
adapter.dataChanged(temp);
titleLayout.setVisibility(View.GONE);
}
lv.setAdapter(adapter);
}
}
private void setAlphabetData() {
TextView jing = (TextView) findViewById(R.id.contact_);
alphabetList.add(jing);
TextView A = (TextView) findViewById(R.id.contact_A);
alphabetList.add(A);
TextView B = (TextView) findViewById(R.id.contact_B);
alphabetList.add(B);
TextView C = (TextView) findViewById(R.id.contact_C);
alphabetList.add(C);
TextView D = (TextView) findViewById(R.id.contact_D);
alphabetList.add(D);
TextView E = (TextView) findViewById(R.id.contact_E);
alphabetList.add(E);
TextView F = (TextView) findViewById(R.id.contact_F);
alphabetList.add(F);
TextView G = (TextView) findViewById(R.id.contact_G);
alphabetList.add(G);
TextView H = (TextView) findViewById(R.id.contact_H);
alphabetList.add(H);
TextView I = (TextView) findViewById(R.id.contact_I);
alphabetList.add(I);
TextView J = (TextView) findViewById(R.id.contact_J);
alphabetList.add(J);
TextView K = (TextView) findViewById(R.id.contact_K);
alphabetList.add(K);
TextView L = (TextView) findViewById(R.id.contact_L);
alphabetList.add(L);
TextView M = (TextView) findViewById(R.id.contact_M);
alphabetList.add(M);
TextView N = (TextView) findViewById(R.id.contact_N);
alphabetList.add(N);
TextView O = (TextView) findViewById(R.id.contact_O);
alphabetList.add(O);
TextView P = (TextView) findViewById(R.id.contact_P);
alphabetList.add(P);
TextView Q = (TextView) findViewById(R.id.contact_Q);
alphabetList.add(Q);
TextView R1 = (TextView) findViewById(R.id.contact_R);
alphabetList.add(R1);
TextView S = (TextView) findViewById(R.id.contact_S);
alphabetList.add(S);
TextView T = (TextView) findViewById(R.id.contact_T);
alphabetList.add(T);
TextView U = (TextView) findViewById(R.id.contact_U);
alphabetList.add(U);
TextView V = (TextView) findViewById(R.id.contact_V);
alphabetList.add(V);
TextView W = (TextView) findViewById(R.id.contact_W);
alphabetList.add(W);
TextView X = (TextView) findViewById(R.id.contact_X);
alphabetList.add(X);
TextView Y = (TextView) findViewById(R.id.contact_Y);
alphabetList.add(Y);
TextView Z = (TextView) findViewById(R.id.contact_Z);
alphabetList.add(Z);
}
}
最后要记住,要在
AndroidManifest.xml给出读取手机联系人的权限声明:
<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>