常用的自定义控件四(QuickBarView)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details/51804865

常用的自定义控件四(QuickBarView)

自定义View 通讯录字母快速索引


在Android日常开发中,我们经常在联系人界面看到一些字母导航栏,点击字母的时候,会根据汉字的首拼音来查找是否存在相应的item,这种效果很常见,几乎所有涉及到通讯的都会用到,包括qq,微信,微博等,今天我为大家带来的就是这种自定义控件

废话不多说 ,大家先来看一下实际的效果

  • 效果图一

转载请注明原博客地址:http://blog.csdn.net/gdutxiaoxu/article/details/51804865 源码下载地址:https://github.com/gdutxiaoxu/QuickIndex.git

大家先来看一下源码

/** * 博客地址:http://blog.csdn.net/gdutxiaoxu * 快速索引,根据字母的索引查找相应的联系人 * * @author xujun * @time 2015/11/1 21:40. */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 static final String TAG = "xujun";    private Paint mPaint;    //字母的宽度    private int cellWidth;    //字母的高度    private float cellHeight;    //记录上一次触摸的Index    private int mLastTouchIndex = -1;    //字母被选中显示的颜色    private int mSelectColor = Color.GRAY;    //字母正常显示的颜色    private int mNormalColor = Color.WHITE;    private Context mContext;    /**     * 暴露一个字母的监听     */    public interface OnLetterUpdateListener {        void onLetterUpdate(String letter);    }    private OnLetterUpdateListener listener;    public OnLetterUpdateListener getListener() {        return listener;    }    /**     * 设置字母更新监听     *     * @param listener     */    public void setListener(OnLetterUpdateListener listener) {        this.listener = listener;    }    public QuickIndexBar(Context context) {        this(context, null);    }    public QuickIndexBar(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public QuickIndexBar(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        mContext = context;        //初始化自定义属性        obtainAttrs(attrs);        // 初始化画笔        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);        float textSize = UIUtils.dip2px(15, mContext);        mPaint.setTextSize(textSize);        mPaint.setTypeface(Typeface.DEFAULT_BOLD);    }    private void obtainAttrs(AttributeSet attrs) {        TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.QuickIndexBar);        int selectColor = typedArray.getColor(R.styleable.QuickIndexBar_select_color, -1);        if (selectColor != -1) {            mSelectColor = selectColor;        }        int normalColor = typedArray.getColor(R.styleable.QuickIndexBar_normal_color, -1);        if (normalColor != -1) {            mNormalColor = normalColor;        }        typedArray.recycle();    }    @Override    protected void onDraw(Canvas canvas) {        for (int i = 0; i < LETTERS.length; i++) {            String text = LETTERS[i];            // 计算坐标            int x = (int) (cellWidth / 2.0f - mPaint.measureText(text) / 2.0f);            // 获取文本的高度            Rect bounds = new Rect();// 矩形            mPaint.getTextBounds(text, 0, text.length(), bounds);            int textHeight = bounds.height();            int y = (int) (cellHeight / 2.0f + textHeight / 2.0f + i * cellHeight);            // 根据按下的字母, 设置画笔颜色            mPaint.setColor(mLastTouchIndex == i ? mSelectColor : mNormalColor);            // 绘制文本A-Z            canvas.drawText(text, x, y, mPaint);        }    }    @Override    public boolean onTouchEvent(MotionEvent event) {        int index = -1;        switch (MotionEventCompat.getActionMasked(event)) {            case MotionEvent.ACTION_DOWN:                // 获取当前触摸到的字母索引                index = (int) (event.getY() / cellHeight);                if (index >= 0 && index < LETTERS.length) {                    // 判断是否跟上一次触摸到的一样,不一样才进行回调                    if (index != mLastTouchIndex) {                        if (listener != null) {                            //                            listener.onLetterUpdate(LETTERS[index]);                        }                        Log.d(TAG, "onTouchEvent: " + LETTERS[index]);                        //记录上一次触摸的Index为当前的index;                        mLastTouchIndex = index;                    }                }                break;            case MotionEvent.ACTION_MOVE:                index = (int) (event.getY() / cellHeight);                if (index >= 0 && index < LETTERS.length) {                    // 判断是否跟上一次触摸到的一样                    if (index != mLastTouchIndex) {                        if (listener != null) {                            listener.onLetterUpdate(LETTERS[index]);                        }                        Log.d(TAG, "onTouchEvent: " + LETTERS[index]);                        mLastTouchIndex = index;                    }                }                break;            case MotionEvent.ACTION_UP:                // 手指抬起的时候重置                mLastTouchIndex = -1;                break;            default:                break;        }        //调用这个方法会重新调用draw方法,重新绘制        invalidate();        return true;    }    /**     * 当大小 改变的时候会回调这个方法,     * 这里我们就不主动调用measure()方法了     *     * @param w     * @param h     * @param oldw     * @param oldh     */    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        // 获取单元格的宽和高        cellWidth = getMeasuredWidth();        int mHeight = getMeasuredHeight();        cellHeight = mHeight * 1.0f / LETTERS.length;    }}

