基于qwertySearch和T9Search实现的通讯录Demo

  • 先上效果图

《基于qwertySearch和T9Search实现的通讯录Demo》 通讯录.gif

  • 实现思路

    1. 异步加载联系人
     ```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,这样就可以实现这种效果用于更精准定位联系人

《基于qwertySearch和T9Search实现的通讯录Demo》 精准定位联系人.png

 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
    原文作者:西瓜太郎123
    原文地址: https://www.jianshu.com/p/3c54cc71e238
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