Android 側欄A-Z的快速滑動搜索(一)

Android側欄A-Z的快速滑動搜索想必大家並不陌生,很多應用裏面都有這樣的功能出現。最常見的如電話聯繫人列表、好友列表、城市列表等等。快速搜索就是方便我們快速定位到我們要找的信息。比如我們想找姓氏爲劉的,那麼我們只需要點擊一下L就能搜索到好友裏面的姓氏拼音首字母以L開頭的,當然姓氏劉也就搜出來的,有可能以L開頭的姓氏比較多,比如李、郎、魯、柳、雷、劉、林、藍、呂等,而我們想找到劉這個姓我們可以讓含有L的這些姓氏全都顯示出來供我們來選擇。
要想實現這些快速搜索首先我們要用側邊要有A-Z的選項供我們選擇。
這裏寫圖片描述
那麼我們就先來說一說側邊欄#和A-Z的實現。實現這個功能其實也很簡單,我們需要自己自定義一個控件,點擊查看自定義控件。自定義控件離不了onDraw。我們這次自定義控件不需要onMeasure和onLayout這兩個。但是我們需要實onSizeChanged,它是在佈局發生變化時的回調函數,由於我們的側欄是滑動搜索onTouchEvent也是離不了的。
那麼我們來實現一下側欄的功能吧。先來一個最簡單的也是必不可少的決定側邊欄要顯示的內容的一步

private String[] mSideLetter = { "#","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",};

我們要顯示的這些內容既然已經確定了,那麼我們就要爲這些內容分配相應的高度和寬度。每個字母的高度是有屏幕的高度來決定的。我們根據獲取到的屏幕的高度/字母的數量 = 每個字母顯示的高度。寬度怎麼辦呢?因爲我們的這些內容是不可能自己去每一個字母一換行的,而我們又想要的是每個字母是一行。我們可以根據字母的數量來決定顯示的行數,那麼我們就可以根據行數來決定每行要顯示的字母是什麼。比如#在第一行那麼當i爲0 的時候我們就可以設置這行顯示的爲#

private int getTextHeight(String text) {
        //獲取文本的高度
        Rect bounds = new Rect();
        paint.getTextBounds(text,0,text.length(), bounds);
        return bounds.height();
    }
@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (int i = 0; i < mSideLetter .length; i++) {
            float x = letterWidth/2;
            float y = letterHeight/2 + getTextHeight(mSideLetter [i])/2 + i*letterHeight;

            paint.setColor(lastIndex==i?Color.BLACK:Color.WHITE);

            canvas.drawText(mSideLetter [i], x, y, paint);
        }
    }

至於爲什麼float x = letterWidth/2。首先要清楚drawText不是以文字的左上角開始繪製,而是以baseLine爲基線繪製。如果我們要想讓字母居中,那麼我們就需要X的座標點在字母的中間位置,也就是字母寬度的一半了。高度float y = letterHeight/2 + getTextHeight(mSideLetter [i])/2 + i*letterHeight; letterHeight/2 + getTextHeight(mSideLetter [i])/2所起的作用就是文字居中顯示, i*letterHeight則是顯示的高度。怎麼理解呢?比如說X的顯示位置,X的高度已經獲取到了,那麼X的顯示位置高度就是在前面所有的字母的高度之上再加上自己的高度就是顯示的位置y了。

package lyx.robert.quicksearch.view;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Paint.Align;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import lyx.robert.quicksearch.utils.DisplayUtil;

public class SideLetterBar extends View{
    private String[] mSideLetter  = { "#","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 Paint paint;
    private int letterWidth;
    private float letterHeight;
    public SideLetterBar(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context);
    }

    public SideLetterBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public SideLetterBar(Context context) {
        super(context);
        init(context);
    }

    private void init(Context context){
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);//設置抗鋸齒
        paint.setColor(Color.WHITE);
        paint.setTextSize(new DisplayUtil().dip2px(context,16));
        paint.setTextAlign(Align.CENTER);////設置文本居中對齊
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        letterWidth = getMeasuredWidth();
        //得到一個字母要顯示的高度
        letterHeight = getMeasuredHeight()*1f/mSideLetter .length;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        for (int i = 0; i < mSideLetter .length; i++) {
            float x = letterWidth/2;
            float y = letterHeight/2 + getTextHeight(mSideLetter [i])/2 + i*letterHeight;

            paint.setColor(lastIndex==i?Color.BLACK:Color.WHITE);

            canvas.drawText(mSideLetter [i], x, y, paint);
        }
    }
    private int lastIndex = -1;//標記上次的觸摸字母的索引
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_MOVE:
            float touchY = event.getY();
            int index = (int) (touchY/letterHeight);//得到字母對應的索引
            if(lastIndex!=index){
                //判斷當前觸摸字母跟上次觸摸的是不是同一個字母
                if(index>=0 && index<mSideLetter .length){
                    //判斷觸摸的範圍是否在所有字母的高度之內
                    if(listener!=null){
                        listener.onTouchLetter(mSideLetter [index]);
                    }
                }
            }
            lastIndex = index;
            break;
        case MotionEvent.ACTION_UP:
            //滑動觸目事件消失,標記的位置索引就要進行重置
            lastIndex = -1;
            break;
        }
        //刷新界面進行從新繪製
        invalidate();
        return true;
    }

    /**
     * 獲取文本的高度
     * @param text
     * @return
     */
    private int getTextHeight(String text) {
        //獲取文本的高度
        Rect bounds = new Rect();
        paint.getTextBounds(text,0,text.length(), bounds);
        return bounds.height();
    }

    private OnTouchLetterListener listener;
    public void setOnTouchLetterListener(OnTouchLetterListener listener){
        this.listener = listener;
    }
    /**
     * 觸摸字母的監聽事件
     *
     */
    public interface OnTouchLetterListener{
        void onTouchLetter(String letter);
    }

}

