联系人列表字母排序索引(一)

很久没有写博客了,对自己又放松了很多。这篇博客本来早就要写了,迟迟拖到现在。今天这里要说的是,联系人列表,分组带索引,即联系人按字母顺序排列并分组,右边还有索引条。先看下效果:



就是这么一个效果,想必大家都想知道是怎么实现的吧。其实很简单,接下来我会慢慢讲解这个效果的实现。


首先我们分析这个联系人列表的组成:

1.联系人列表 ----- ListView

2.右边索引-------自定义View

3.浮动窗口------WindowManager

4.获取名称的首字母----PinYin4j(已经将github上的封装成了依赖包)



下面我将按照我的实现顺序进行说明:

1.先自定义右边索引。

2.设置浮动窗口。

3.添加列表数据,对数据进行排序,对列表进行分组。

4.添加列表与索引间的监听。



一、自定义索引

这里的索引,我把它命名为IndexView,IndexView 包含26个英文字母和“#”,每个字母所占据的区域位置,IndexView按下时显示黑色背景,每个字母都有文字颜色,触摸时浮动窗口显示不同的字母。这里,我们就有概念了,于是可以开始写代码了:

1.先定义基本的成员变量

 /*字母表数组*/
    private String letter = "ABCDEFGHIJKLMNOPQRSTUVWXYZ#";
    private Paint mPaint;
    private int mTextColor;
    private int mTextSize;
    private int mPadding;
    private int mLetterWidth;
    private int mLetterHeight;
    //当被触摸时,绘制背景
    private boolean isTouched;
    private int mBackgroundColor;
    private int mTransparentColor;
    //除了用于绘制外,更涉及到触摸事件
    private ArrayList<Rect> mRects;
    private OnCharTouchEvent mListener;
    //上一次获取的字母
    private String mPreLetter;

2.然后开始写逻辑,首先,我们必须继承View,然后重写构造方法,在构造方法中都调用一个init()方法:

public class IndexView extends View
    public IndexView(Context context) {
        super(context);
        init(context);
    }

    public IndexView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public IndexView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

init里面完成了什么工作呢?主要是对一些成员变量的初始化,其中画笔用来绘制文字的:

  private void init(Context context) {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mTextColor = Color.parseColor("#666666");
        mBackgroundColor =  Color.parseColor("#bbbbbb");
        mTransparentColor = Color.parseColor("#00000000");
        mRects = new ArrayList<>();
    }

3.设置每个字母的位置,每个字母的位置都是一个矩形区域,我们用Rect表示,Rects里面保存所有的字母位置,因此,重写onSizeChanged方法,onSizeChanged方法,是在measure之后调用的,因为在onMeasure里面,我们会得到每个字母的宽和高,

因此这里能够得到每个字母的矩形位置:

 @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mRects.clear();
        for (int i = 0; i < letter.length(); i++) {
            int x = 0;
            int y = mLetterHeight * i;
            Rect rect = new Rect(x, y, x + mLetterWidth, y + mLetterHeight);
            mRects.add(rect);
        }
    }

4.重写 onMeasure方法,调用于onSizeChanged之前,这里面我们要完成测量当前自定义View的宽和高,以及每个字母的宽和高,这里我们设置字母所占的宽和高是一样的,因此,当前自定义view的宽也就是字母的宽,然后按比例设置了字母的大小,间距等。

   @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //widthMeasureSpec是父类中,调用该类的measure方法设置的
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        mLetterWidth = mLetterHeight = heightSize / letter.length();
        mPadding = mLetterHeight / 5;
        mTextSize = mLetterHeight - mPadding * 2;
        setMeasuredDimension(mLetterWidth, heightSize);
    }


5.重写onDraw()方法,主要完成字母的绘制工作。先通过一个变量,判断当前是否是触摸状态,由此设置绘制背景;接着循环绘制每一个字母。

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(isTouched ? mBackgroundColor : mTransparentColor);
        for (int i = 0; i < letter.length(); i++) {
            char c = letter.charAt(i);
            String s = String.valueOf(c);
            mPaint.setColor(mTextColor);
            mPaint.setTextSize(mTextSize);
            mPaint.setTextAlign(Paint.Align.CENTER);
            drawTextCenter(canvas, mPaint, s, mRects.get(i));
        }

    }

   /**
     * 将文本绘制在居中位置
     *
     * @param canvas
     * @param paint
     * @param s
     * @param rect
     */
    private void drawTextCenter(Canvas canvas, Paint paint, String s, Rect rect) {
        Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
        int baseline = rect.top + (rect.bottom - rect.top - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top;
        paint.setTextAlign(Paint.Align.CENTER);
        canvas.drawText(s, rect.centerX(), baseline, paint);
    }

drawTextCenter使用之前,需要设置画笔居中,这个方法是根据文本的绘制原理,来将文本绘制在居中位置的。不懂的可以自行百度,谷歌。


到这里,我们已经绘制完一个索引了。但是这是不够的,我们还需要为它添加触摸事件。


6.重写onTouchEvent方法。

/**
     * 接收到事件,进行处理,并消费掉
     *
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                doDown(event);
                break;
            case MotionEvent.ACTION_MOVE:
                doMove(event);
                break;
            case MotionEvent.ACTION_UP:
                doCancelOrUp();
                break;
            case MotionEvent.ACTION_CANCEL:
                doCancelOrUp();
                break;
        }
        return true;
    }

直接返回了true,即所有的触摸事件到这里,都将被消费掉。然后针对不同的事件进行处理。

ActionDown 事件:

   private void doDown(MotionEvent event) {
        isTouched = true;
        int downX = (int) event.getX();
        int downY = (int) event.getY();
        String s = getTouchedLetter(downX, downY);
        Log.v("@s", s + "");
        if (mListener != null)
            mListener.onTouch(s);
        invalidate();
    }
记录位置,根据位置获取对应的字母,然后设置监听事件。(这里,先理解,getTouchedLetter为通过坐标位置获取字母,mListener为监听事件,用于回调浮动窗口的)。


ActionMove事件:

 /**
     * 移动时,判断选中的字母是否发生了变化,并进行回调
     *
     * @param event
     */
    private void doMove(MotionEvent event) {
        isTouched = true;
        int downX = (int) event.getX();
        int downY = (int) event.getY();
        String s = getTouchedLetter(downX, downY);
        if (s == null)
            return;
        if (mListener != null) {
            if (mPreLetter == null || (!mPreLetter.equalsIgnoreCase(s))) {
                mListener.onLetterChanged(mPreLetter, s);
                mPreLetter = s;
            }
        }

        invalidate();
    }
这里每次都要记录上一个的字母,如果字母发生变化,才回调。

ActionUp|ActionCancel事件:

   isTouched = false;
        if (mListener != null)
            mListener.onRelease();
        invalidate();
触摸标志位设置为false,并回调关闭浮动窗口


通过位置获取字母,循环判断坐标位置,包含在哪个字母的Rect矩形位置中,通过这个位置获取字母。

  private String getTouchedLetter(int x, int y) {
        for (int i = 0; i < mRects.size(); i++) {
            Rect rect = mRects.get(i);
            if (rect.contains(x, y)) {
                return String.valueOf(letter.charAt(i));
            }
        }

        return null;
    }


最后一个是监听接口:

  public interface OnCharTouchEvent {
        void onTouch(String s);

        void onLetterChanged(String preLetter, String letter);

        void onRelease();
    }


到这里,我们的效果是这样的:




今天先到这里,源码提前放出


源码



  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值