天天看點

Android 自定義 View 實作通訊錄字母索引(仿微信通訊錄)

一、效果:我們看到很多軟體的通訊錄在右側都有一個字母索引功能,像微信,小米通訊錄,QQ,還有美團選擇地區等等。這裡我截了一張美團選擇城市的圖檔來看看;

Android 自定義 View 實作通訊錄字母索引(仿微信通訊錄)

我們今天就來實作圖檔中右側子產品的索引功能,包括觸摸顯示以選中的索引字母。這裡我的UI界面主要是參照微信的界面來實作,是以各位也可以對照微信來看看效果,什麼都不說了,隻有效果圖最具有說服力!

Android 自定義 View 實作通訊錄字母索引(仿微信通訊錄)

二、分析:

我們看到這樣的效果我們心理都回去琢磨,他是如何實作的;

首先,它肯定是通過自定義 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>           

最後就是源碼下載下傳位址:在這裡

繼續閱讀