下面我們再來看看搜索框的實現代碼,搜索框內含有刪除按鈕的功能,以及EditText的文字輸入長度變化的監聽,網上有很多,這裏就不介紹了

package lyx.robert.quicksearch.view;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnFocusChangeListener;
import android.widget.EditText;

import lyx.robert.quicksearch.R;

public class ClearEditText extends EditText implements  
        OnFocusChangeListener, TextWatcher {
    /**
     * 刪除按鈕的引用
     */
    private Drawable mClearDrawable;

    public ClearEditText(Context context) {
        this(context, null);
    }

    public ClearEditText(Context context, AttributeSet attrs) {
        //這裏構造方法也很重要,不加這個很多屬性不能再XML裏面定義
        this(context, attrs, android.R.attr.editTextStyle);
    }

    public ClearEditText(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }


    private void init() {
        //獲取EditText的DrawableRight,假如沒有設置我們就使用默認的圖片
        mClearDrawable = getCompoundDrawables()[2];
        if (mClearDrawable == null) {
            mClearDrawable = getResources()
                    .getDrawable(R.drawable.icon_delete);
        }
        mClearDrawable.setBounds(0, 0, mClearDrawable.getIntrinsicWidth(), mClearDrawable.getIntrinsicHeight());
        setClearIconVisible(false);
        setOnFocusChangeListener(this);
        addTextChangedListener(this);
    }


    /**
     * 因爲我們不能直接給EditText設置點擊事件,所以我們用記住我們按下的位置來模擬點擊事件
     * 當我們按下的位置 在  EditText的寬度 - 圖標到控件右邊的間距 - 圖標的寬度  和
     * EditText的寬度 - 圖標到控件右邊的間距之間我們就算點擊了圖標,豎直方向沒有考慮
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (getCompoundDrawables()[2] != null) {
            if (event.getAction() == MotionEvent.ACTION_UP) {
                boolean touchable = event.getX() > (getWidth()
                        - getPaddingRight() - mClearDrawable.getIntrinsicWidth())
                        && (event.getX() < ((getWidth() - getPaddingRight())));
                if (touchable) {
                    this.setText("");
                }
            }
        }

        return super.onTouchEvent(event);
    }

    /**
     * 當ClearEditText焦點發生變化的時候,判斷裏面字符串長度設置清除圖標的顯示與隱藏
     */
    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        if (hasFocus) {
            setClearIconVisible(getText().length() > 0);
        } else {
            setClearIconVisible(false);
        }
    }


    /**
     * 設置清除圖標的顯示與隱藏,調用setCompoundDrawables爲EditText繪製上去
     * @param visible
     */
    protected void setClearIconVisible(boolean visible) {
        Drawable right = visible ? mClearDrawable : null;
        setCompoundDrawables(getCompoundDrawables()[0],
                getCompoundDrawables()[1], right, getCompoundDrawables()[3]);
    }

    /**
     * 當輸入框裏面內容發生變化的時候回調的方法
     */
    @Override
    public void onTextChanged(CharSequence s, int start, int count,
                              int after) {
        setClearIconVisible(s.length() > 0);
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count,
                                  int after) {

    }

    @Override
    public void afterTextChanged(Editable s) {

    }

}

佈局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activities.MainActivity" >

    <ListView
        android:layout_below="@+id/et_clear"
        android:id="@+id/lv_contact"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </ListView>
    <TextView
        android:id="@+id/tv_alphabet"
        android:layout_below="@+id/et_clear"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:textSize="16dp"
        android:textColor="@android:color/white"
        android:background="#B51421"/>
    <lyx.robert.quicksearch.view.ClearEditText
        android:id="@+id/et_clear"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:paddingLeft="15dp"
        android:paddingRight="15dp"
        android:textSize="18dp"
        android:textColor="@android:color/white"
        android:background="@drawable/search_bg"
        android:layout_width="match_parent"
        android:layout_height="40dp" />
    <lyx.robert.quicksearch.view.SideLetterBar
        android:layout_below="@id/tv_alphabet"
        android:id="@+id/sideLetterBar"
        android:layout_alignParentRight="true"
        android:layout_width="30dp"
        android:layout_height="match_parent"
        android:background="#14b58d" />


    <RelativeLayout
        android:id="@+id/rel_notice"
        android:visibility="invisible"
        android:layout_alignParentRight="true"
        android:layout_width="wrap_content"
        android:layout_marginTop="90dp"
        android:layout_marginRight="80dp"
        android:layout_height="wrap_content">
        <TextView
            android:id="@+id/tv_notice"
            android:layout_width="40dp"
            android:layout_height="wrap_content"
            android:textColor="#FFFFFF"
            android:padding="10dp"
            android:textSize="18dp"
            android:background="#00ff8c"
            />
        <ListView
            android:layout_below="@id/tv_notice"
            android:background="#FF46285b"
            android:divider="@null"
            android:id="@+id/lv_alphabet"
            android:layout_width="40dp"
            android:scrollbars="none"
            android:layout_height="wrap_content">
        </ListView>
    </RelativeLayout>
</RelativeLayout>

側滑刪除的實現點擊前往Android 側欄A-Z的快速滑動搜索(二)

點擊前往csdn下載源碼

點擊前往GitHub下載源碼

帥哥/美女,如果對您有幫助在GitHub上面點下star唄哦!再csdn上面給個好評也行啊!也不枉費我忙活了這些時間了。謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章