1. 程式人生 > >聯絡人列表字母排序索引(一)

聯絡人列表字母排序索引(一)

很久沒有寫部落格了,對自己又放鬆了很多。這篇部落格本來早就要寫了,遲遲拖到現在。今天這裡要說的是,聯絡人列表,分組帶索引,即聯絡人按字母順序排列並分組,右邊還有索引條。先看下效果:


就是這麼一個效果,想必大家都想知道是怎麼實現的吧。其實很簡單,接下來我會慢慢講解這個效果的實現。

首先我們分析這個聯絡人列表的組成:

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();
    }


到這裡,我們的效果是這樣的:



今天先到這裡,原始碼提前放出

原始碼