Android 自定义 View 实现通讯录字母索引(仿微信通讯录)

一、效果:我们看到很多软件的通讯录在右侧都有一个字母索引功能,像微信,小米通讯录,QQ,还有美团选择地区等等。这里我截了一张美团选择城市的图片来看看;

我们今天就来实现图片中右侧模块的索引功能,包括触摸显示以选中的索引字母。这里我的UI界面主要是参照微信的界面来实现,所以各位也可以对照微信来看看效果,什么都不说了,只有效果图最具有说服力!

二、分析:

我们看到这样的效果我们心理都回去琢磨,他是如何实现的;

首先,它肯定是通过自定义 View 来实现的,因为 Android 没有提供类似这样的控件、那么接下来就是如何自定义我们的 View ,我们知道自定义 View 最最主要的两个方法就是 onDraw(Canvas canvas)和

onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法,当然,如果是自定义 ViewGroup 的话就必须实现

onLayout(boolean changed, int left, int top, int right, int bottom) 方法,这里我们显然用自定义 View 就能够实现此功能,通过效果图可以看带,当触摸这块区域的时候,会弹出一个悬浮类似 Toast 的框来显示已经选中的索引内容,所以这里还需要重写View 的onTouchEvent(MotionEvent event)事件,最后就是悬浮框的实现。那么接下来就开始我们编码。

三、编码实现:

我们就按照 View 的执行顺序来实现

1、实现onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法,这个方法的功能是测量出我们的宽和高,具体实现看代码

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));
    }

这里定义了两个方法measureWidth( int) 和 measureHeight(int) ,通过方法名可以很清楚的知道,其功能分别是测量宽和高,进去看看是如何测量的。

    /**
     * 测量本身的大小,这里只是测量宽度
     * @param widthMeaSpec 传入父View的测量标准
     * @return 测量的宽度
     */
    private int measureWidth(int widthMeaSpec){
        /*定义view的宽度*/
        int width ;
        /*获取当前 View的测量模式*/
        int mode = MeasureSpec.getMode(widthMeaSpec) ;
        /*
        * 获取当前View的测量值,这里得到的只是初步的值,
        * 我们还需根据测量模式来确定我们期望的大小
        * */
        int size = MeasureSpec.getSize(widthMeaSpec) ;
        /*
        * 如果,模式为精确模式
        * 当前View的宽度,就是我们
        * 的size ;
        * */
        if(mode == MeasureSpec.EXACTLY){
            width = size ;
        }else {
            /*否则的话我们就需要结合padding的值来确定*/
            int desire = size + getPaddingLeft() + getPaddingRight() ;
            if(mode == MeasureSpec.AT_MOST){
                width = Math.min(desire,size) ;
            }else {
                width = desire ;
            }
        }
        mViewWidth = width ;
        return width ;
    }

以上是测量宽度的代码,其测量高度的代码,跟测量宽度的代码大致雷同,就不贴出来了,我会在最后附上源码。

2、实现onDraw(Canvas c)方法,这个方法相信大家都非常熟悉,就是把这些索引的内容绘制到 View 上显示出来,包括选中的时候背景颜色的变化;

    @Override
    protected void onDraw(Canvas canvas) {
        if(mTouched){
            canvas.drawColor(0x30000000);
        }
        for (int i = 0 ; i < mIndex.length ; i ++){
            mPaint.setColor(0xff000000);
            mPaint.setTextSize(mTextSize * 3.0f / 4.0f);
            mPaint.setTypeface(Typeface.DEFAULT) ;
            mPaint.getTextBounds(mIndex[i],0,mIndex[i].length(),mTextBound);
            float formX = mViewWidth/2.0f - mTextBound.width()/2.0f ;
            float formY = mTextSize*i + mTextSize/2.0f + mTextBound.height()/2.0f ;
            canvas.drawText(mIndex[i],formX,formY,mPaint);
            mPaint.reset();
        }
    }

我来讲一下 onDraw 方法中大致做了什么事,第一,绘制背景颜色,注意不是一上来就绘制,而是等到有手指触摸的时候就绘制背景颜色,第二,就是绘制索引的内容,这里需要根据当前 View 的宽和高来决定绘制内容的大小,和位置。

