自定义View(四)之QuickIndex的实现

一,概述

在APP中,只要有联系人,都肯定有通讯录页面,而且所有的通讯录以页面都很相似,一个列表,列表中的人员按字母排列,而且按字母分组显示,且右侧有快速索引。具体效果如下图:
这里写图片描述

右侧带字母的view就是快速索引条,点击字母就可以快速找到该字母对应的联系人。使用如此广泛的View,在android SDK中并没有提供,此时就需要自定义View实现。

该View实用且简单,值得学习自定义View的小伙伴来学习。此View中可以体会到测量高度时onDraw方法的影响,以至于对UI效果的影响。

网上实现这种功能的demo很多,大家都约定俗成的称为QuickIndex,这里也把这个View称为QuickIndex。

在QuickIndex中会使用到Touch事件,所以先简单讲述一下Touch事件的使用。

二,Touch事件的使用

用户与APP的交互都是通过Touch事件完成的,比如Button的点击事件,ListView的滑动事件,等等都属于Touch事件。自定View如果需要与用户就行交互也需要实现Touch事件的操作逻辑。在Android中有一整套的Touch事件机制,提供了很多相关的方法,下面主要讲一下View的onTouch方法,Touch的机制在后面会有专门的blog来讲解。

对于QuickIndex这个View,需要实现按下事件和抹动事件,无论是按下某个字母,或者抹动到某个字母都应该有事件响应,告诉程序抹动了哪一个字母,然后做出响应。实现这个功能只需要对View的onTouchEvent方法进行重写即可。关于onTouchEvent方法的基本用法如下:

 public boolean onTouchEvent(MotionEvent event) {//MotionEvent中包含事件类型:按下,滑动,抬起,消失。
        switch (event.getAction()) {//使用switch区分不同类型的事件,然后做出不同的响应
            case MotionEvent.ACTION_DOWN://当按下时,每次操作屏幕肯定有按下事件。

                break;
            case MotionEvent.ACTION_MOVE://当滑动时,注意手指每移动一个像素就是一个滑动事件,所以滑动事件常常会连续发生很多次。

                break;
            case MotionEvent.ACTION_UP://当抬起时

                break;
            case MotionEvent.ACTION_CANCEL://单消失时,当手指滑出View的边界时,此时会响应这个事件。

                break;
            default:
                break;
        }
        return true;//注意一定要返回true,只有返回true事件才会有效。
    }

了解Touch事件后,下面开始实现QuickIndex。

三,QuickIndex的实现

由于这是第二次讲自定义View,大家都不在是新手了,如果是新手建议先看一下View(一)之初识自定义View。既然大家都不是新手,这里就直接上代码,已经讲解过的知识也不在使用注释说明。

1,自定义一个类QuickIndex,继承View,实现三个构造方法,并声明一些字段

public class QuickIndexBar extends View {
    //显示的所有字母符号
    private static final String[] LETTERS = new String[]{
            "↑", "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 int mCellWidth;//一个字母所在小格子的宽度
    private float mCellHeight;//一个字母所在小格子的高度


    private Paint mPaint;//画笔
    private int mPreIndex = -1;//上次索引
    private int mCurIndex = -1;//当前索引
    private Context mContext;
    public QuickIndexBar(Context context) {
        super(context);
        init(context);
    }
    public QuickIndexBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }
    public QuickIndexBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    /**
     * 对画笔进行初始化
     */
    private void init(Context context) {
        mContext = context;
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);//创建画笔对象
    }
}

2,重写onSizeChanged方法

这个方法也属于View的声明周期方法,当测量Size发生变化时调用,且在onMeasure方法发送后调用,所以在这个方法中可以通过getMeasureWidth和getMeasureHeight方法得到测量的宽高。方法的重写如下:

    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mCellWidth = getMeasuredWidth();//得到每个字母所在小格子的宽度
        int mHeight = getMeasuredHeight() ;
        mCellHeight = mHeight * 1.0f / LETTERS.length;//得到每个字母所在小格子的高度
    }

注意:每个字母所在小格子的宽度和高度都是通过测量宽度和测量高度计算出来的,在这儿体现出来了测量宽度和测量高度的作用,也即体现出了onMeasure方法的作用。

