- 先上效果图
-
实现思路
- 异步加载联系人
```java private List<BeanContact> getBaseContactList(Context context) { List<BeanContact> kanjiStartBeanContact = new ArrayList<BeanContact>(); HashMap<String, BeanContact> kanjiStartBeanContactHashMap = new HashMap<String, BeanContact>(); List<BeanContact> nonKanjiStartBeanContact = new ArrayList<BeanContact>(); HashMap<String, BeanContact> nonKanjiStartBeanContactHashMap = new HashMap<String, BeanContact>(); List<BeanContact> beanContactList = new ArrayList<BeanContact>(); BeanContact cs = null; Cursor cursor = null; String sortkey = null; long startLoadTime = System.currentTimeMillis(); String[] projection = new String[]{ContactsContract.CommonDataKinds.Phone.CONTACT_ID, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER}; try { cursor = context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, null, null, "sort_key"); int idColumnIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.CONTACT_ID); int dispalyNameColumnIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME); int numberColumnIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER); while (cursor.moveToNext()) { String id = cursor.getString(idColumnIndex); String displayName = cursor.getString(dispalyNameColumnIndex); String phoneNumber = cursor.getString(numberColumnIndex); boolean kanjiStartBeanContactExist = kanjiStartBeanContactHashMap.containsKey(id); boolean nonKanjiStartBeanContactExist = nonKanjiStartBeanContactHashMap.containsKey(id); if (true == kanjiStartBeanContactExist) { cs = kanjiStartBeanContactHashMap.get(id); cs.addMultipleContact(cs, phoneNumber); } else if (true == nonKanjiStartBeanContactExist) { cs = nonKanjiStartBeanContactHashMap.get(id); cs.addMultipleContact(cs, phoneNumber); } else { cs = new BeanContact(id, displayName, phoneNumber); PinyinUtil.parse(cs.getmNamePinyinSearchUnit()); sortkey = PinyinUtil.getSortKey(cs.getmNamePinyinSearchUnit()).toUpperCase(); cs.setmSortKey(praseSortKey(sortkey)); boolean isKanji = PinyinUtil.isKanji(cs.getmName().charAt(0)); if (true == isKanji) { kanjiStartBeanContactHashMap.put(id, cs); } else { nonKanjiStartBeanContactHashMap.put(id, cs); } } } } catch (Exception e) { e.printStackTrace(); } finally { if (null != cursor) { cursor.close(); cursor = null; } } kanjiStartBeanContact.addAll(kanjiStartBeanContactHashMap.values()); Collections.sort(kanjiStartBeanContact, BeanContact.mAscComparator); nonKanjiStartBeanContact.addAll(nonKanjiStartBeanContactHashMap.values()); Collections.sort(nonKanjiStartBeanContact, BeanContact.mAscComparator); beanContactList.addAll(kanjiStartBeanContact); //merge nonKanjiStartBeanContact and kanjiStartBeanContact int lastIndex = 0; boolean shouldBeAdd = false; for (int i = 0; i < nonKanjiStartBeanContact.size(); i++) { String nonKanfirstLetter = PinyinUtil.getFirstLetter(nonKanjiStartBeanContact.get(i).getmNamePinyinSearchUnit()); int j = 0; for (j = 0 + lastIndex; j < beanContactList.size(); j++) { String firstLetter = PinyinUtil.getFirstLetter(beanContactList.get(j).getmNamePinyinSearchUnit()); lastIndex++; if (firstLetter.charAt(0) > nonKanfirstLetter.charAt(0)) { shouldBeAdd = true; break; } else { shouldBeAdd = false; } } if (lastIndex >= beanContactList.size()) { lastIndex++; shouldBeAdd = true; } if (true == shouldBeAdd) { beanContactList.add(j, nonKanjiStartBeanContact.get(i)); shouldBeAdd = false; } } long endLoadTime = System.currentTimeMillis(); Log.i(TAG, "endLoadTime-startLoadTime=[" + (endLoadTime - startLoadTime) + "] BeanContact.size()=" + beanContactList.size()); return beanContactList;
}
```
2 . 解析基础联系人list生成Map用于映射26个英文字母和对应的联系人list,这样就可以实现这种效果用于更精准定位联系人
public class ContactIndexHelper {
private HashMap<String, List<BeanContact>> hashMap;//映射对应字母的联系人集合
private static ContactIndexHelper helper;
public HashMap<String, List<BeanContact>> getHashMap() {
return hashMap;
}
public void setHashMap(HashMap<String, List<BeanContact>> hashMap) {
this.hashMap = hashMap;
}
private ContactIndexHelper() {
hashMap = new HashMap<>();
for (int i = 0; i < QuickAlphabeticBar.getSelectCharacters().length; i++) {
hashMap.put(String.valueOf(QuickAlphabeticBar.getSelectCharacters()[i]), new ArrayList<BeanContact>());
}
}
public static ContactIndexHelper getInstance() {
if (helper == null) {
helper = new ContactIndexHelper();
}
return helper;
}
public void parseContact(List<BeanContact> list) {
for (int i = 0; i < list.size(); i++) {
hashMap.get(String.valueOf(list.get(i).getmSortKey().charAt(0))).add(list.get(i));
}
}
}
//当滑动右侧quickBar的时候根据手指滑动到的字母获取到对应的联系人集合,然后再次进行过滤获取不重复的联系人
public void updateUi(String letter) {
textView.setText(letter);
List<BeanContact> tempList = ContactIndexHelper.getInstance().getHashMap().get(letter);//获取对应字母的联系人集合
List<BeanContact> resultList = new ArrayList<>();
if (tempList.size() > 0 && tempList.get(0) != null) {
resultList.add(tempList.get(0));
}
for (int i = 1; i < tempList.size(); i++) {//过滤这个联系人list,得到姓不重复的新联系人list
if (!String.valueOf(tempList.get(i).getmName().charAt(0)).equalsIgnoreCase(String.valueOf(tempList.get(i - 1).getmName().charAt(0)))) {
resultList.add(tempList.get(i));
}
}
quickAdapter.clear();
quickAdapter.addAll(resultList);
}
```
3 . 自定义右侧qucikBar
```java
public class QuickAlphabeticBar extends View {
private static final String TAG = "QuickAlphabeticBar";
public static final char DEFAULT_INDEX_CHARACTER = '#';
private static char[] mSelectCharacters = {DEFAULT_INDEX_CHARACTER, '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'};
private SectionIndexer mSectionIndexer;
private ListView mQuickAlphabeticLv;
private TextView mSelectCharTv;
private char mCurrentSelectChar;
private OnQuickAlphabeticBar onQuickAlphabeticBar;
interface OnQuickAlphabeticBar {
abstract void onTouchDown();//手指按下
abstract void onTouchUp();//手指抬起
}
public OnQuickAlphabeticBar getOnQuickAlphabeticBar() {
return onQuickAlphabeticBar;
}
public void setOnQuickAlphabeticBar(OnQuickAlphabeticBar onQuickAlphabeticBar) {
this.onQuickAlphabeticBar = onQuickAlphabeticBar;
}
public QuickAlphabeticBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public static char[] getSelectCharacters() {
return mSelectCharacters;
}
/*public static void setSelectCharacters(char[] mSelectCharacters) {
QuickAlphabeticBar.mSelectCharacters = mSelectCharacters;
}*/
public SectionIndexer getSectionIndexer() {
return mSectionIndexer;
}
public void setSectionIndexer(SectionIndexer sectionIndexer) {
mSectionIndexer = sectionIndexer;
}
public ListView getQuickAlphabeticLv() {
return mQuickAlphabeticLv;
}
public void setQuickAlphabeticLv(ListView quickAlphabeticLv) {
mQuickAlphabeticLv = quickAlphabeticLv;
}
public TextView getSelectCharTv() {
return mSelectCharTv;
}
public void setSelectCharTv(TextView selectCharTv) {
mSelectCharTv = selectCharTv;
}
public char getCurrentSelectChar() {
return mCurrentSelectChar;
}
public void setCurrentSelectChar(char currentSelectChar) {
mCurrentSelectChar = currentSelectChar;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
int index = getCurrentIndex(event);
//Log.i(TAG,"index="+index);
if ((event.getAction() == MotionEvent.ACTION_DOWN) || (event.getAction() == MotionEvent.ACTION_MOVE)) {
setBackgroundColor(ContextCompat.getColor(getContext(), R.color.gray));
//reference: http://blog.csdn.net/jack_l1/article/details/14165291
if (null != mSectionIndexer) {
int position = mSectionIndexer.getPositionForSection(mSelectCharacters[index]);
if (position < 0) {
return true;
}
if (null != mSelectCharTv) { //show select char
mSelectCharTv.setVisibility(View.VISIBLE);
mSelectCharTv.setText(String.valueOf(mSelectCharacters[index]));
}
if (null != mQuickAlphabeticLv) {
mQuickAlphabeticLv.setSelection(position);
}
if (onQuickAlphabeticBar != null) {
onQuickAlphabeticBar.onTouchDown();
}
}
} else if (event.getAction() == MotionEvent.ACTION_UP) {
setBackgroundColor(ContextCompat.getColor(getContext(), android.R.color.transparent));
if (null != mSelectCharTv) { //hide select char
mSelectCharTv.setVisibility(View.GONE);
}
if (onQuickAlphabeticBar != null) {
onQuickAlphabeticBar.onTouchUp();
}
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
float xPos = getMeasuredWidth() / 2;
float yPos = 0;
if (mSelectCharacters.length > 0) {
float sigleHeight = getMeasuredHeight() / mSelectCharacters.length;
for (int i = 0; i < mSelectCharacters.length; i++) {
yPos = (i + 1) * sigleHeight;
if (mSelectCharacters[i] == mCurrentSelectChar) {
RectF rectF = new RectF(xPos - sigleHeight / 3, yPos - sigleHeight / 3, xPos + sigleHeight / 3, yPos + sigleHeight / 3);
canvas.drawRect(rectF, mBluePaint);
Paint.FontMetricsInt fontMetrics = mCurrentIndexPaint.getFontMetricsInt();
// 转载请注明出处:http://blog.csdn.net/hursing
int baseline = ((int) rectF.bottom + (int) rectF.top - fontMetrics.bottom - fontMetrics.top) / 2;//让文字竖直居中可以看下textview源码
canvas.drawText(String.valueOf(mSelectCharacters[i]), rectF.centerX(), baseline, mCurrentIndexPaint);
} else {
RectF rectF = new RectF(xPos - sigleHeight / 3, yPos - sigleHeight / 3, xPos + sigleHeight / 3, yPos + sigleHeight / 3);
Paint.FontMetricsInt fontMetrics = mCurrentIndexPaint.getFontMetricsInt();
int baseline = ((int) rectF.bottom + (int) rectF.top - fontMetrics.bottom - fontMetrics.top) / 2;
canvas.drawText(String.valueOf(mSelectCharacters[i]), rectF.centerX(), baseline, mOtherIndexPaint);
}
}
}
super.onDraw(canvas);
}
private int getCurrentIndex(MotionEvent event) {
if (null == event) {
return 0;
}
int y = (int) event.getY();
int index = y / (getMeasuredHeight() / mSelectCharacters.length);
if (index < 0) {
index = 0;
} else if (index >= mSelectCharacters.length) {
index = mSelectCharacters.length - 1;
}
return index;
}
private Paint mCurrentIndexPaint = new Paint();
{
mCurrentIndexPaint.setColor(Color.WHITE);
mCurrentIndexPaint.setTextSize(24);
mCurrentIndexPaint.setTypeface(Typeface.DEFAULT_BOLD);
mCurrentIndexPaint.setTextAlign(Paint.Align.CENTER);
}
private Paint mBluePaint = new Paint();
{
mBluePaint.setColor(ContextCompat.getColor(getContext(), R.color.blue2));
}
private Paint mOtherIndexPaint = new Paint();
{
mOtherIndexPaint.setColor(Color.BLACK);
mOtherIndexPaint.setTextSize(24);
mOtherIndexPaint.setTypeface(Typeface.DEFAULT_BOLD);
mOtherIndexPaint.setTextAlign(Paint.Align.CENTER);
}
}
4 .qwertySearch和T9Search 分别用于联系人页的搜索和拨号页的拨号盘搜索
public List<BeanContact> qwertySearch(String keyword) {
List<BeanContact> mSearchContacts = new ArrayList<BeanContact>();
if (TextUtils.isEmpty(keyword)) {// add all base data to search
for (int i = 0; i < mBaseContactList.size(); i++) {
BeanContact currentContacts = null;
for (currentContacts = mBaseContactList.get(i); null != currentContacts; currentContacts = currentContacts.getmNextContacts()) {
currentContacts.setSearchByType(BeanContact.SearchByType.SearchByNull);
currentContacts.setmMatchKeywords("");
if (true == currentContacts.ismFirstMultipleContacts()) {
mSearchContacts.add(currentContacts);
} else {
if (false == currentContacts.ismHideMultipleContacts()) {
mSearchContacts.add(currentContacts);
}
}
}
}
return mSearchContacts;
}
int contactsCount = mBaseContactList.size();
/**
* search process: 1:Search by name (1)Search by original name (2)Search
* by name pinyin characters(original name->name pinyin characters)
* 2:Search by phone number
*/
for (int i = 0; i < contactsCount; i++) {
PinyinSearchUnit namePinyinSearchUnit = mBaseContactList.get(i).getmNamePinyinSearchUnit();
if (true == QwertyUtil.match(namePinyinSearchUnit, keyword)) {// search by name;
BeanContact currentContacts = null;
currentContacts = mBaseContactList.get(i);
currentContacts.setSearchByType(BeanContact.SearchByType.SearchByName);
currentContacts.setmMatchKeywords(namePinyinSearchUnit.getMatchKeyword().toString());
mSearchContacts.add(currentContacts);
continue;
} else {
BeanContact currentContacts = null;
for (currentContacts = mBaseContactList.get(i); null != currentContacts; currentContacts = currentContacts.getmNextContacts()) {
if (currentContacts.getmPhoneNumber().contains(keyword)) {// search by phone number
currentContacts.setSearchByType(BeanContact.SearchByType.SearchByPhoneNumber);
currentContacts.setmMatchKeywords(keyword);
mSearchContacts.add(currentContacts);
}
}
continue;
}
}
if (mSearchContacts.size() <= 0) {
} else {
Collections.sort(mSearchContacts, BeanContact.mAscComparator);
}
return mSearchContacts;
}
public List<BeanContact> t9InputSearch(String keyword) {
List<BeanContact> mSearchByNameContacts = new ArrayList<BeanContact>();
List<BeanContact> mSearchByPhoneNumberContacts = new ArrayList<BeanContact>();
List<BeanContact> mSearchContacts = new ArrayList<BeanContact>();
if (null == keyword) {// add all base data to search
return mSearchContacts;
}
int contactsCount = mBaseContactList.size();
/**
* search process: 1:Search by name (1)Search by name pinyin
* characters(org name->name pinyin characters) ('0'~'9','*','#')
* (2)Search by org name ('0'~'9','*','#') 2:Search by phone number
* ('0'~'9','*','#')
*/
for (int i = 0; i < contactsCount; i++) {
PinyinSearchUnit namePinyinSearchUnit = mBaseContactList.get(i).getmNamePinyinSearchUnit();
if (true == T9Util.match(namePinyinSearchUnit, keyword)) {// search by name;
BeanContact currentContacts = null;
currentContacts = mBaseContactList.get(i);
// currentContacts.setSearchByType(BeanContact.SearchByType.SearchByName);
currentContacts.setmMatchT9Keywords(namePinyinSearchUnit.getMatchKeyword().toString());
mSearchByNameContacts.add(currentContacts);
// BeanContact firstContacts = null;
// for (currentContacts = mBaseContactList.get(i), firstContacts = currentContacts; null != currentContacts; currentContacts = currentContacts.getmNextContacts()) {
// currentContacts.setSearchByType(BeanContact.SearchByType.SearchByName);
// currentContacts.setmMatchKeywords(namePinyinSearchUnit.getMatchKeyword().toString());
//// currentContacts.setMatchStartIndex(firstContacts.getName().indexOf(firstContacts.getMatchKeywords().toString()));
//// currentContacts.setMatchLength(firstContacts.getMatchKeywords().length());
// mSearchByNameContacts.add(currentContacts);
// }
continue;
} else {
BeanContact currentContacts = null;
for (currentContacts = mBaseContactList.get(i); null != currentContacts; currentContacts = currentContacts.getmNextContacts()) {
if (currentContacts.getmPhoneNumber().contains(keyword)) {// search by phone number
// currentContacts.setSearchByType(BeanContact.SearchByType.SearchByPhoneNumber);
currentContacts.setmMatchT9Keywords(keyword);
// currentContacts.setMatchStartIndex(currentContacts.getPhoneNumber().indexOf(keyword));
// currentContacts.setMatchLength(keyword.length());
mSearchByPhoneNumberContacts.add(currentContacts);
}
}
continue;
}
}
if (mSearchByNameContacts.size() > 0) {
Collections.sort(mSearchByNameContacts, BeanContact.mAscComparator);
}
if (mSearchByPhoneNumberContacts.size() > 0) {
Collections.sort(mSearchByPhoneNumberContacts, BeanContact.mAscComparator);
}
mSearchContacts.clear();
mSearchContacts.addAll(mSearchByNameContacts);
mSearchContacts.addAll(mSearchByPhoneNumberContacts);
return mSearchContacts;
}
```
5 . 通话记录获取
```java
public class CallLogHelper extends ContextWrapper {
private SimpleDateFormat format;
public List<BeanCallLog> getCalllogList(BaseFragment context) {
HashMap<String, BeanCallLog> map = new HashMap<>();
Numbercity nc = new Numbercity();
List<BeanCallLog> list_bCallLogs = new ArrayList<BeanCallLog>();
String[] projection = {CallLog.Calls.DATE, // 通话时间
CallLog.Calls.NUMBER,// 电话号码
CallLog.Calls.TYPE, // 通话类型,主拨,被叫,未接
CallLog.Calls.CACHED_NAME,// 显示名称
CallLog.Calls.DURATION, // 通话时长
CallLog.Calls._ID}; // 查询的列
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG) != PackageManager.PERMISSION_GRANTED) {
context.requestPermissions(new String[]{Manifest.permission.READ_CALL_LOG}, CallFragment.READ_CALL_LOG_REQUEST_CODE);
return list_bCallLogs;
}
Cursor cursor = getContentResolver().query(CallLog.Calls.CONTENT_URI, projection, null,
null, CallLog.Calls.DEFAULT_SORT_ORDER);
int id;
String name;// 名称
String number;// 号码
String date_str;// 日期
int type;// 来电:1,拨出:2,未接:3
int duration; // 通话时长
while (cursor.moveToNext()) {
Date date = new Date(cursor.getLong(cursor
.getColumnIndex(CallLog.Calls.DATE)));
number = cursor.getString(cursor
.getColumnIndex(CallLog.Calls.NUMBER));
type = cursor.getInt(cursor.getColumnIndex(CallLog.Calls.TYPE));
name = cursor.getString(cursor
.getColumnIndex(CallLog.Calls.CACHED_NAME));// 缓存的名称与电话号码,如果它的存在
id = cursor.getInt(cursor.getColumnIndex(CallLog.Calls._ID));
duration = cursor.getInt(cursor
.getColumnIndex(CallLog.Calls.DURATION));
date_str = format.format(date);
BeanCallLog clb = new BeanCallLog();
clb.setId(id);
if (name == null) {
name = "";
}
clb.setName(name);
clb.setNumber(number);
clb.setDate(date);
clb.setType(type);
clb.setDuration(duration);
clb.setCount(1);
clb.setPlace(nc.getCityByNum(number));
if (!number.equals("-1")) {// 未知的号码不加入通话记录里面
if (map.containsKey(clb.getName() + clb.getType() + date_str)) {
BeanCallLog beanCallLog = map.get(clb.getName() + clb.getType() + date_str);
int count = beanCallLog.getCount() + 1;
beanCallLog.setCount(count);
} else {
map.put(clb.getName() + clb.getType() + date_str, clb);
}
}
}
cursor.close();
nc.close();
list_bCallLogs.addAll(map.values());
Collections.sort(list_bCallLogs, BeanCallLog.mDescComparator);
return list_bCallLogs;
}
public CallLogHelper(Context base) {
super(base);
format = new SimpleDateFormat("MM-dd");
}
}
```
参考资料:
https://github.com/handsomezhou/PinyinSearchLibrary
Demo源码:
https://github.com/luohaiah/ContactDemo