3、onTouchEvent(MotionEvent event)方法的实现

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float y = event.getY() ;
        int index = (int) (y / mTextSize);
        if(index >= 0 && index < mIndex.length){
            Log.v("zgy","======index======="+index) ;
            selectItem(index);
        }
        if(event.getAction() == MotionEvent.ACTION_MOVE){
            mTouched = true ;
        }else if (event.getAction() == MotionEvent.ACTION_MOVE){

        }else {
            mFloatView.setVisibility(INVISIBLE);
            mTouched = false ;
        }
        invalidate();
        /*过滤点其他触摸事件*/
        return true;
    }

代码也相对比较简单,首先获取当前触摸的点,根据点的坐标来获取索引的位置,从而拿到索引的位置。

4、到这里其实就已经实现了我们想要的效果,但是这样我们还是无法运用它,这里就需要定义一个回调接口

    /*定义一个回调接口*/
    public interface OnIndexSelectListener{
        /*返回选中的位置,和对应的索引名*/
        void  onItemSelect(int position, String value) ;
    }

回调接口我们放在哪里调用呢,当我们手指按下的时候,这时候其实我们需要确定我们按下的是哪个索引,滑动的时候也是一样,所以,这个没什么好商量的,直接放在onTouchEvent(MotionEvent event)中就可以,

        float y = event.getY() ;
        int index = (int) (y / mTextSize);
        if(index >= 0 && index < mIndex.length){
            Log.v("zgy","======index======="+index) ;
            selectItem(index);
        }

selectItem(int)方法中就是执行的回调方法。

5、实现悬浮框显示已经选中的索引内容

这里需要用到 WindowManager 容器,然需要现实的 View 附在这上面的就行,当手指按下的时候,让 View 显示出来,松开不显示就行了

        /*设置浮动选中的索引*/
        /*获取windowManager*/
        mWindowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        /*overly 视图,通过LayoutInflater 获取*/
        mFloatView = LayoutInflater.from(getContext()).inflate(R.layout.overlay_indexview,null) ;
        /*开始让其不可见*/
        mFloatView.setVisibility(INVISIBLE);
        /*转换 高度 和宽度为Sp*/
        mOverlyWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,70,getResources().getDisplayMetrics()) ;
        mOverlyHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,70,getResources().getDisplayMetrics()) ;
        post(new Runnable() {
            @Override
            public void run() {
                WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(mOverlyWidth,mOverlyHeight,
                        WindowManager.LayoutParams.TYPE_APPLICATION,
                        WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                        PixelFormat.TRANSLUCENT) ;
                mWindowManager.addView(mFloatView,layoutParams);
            }
        }) ;

同样的道理,如果需要改变显示的内容,就需要在调用回调的位置,为 View 中的 TextView 设置当前的索引内容。

好了此 View 的代码就这么多,

接下来就把引用他的 Xml 和浮动 View 的 Xml 也贴出来,

引用的布局文件

    <moon.wechat.view.IndexView
        android:layout_width="25dp"
        android:layout_height="match_parent"
        android:layout_alignParentRight="true"/>

浮动 View 的布局文件

<?xml version="1.0" encoding="utf-8"?>

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/overly_text"
    android:layout_width="70dp"
    android:layout_height="70dp"
    android:text="A"
    android:gravity="center"
    android:background="@drawable/bg_overly_text"
    android:textSize="40sp"
    android:textColor="#ffffffff"
    android:layout_gravity="center">

</TextView>

浮动 View 的背景

<?xml version="1.0" encoding="utf-8"?>

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item>
        <shape>
            <solid android:color="#88000000"/>
            <corners android:radius="5dp"/>
        </shape>
    </item>
</layer-list>

最后就是源码下载地址:

时间: 2024-08-04 14:13:58

Android 自定义 View 实现通讯录字母索引(仿微信通讯录)的相关文章

android自定义View之(七)------自定义控件组合仿actionbar控件

我们前面写了6个自定义view的样例,这都是全新自已画的控件.在这个样例中,我们来用几个现有的控件来组合成一个新的控件. 效果图: 我们用二个Button和一个TextView组合来成为一个actionbar,下面先来一个效果图: 关键代码: (1)res/layout/custom_action_bar.xml----组合控件布局文件 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android&quo

android自定义View之仿通讯录侧边栏滑动,实现A-Z字母检索

我们的手机通讯录一般都有这样的效果,如下图: OK,这种效果大家都见得多了,基本上所有的Android手机通讯录都有这样的效果.那我们今天就来看看这个效果该怎么实现. 一.概述 1.页面功能分析 整体上来说,左边是一个ListView,右边是一个自定义View,但是左边的ListView和我们平常使用的ListView还有一点点不同,就是在ListView中我对所有的联系人进行了分组,那么这种效果的实现最常见的就是两种思路: 1.使用ExpandableListView来实现这种分组效果 2.使

