Android实现通讯录功能,排序+字母索引(全拼加简拼)

目录

  • 一、效果图展示
  • 二、功能与准备
    • 2.1 功能
    • 2.2 准备
  • 三、开工
    • 3.1 右侧字母的索引
    • 3.2、通讯录分组
    • 3.3 数据整理排序
    • 3.4 搜索结果处理
    • 3.5 列表跟随索引移动
  • 四 DEMO

一、效果图展示

无图不BB,先上图

二、功能与准备

2.1 功能

  1. 按照拼音顺序对好友进行排序,英文数字符号归为#
  2. 右侧字母导航条,既可拖动也可点击
  3. 粘性头布局
  4. 搜索(全拼+简拼)

2.2 准备

需要导入文字转拼音的库
com.belerweb:pinyin4j:2.5.1'

三、开工

3.1 右侧字母的索引

  1. 字母的绘画
   private static final String[] DEFAULT_INDEX_ITEMS = {"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", "#"};

  @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        String index;
        //y的位置是baseline线的位置 加上mTopMargin
        //循环画上所有的字母
        for (int i = 0; i < mIndexItems.size(); i++) {
            index = mIndexItems.get(i);
            Paint.FontMetrics fm = mPaint.getFontMetrics();
            canvas.drawText(index,
                    (mWidth - mPaint.measureText(index)) / 2,
                    mItemHeight / 2 + (fm.bottom - fm.top) / 2 - fm.bottom + mItemHeight * i + mTopMargin,
                    i == mCurrentIndex ? mTouchedPaint : mPaint);
        }
    }
  1. 字母列表的触摸效果
 @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                float y = event.getY();
                //得到字母个数
                int indexSize = mIndexItems.size();
                //计算按压的位置
                int touchIndex = (int) (y / mItemHeight);
                //小于0的话那就默认第一个,大于他的个数就是最后一个
                if (touchIndex < 0) {
                    touchIndex = 0;
                } else if (touchIndex >= indexSize) {
                    touchIndex = indexSize - 1;
                }

                if (mOnIndexChangedListener != null && touchIndex >= 0 && touchIndex < indexSize) {

                    if (touchIndex != mCurrentIndex) {
                        mCurrentIndex = touchIndex;
                        if (mCenterTextView!=null){
                            mCenterTextView.setText(mIndexItems.get(touchIndex));
                            mCenterTextView.setVisibility(VISIBLE);
                        }
                        mOnIndexChangedListener.onIndexChanged(mIndexItems.get(touchIndex), touchIndex);
                        invalidate();
                    }

                }

                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:

                if (mCenterTextView!=null){
                    mCenterTextView.setVisibility(VISIBLE);
                }

                mCurrentIndex=-1;

                invalidate();

                break;


        }
        return true;
    }

    //显示居中的字母View
    public SideIndexBar setCenterTextView(TextView view){
        this.mCenterTextView = view;
        return this;
    }

    
    public SideIndexBar setOnIndexChangedListener(OnIndexTouchedChangedListener listener) {
        this.mOnIndexChangedListener = listener;
        return this;
    }
    //改变位置的接口
    public interface OnIndexTouchedChangedListener {
        void onIndexChanged(String index, int position);
    }

3.2、通讯录分组

  1. 首先先判断是否是同组的第一个
//判断该是否是同组的第一个
    private boolean isFirst(int position) {
        if (mStrings == null) {
            return false;
        }
        if (mStrings.isEmpty()) {
            return false;
        }
        if (position <= 0) {
            return true;
        } else {
            return !mStrings.get(position).getSection().equals(mStrings.get(position - 1).getSection());
        }

    }

  1. 再绘制子Item之间的距离
    //getItemOffsets 可以实现类似于padding的效果
    //
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        int position = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
        //mSectionHeight是粘性头部的高度,是同组的第一个就设置top
        if (isFirst(position)) {
            outRect.top = mSectionHeight;
        } else {
            outRect.top = 0;
        }

    }
  1. 绘制每组头部的背景和文字
    //实现类似绘制背景的效果,内容在上面
    //绘制背景和文字
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);

        int left = parent.getPaddingLeft();
        int right = parent.getWidth() - parent.getPaddingRight();
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            int position = params.getViewLayoutPosition();
            //是第一个就绘制背景以及文字
            if (isFirst(position)) {
                String name = mStrings.get(position).getSection();

                c.drawRect(left, child.getTop() - params.topMargin - mSectionHeight, right, child.getTop() - params.topMargin, mBgPaint);
                Paint.FontMetrics fm = mTextPaint.getFontMetrics();
                c.drawText(name, child.getPaddingLeft(), (child.getTop() - (mSectionHeight / 2 - (fm.descent - fm.ascent) / 2 + fm.descent) - params.topMargin), mTextPaint);
            }
        }

    }
  1. 绘制粘性头部,粘性头部就是滑动范围还在该组时,在最上方显示该组头部,其实就是头部覆盖在内容上。

    //onDrawOver 绘制在内容的上面,覆盖内容
    //这个是实现粘性头部的关键
    @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
        //拿到屏幕上显示的第一个item的位置
        int pos = ((LinearLayoutManager) (parent.getLayoutManager())).findFirstVisibleItemPosition();
        if (pos < 0) return;
        if (mStrings == null || mStrings.isEmpty()) return;
        String section =mStrings.get(pos).getSection();
        View child = parent.findViewHolderForLayoutPosition(pos).itemView;

        boolean flag = false;
        //添加一个平移替换效果
        if ((pos + 1) < mStrings.size()) {
            if (null != section && !section.equals(mStrings.get(pos + 1).getSection())) {
                //如果子item的高度+加距离顶部的距离 小于 section的高度,则进行平移
                if (child.getHeight() + child.getTop() < mSectionHeight) {
                    c.save();
                    flag = true;
                    c.translate(0, child.getHeight() + child.getTop() - mSectionHeight);
                }
            }
        }
        c.drawRect(parent.getPaddingLeft(),
                parent.getPaddingTop(),
                parent.getRight() - parent.getPaddingRight(),
                parent.getPaddingTop() + mSectionHeight, mBgPaint);
        Paint.FontMetrics fm = mTextPaint.getFontMetrics();
        c.drawText(section,
                child.getPaddingLeft(),
                parent.getPaddingTop() + mSectionHeight -(mSectionHeight / 2 - (fm.descent - fm.ascent) / 2 + fm.descent),
                mTextPaint);
        if (flag)
            c.restore();
    }