注:这个View中没有重写onMeasure方法,会执行View类中的onMeasure方法,测量宽高的设置与布局文件中的宽高有关。

3,重写onDraw方法

方法源码如下:

    protected void onDraw(Canvas canvas) {
        for (int i = 0; i < LETTERS.length; i++) {//遍历每一个字母
            String text = LETTERS[i];
            Rect bounds = new Rect();
            mPaint.getTextBounds(text, 0, text.length(), bounds);//得到文字的宽高,并放入bounds对象中
            int x = (int) (mCellWidth / 2.0f - bounds.width() / 2.0f);
            int y = (int) (mCellHeight / 2.0f + bounds.height() / 2.0f + i * mCellHeight );
            if(i == mCurIndex){
                mPaint.setColor(Color.GRAY);//当选中时字母的字体颜色设为灰色
                mPaint.setTextSize(pxFromSp(mContext, 20 ));//当选中时字母的字体大小加5
            }else {
                mPaint.setColor(Color.BLACK);
                mPaint.setTextSize(pxFromSp(mContext, 15));
            }
            canvas.drawText(text, x, y, mPaint);//画字母
        }
    }

截止到此时,快速索引View是显示出来了。但是它还不具有Touch事件,下面实现Touch事件

4,Touch事件的实现

当按下或者滑动到某个字母时,要做出响应,此时需要实现Touch事件。本例中通过回调接口的方式对外提供访问的接口,所以先定义一个接口,如下:

/**
     * 字母改变监听器接口
     */
    public interface OnLetterChangedListener {
        /**
         * 当字母改变后调用,参数letter为改变后的字母
         */
        void onLetterChanged(String letter);

        /**
         * 当手指抬起时调用
         */
        void onLetterGone();
    }

    /**
     * 监听器
     */
    private OnLetterChangedListener mLetterChangedListener;

    /**
     * 设置监听器
     */
    public void setOnLetterChangedListener(OnLetterChangedListener listener) {
        mLetterChangedListener = listener;
    }

然后重写onTouchEvent方法:

public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE://DOWN和MOVE都表示触摸到某个字母事件
                setBackgroundColor(Color.GRAY);//设置按下的背景颜色

                mCurIndex = (int) ((event.getY() ) / mCellHeight);//根据Y坐标得到对应的字母索引
                if (mCurIndex >= 0 && mCurIndex < LETTERS.length && mCurIndex != mPreIndex) {
                    if (mLetterChangedListener != null) {
                        mLetterChangedListener.onLetterChanged(LETTERS[mCurIndex]);//当字母改变时调用
                        mPreIndex = mCurIndex;
                    }

                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL://当UP和CANCEL时都表示手指抬起
                setBackgroundColor(Color.TRANSPARENT);
                mCurIndex = -1;
                mPreIndex = -1;
                if (mLetterChangedListener != null) {
                    mLetterChangedListener.onLetterGone();//当手指抬起时调用
                }
                break;
            default:
                break;
        }
        invalidate();//重绘View。注意一定要调用这个方法。
        return true;
    }

注意两点:
1,invalidate()方法。这个方法已经讲过,调用这个方法会调用onDraw方法,让View重绘。
2,返回值一定要是true。

5,总结

以上4步就实现了QuickIndex,是不是特别简单。在这个自定义View中有四个注意点:
1,onMeasure方法。虽然我们没有重写onMeasure方法,但它会调用父类的onMeasure方法。默认的onMeasure方法得到的测量宽高与布局文件中设置的宽高有关。
2,onSizeChange方法。这个方法在onMeasure方法执行后执行,可以得到测量的宽高。
3,onDraw方法。onDraw方法是绘制的本质,非常重要。此时绘制的坐标与测量的宽高有关,即表现出来onMeasure与onDraw方法的相互关联。
4,onTouchEvent方法。这个方法也算View 的声明周期方法,当手指触摸屏幕时调用。

以上就是QuickIndex的实现过程。具体demo的源码分享到了github上,地址是:https://github.com/guozhengXia/QuickIndex。Demo中仿照微信实现了通讯录的功能,联系人按字母分组显示,右侧添加快速索引条。感兴趣的小伙伴可以下载查看。

猜你喜欢

转载自blog.csdn.net/fightingxia/article/details/72594126
今日推荐