【Android自定义View实战】之仿百度加载动画,一种优雅的Loading方式

转载请注明出处:http://blog.csdn.net/linglongxin24/article/details/53470872 本文出自[DylanAndroid的博客] Android自定义View实战之仿百度加载动画一种优雅的Loading方式 第一个仿百度加载动画用ObjectAnimator属性动画操作ImageView的属性方法实现 第二个仿百度加载动画第二种实现方式用ValueAnimator原生的ondraw方法实现 第三个扔球动画-水平旋转动画 第四个扔球动画-垂直旋转动

android自定义View之(六)------高仿华为荣耀3C的圆形刻度比例图(ShowPercentView)

为什么写这篇文章: 显示当前的容量所占的比例,表现当前计划的进度,一般都会采用百分比的方式,而图形显示,以其一目了然的直观性和赏心悦目的美观形成为了我们的当然的首选. 在图形表示百分比的方法中,我们有用画圆的圆形进度条的方法<<android自定义View之(二)------简单进度条显示样例篇>>,也有用画弧形的进度条的方法<<android自定义View之(三)------视频音量调控样例>>,今天看到华为荣耀3C的一个界面: 个人觉得这个表示比例的圆形

【Android 仿微信通讯录 导航分组列表-上】使用ItemDecoration为RecyclerView打造带悬停头部的分组列表

[Android 仿微信通讯录 导航分组列表-上]使用ItemDecoration为RecyclerView打造带悬停头部的分组列表 一 概述 本文是Android导航分组列表系列上,因时间和篇幅原因分上下,最终上下合璧,完整版效果如下: 上部残卷效果如下:两个ItemDecoration,一个实现悬停头部分组列表功能,一个实现分割线(官方demo) 网上关于实现带悬停分组头部的列表的方法有很多,像我看过有主席的自定义ExpandListView实现的,也看过有人用一个额外的父布局里面套 Rec

android自定义View之NotePad出鞘记

现在我们的手机上基本都会有一个记事本,用起来倒也还算方便,记事本这种东东,如果我想要自己实现,该怎么做呢?今天我们就通过自定义View的方式来自定义一个记事本.OK,废话不多说,先来看看效果图. 整个页面还是很简单的. 1.自定义View的分类 OK,那么在正文开始之前,我想先来说说自定义View的分类,自定义View我们一共分为三类 1.自绘控件 自绘控件就是我们自定义View继承自已有控件,然后扩展其功能,之前两篇自定义View的博客(android自定义View之钟表诞生记,android

Android 自定义View——自定义点击事件

每个人手机上都有通讯录,这是毫无疑问的,我们通讯录上有一个控件,在通讯录的最左边有一列从"#"到"Z"的字母,我们通过滑动或点击指定的字母来确定联系人的位置,进而找到联系人.我们这一节就通过开发这个控件,来学如何自定义控件的点击事件. 通讯录列表查找控件界面绘制 首先我们需要先将控件的基本布局绘制出来,这里我们不在做详细的解释,在<Android 自定义View--自定义View控件 >博客中,我们已经详细讲解了如何绘制自定义控件的布局.通讯录列表查找控

Android自定义View(二、深入解析自定义属性)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51468648 本文出自:[openXu的博客] 目录: 为什么要自定义属性 怎样自定义属性 属性值的类型format 类中获取属性值 Attributeset和TypedArray以及declare-styleable ??在上一篇博客<Android自定义View(一.初体验)>中我们体验了自定义控件的基本流程: 继承View,覆盖构造方法 自定义属性 重写onMeasure方法测量宽

Android自定义View(CustomCalendar-定制日历控件)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/54020386 本文出自:[openXu的博客] 目录: 1分析 2自定义属性 3onMeasure 4onDraw 绘制月份 绘制星期 绘制日期及任务 5事件处理 源码下载 ??应项目需求,需要做一个日历控件,效果图如下: ???? ??接到需求后,没有立即查找是否有相关开源日历控件可用.系统日历控件是否能满足 ,第一反应就是这个控件该怎么画?谁叫咱自定义控件技术牛逼呢O(∩_∩)O哈哈~

Android 自定义View合集

自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/Mr-XiaoLiang 自定义控件三部曲 http://blog.csdn.net/harvic880925?viewmode=contents Android 从0开始自定义控件之View基础知识与概念 http://blog.csdn.net/airsaid/article/details/5