3.3 数据整理排序

  1. 先看Bean类
public class Star implements Comparable<Star> {

    private String name;
    private String pinyin; //拼音
    private String jianpin;//简拼

    /***
     * 获取悬浮栏文本,(#、定位、热门 需要特殊处理)
     * @return
     */
    public String getSection() {
        String s= pinyin;
        if (TextUtils.isEmpty(s)) {
            return "#";
        } else {
            String c = s.substring(0, 1);
            Pattern p = Pattern.compile("[a-zA-Z]");
            Matcher m = p.matcher(c);
            if (m.matches()) {
                return c.toUpperCase();
            } else {
                return "#";
            }

        }
    }
    //排序 #都往后放
    @Override
    public int compareTo(@NonNull Star o) {
        if (getSection().equals("#")&&!o.getSection().equals("#")){
            return 1;
        }else if (!getSection().equals("#")&&o.getSection().equals("#")){
            return -1;
        }else {
            return getSection().compareToIgnoreCase(o.getSection());
        }
    }

//省略get···set方法
}
  1. 简拼和全拼的获取时通过
 //获取全拼
    public String getPinYi(String chines) {

        sb.setLength(0);
        char[] nameChar = chines.toCharArray();
        HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
        defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
        defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        for (int i = 0; i < nameChar.length; i++) {
            if (nameChar[i] > 128) {
                try {
                    sb.append(PinyinHelper.toHanyuPinyinStringArray(nameChar[i], defaultFormat)[0]);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                sb.append(nameChar[i]);
            }
        }
        return sb.toString();
    }


    //获取简拼
    public String getPinYinHeadChar(String chines) {

        sb.setLength(0);
        char[] chars = chines.toCharArray();
        HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
        defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
        defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
        for (int i = 0; i < chars.length; i++) {
            if (chars[i] > 128) {
                try {
                    sb.append(PinyinHelper.toHanyuPinyinStringArray(chars[i], defaultFormat)[0].charAt(0));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                sb.append(chars[i]);
            }
        }
        return sb.toString();
    }

3.4 搜索结果处理

    @Override
    public void afterTextChanged(Editable s) {
        String keyword = s.toString();
        if (TextUtils.isEmpty(keyword)) {
            mClearAllBtn.setVisibility(View.GONE);
            mEmptyView.setVisibility(View.GONE);
            mResults = mAllCities;
            ((SectionDividerDecoration) (mRecyclerView.getItemDecorationAt(0))).setData(mResults);
            mAdapter.updateData(mResults);
        } else {
            mClearAllBtn.setVisibility(View.VISIBLE);
            //search是匹配结果,下面显示
            mResults = search(keyword, mAllCities);
            ((SectionDividerDecoration) (mRecyclerView.getItemDecorationAt(0))).setData(mResults);
            if (mResults == null || mResults.isEmpty()) {
                mEmptyView.setVisibility(View.VISIBLE);
            } else {
                mEmptyView.setVisibility(View.GONE);
                mAdapter.updateData(mResults);
            }
        }
        mRecyclerView.scrollToPosition(0);
    }
   public List search(String name, List<Star> list) {
        List results = new ArrayList();

        String patten = Pattern.quote(name);
        Pattern pattern = Pattern.compile(patten, Pattern.CASE_INSENSITIVE);
        for (int i = 0; i < list.size(); i++) {
            //根据拼音
            Matcher matcherPin = pattern.matcher((list.get(i)).getPinyin());
            //根据简拼
            Matcher jianPin = pattern.matcher((list.get(i)).getJianpin());
            //根据名字
            Matcher matcherName = pattern.matcher((list.get(i)).getName());
            if (matcherPin.find() || matcherName.find() || jianPin.find()) {

                results.add(list.get(i));
            }
        }
        return results;
    }

3.5 列表跟随索引移动

在Fragment中实现索引的接口
在实现里写上

  /**
    * 滚动RecyclerView到索引位置
    *
    * @param index
    */
   public void scrollToSection(String index) {
       if (mData == null || mData.isEmpty()) return;
       if (TextUtils.isEmpty(index)) return;
       int size = mData.size();
       for (int i = 0; i < size; i++) {
           if (TextUtils.equals(index.substring(0, 1), mData.get(i).getSection().substring(0, 1))) {
               if (mLayoutManager != null) {
                   mLayoutManager.scrollToPositionWithOffset(i, 0);
                   return;
               }
           }
       }
   }

四 DEMO

因为是项目里功能需求,有时间了我分离出来。

你可能感兴趣的:(android)