代码 解析

  • 代码其实不长,加上一些注释总共才180多行,总体来说,思路分分为以下几个步骤
    1. 在构造方法里面初始化画笔,同时为了使用方便,我们封装了自定义属性
 public QuickIndexBar(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        mContext = context;        //初始化自定义属性        obtainAttrs(attrs);        // 初始化画笔        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);        float textSize = UIUtils.dip2px(15, mContext);        mPaint.setTextSize(textSize);        mPaint.setTypeface(Typeface.DEFAULT_BOLD); }     private void obtainAttrs(AttributeSet attrs) {        TypedArray typedArray = mContext.obtainStyledAttributes(attrs, R.styleable.QuickIndexBar);        int selectColor = typedArray.getColor(R.styleable.QuickIndexBar_select_color, -1);        if (selectColor != -1) {            mSelectColor = selectColor;        }        int normalColor = typedArray.getColor(R.styleable.QuickIndexBar_normal_color, -1);        if (normalColor != -1) {            mNormalColor = normalColor;        }        typedArray.recycle();    }
  1. 接着我们在onSizeChange 方法里面拿到我们时间的宽度和高度,有人可能会问了为什么不在onMeasure里面获取了,其实在onMeasure方法里面获取是可以的,只不过我们还需要调用一下measure方法而已,在onSizeChnage方法里面,我们直接调用
 cellWidth = getMeasuredWidth();

即可获取到我们需要的宽度和高度。用起来比较方便,不过更多的是为了让大家知道View有这一个方法存在以及怎么使用它  顺便我们来看一下google官方对onSizeChange方法的解释

This is called during layout when the size of this view has changed. If you were just added to the view hierarchy, you're called with the old values of 0.

从官方的解释我们可以知道这个方法是在onLayout方法中当大小改变的时候会调用这个方法,因此我们直接调用getMeasuredWidth();是可以获取得到宽度的,因为onMeasure 是先于onLayout方法调用的。  3. 接着我们重写onDraw方法,在onDraw方法我们所做的工作就是绘制 我们需要的26个字母

    protected void onDraw(Canvas canvas) {        for (int i = 0; i < LETTERS.length; i++) {            String text = LETTERS[i];            // 计算坐标            int x = (int) (cellWidth / 2.0f - mPaint.measureText(text) / 2.0f);            // 获取文本的高度            Rect bounds = new Rect();// 矩形            mPaint.getTextBounds(text, 0, text.length(), bounds);            int textHeight = bounds.height();            int y = (int) (cellHeight / 2.0f + textHeight / 2.0f + i * cellHeight);            // 根据按下的字母, 设置画笔颜色            mPaint.setColor(mLastTouchIndex == i ? mSelectColor : mNormalColor);            // 绘制文本A-Z            canvas.drawText(text, x, y, mPaint);        }    }
  1. 讲到这里,我们的工作已经完成一大半了,接着就是处理我们是按下或者一个字母了,我们重写onTouchEvent方法,并且return true;是为了保证 Action_down动作按下以后,Action_move以后的动作能够顺利接受到,这涉及到View的事件分发机制,有空的话我会尝试总结一下,这里就不说了
// 获取当前触摸到的字母索引index = (int) (event.getY() / cellHeight);

同时我们记录下我们当前是触摸或者按下哪一个字母

mLastTouchIndex = index;
  1. 知道了我们当前是触摸或者按下哪一个字母了,那我们要怎样将这些信息暴露出去了,不难想象就是采用接口回调的方法,为此我们提供了这样一个接口
 /** * 暴露一个字母的监听 */public interface OnLetterUpdateListener {    void onLetterUpdate(String letter);}

并且提供了设置监听器的方法,这样我们就成功将我们的按下字母的信息提供给外界了

public void setListener(OnLetterUpdateListener listener) {        this.listener = listener;}

详细代码如下

int index = -1;switch (MotionEventCompat.getActionMasked(event)) {    case MotionEvent.ACTION_DOWN:        // 获取当前触摸到的字母索引        index = (int) (event.getY() / cellHeight);        if (index >= 0 && index < LETTERS.length) {            // 判断是否跟上一次触摸到的一样,不一样才进行回调            if (index != mLastTouchIndex) {                if (listener != null) {                    //                    listener.onLetterUpdate(LETTERS[index]);                }                Log.d(TAG, "onTouchEvent: " + LETTERS[index]);                //记录上一次触摸的Index为当前的index;                mLastTouchIndex = index;            }        }        break;    case MotionEvent.ACTION_MOVE:        index = (int) (event.getY() / cellHeight);        if (index >= 0 && index < LETTERS.length) {            // 判断是否跟上一次触摸到的一样            if (index != mLastTouchIndex) {                if (listener != null) {                    listener.onLetterUpdate(LETTERS[index]);                }                Log.d(TAG, "onTouchEvent: " + LETTERS[index]);                mLastTouchIndex = index;            }        }        break;    case MotionEvent.ACTION_UP:        // 手指抬起的时候重置        mLastTouchIndex = -1;        break;    default:        break;}//调用这个方法会重新调用draw方法,重新绘制invalidate();return true;

到此 QuickBarView的源码分析为止,下面我们来学习一下是怎样结合ListView使用的


下面我贴出核心代码,想仔细了解的请点击源码下载 源码下载地址:

mQuickIndexBar.setListener(new OnLetterUpdateListener() {    @Override    public void onLetterUpdate(String letter) {        //          UIUtils.showToast(getApplicationContext(), letter);        showLetter(letter);        // 根据字母定位ListView, 找到集合中第一个以letter为拼音首字母的对象,得到索引        for (int i = 0; i < persons.size(); i++) {            Person person = persons.get(i);            String l = person.getPinyin().charAt(0) + "";            if (TextUtils.equals(letter, l)) {                // 匹配成功                mListView.setSelection(i);                break;            }        }    }});

思路解析 如下 1. 在我们的List数据里面查找是否有有相应的首字母是触摸的字母,有的话返回相应的index,  2. 然后再调用ListView的setSelection(i)方法选中哪一个Item

mListView.setSelection(i);

至于有些item有显示字母,有一些没有显示字母,其实就是判断上一个item的首字母是不是跟当前的首字母是不是一样的,不一样的话,显示当前item的字母,不过要注意一点,就是position等于0的时候,我们需要做特殊处理,代码如下

String str = null;String currentLetter = p.getPinyin().charAt(0) + "";// 根据上一个首字母,决定当前是否显示字母if(position == 0){   str = currentLetter;}else {   // 上一个人的拼音的首字母   String preLetter = persons.get(position - 1).getPinyin().charAt(0) + "";   if(!TextUtils.equals(preLetter, currentLetter)){      str = currentLetter;   }}// 根据str是否为空,决定是否显示索引栏mViewHolder.mIndex.setVisibility(str == null ? View.GONE : View.VISIBLE);

到此我们的分析为止

转载请注明原博客地址:http://blog.csdn.net/gdutxiaoxu/article/details/51804865 源码下载地址:https://github.com/gdutxiaoxu/QuickIndex.git

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码农阿宇

利用GDI+在Winfrom绘制验证码

string yzm; private void yangzhengma() { Bitma...

3027
来自专栏刘晓杰

嵌套滑动机制详解

4488
来自专栏游戏杂谈

利用threshold实现的遮罩引导

点击“开始引导”,则进入引导操作。除指定的按钮可以操作外,其它区域均不可点击。这种应用当新功能或是新产品上线后,用来引导用户来使用产品/功能,是十分有用的。fa...

1101
来自专栏三流程序员的挣扎

RecyclerView 使用总结

自定义类继承 RecyclerView.ItemDecoration,重写回调方法

2902
来自专栏mathor

事件模型

 事件模型如何理解呢,举个例子,你老婆出门了,让你在家看孩子,难道你每过一分钟就去看一次孩子吗,对于计算机来说,我做一个按钮,难道就一直监听这个按钮使用没有...

971
来自专栏java架构师

C#对图片文件的压缩、裁剪操作初探

在做项目时,对图片的处理,以前都采用在上传时,限制其大小的方式,这样带来诸多不便。毕竟网站运维人员不一定会对图片做处理,经常超出大小限制,即使会使用图片处理软件...

3296
来自专栏烙馅饼喽的技术分享

asp.net生成透明gif的准完美方案

       我最近在实现我的WMS服务器的时候,发现了一个超级郁闷的问题,问题描述如下:客户需要动态叠加一个透明层到底图上,比如说公交线路层,这个层有透明背景...

5987
来自专栏Java技术分享圈

杨老师课堂之JavaScript定时器限时抢购秒杀商品案例

定时器 setInterval(函数,毫秒):在指定的毫秒数后调用函数或执行一段代码

1192
来自专栏Coco的专栏

【CSS进阶】原生JS getComputedStyle等方法解析

2335
来自专栏java沉淀

java生成二维码例子及Qrcode.jar包(demo小例子)

2433

扫码关注云+社区

领取腾讯云代金券