1. 程式人生 > >Android通訊錄字母排序城市列表展示效果

Android通訊錄字母排序城市列表展示效果

本篇文章主要給大家介紹一個通訊錄列表字母A——Z排序展示的效果,其實很多場景都會用到,今天一個同事做城市列表也需要類似這樣的效果,於是乎我就給他簡單寫了一個demo,剛好藉此機會將此demo和效果展示給正在學習和需要用到該效果的android開發者。個人認為學習android是一個日積月累的過程,見得多了,寫的多了,自身能力自然會提升,所以建議廣大朋友可以平時多積累一些專案開發中常用的demo,這樣我相信在工作中能極大的提高開發效率。時間長了,也會慢慢發現自己可以寫一些簡單的demo,可能哪一天別人也在學習著自己寫的demo,這樣豈不是一件很開心的事情。

還是先上效果圖:
這裡寫圖片描述

這裡寫圖片描述


文章最底部有本篇文章Demo下載地址!

注意本篇文章以下幾點:

  1. Demo的展示資料儲存在本地assets中的drugs.xml中,通過解析XML檔案或許資料集,其實解析完成後依然是一個List,所以這點大可放心,資料庫定會返回集合資料的。
  2. 介面右邊A——Z通過自定義View實現一個SideBar,定義觸控事件介面OnTouchingLetterChangedListener並實現一個public void onTouchingLetterChanged(String s);方法,在使用者點選觸控右邊SideBar時判斷點選是否在SideBar控制元件上,然後設定點選的字母即可。
  3. 將獲取到的List集合通過拼音來進行A——Z排序展示,本篇文章用到”pinyin4j-2.5.0.jar”工具來進行轉換操作。
    pinyin4j-2.5.0.jar下載地址:http://download.csdn.net/detail/jaynm/9570829
  4. 自定義EditText,獲取EditText的DrawableRight,假如沒有設定我們就使用預設的圖片。

自定義SideBar實現右側A——Z列表,建立陣列String[]b,重寫onDraw()方法,獲取View對應的高度和寬度,然後獲取到每一個字母對應的高度,遍歷String[]b,給26個字母設定顏色、字型、型別等屬性並且進行繪製。

/**
 * 重寫onDraw()這個方法
 */
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    // 獲取焦點改變背景顏色.
    int height = getHeight();// 獲取對應高度
    int width = getWidth(); // 獲取對應寬度
    int singleHeight = height / b.length;/// 獲取每一個字母的高度

    for (int i = 0; i < b.length; i++) {
        paint.setColor(Color.parseColor("#CDCDCD"));
        // paint.setColor(Color.WHITE);
        paint.setTypeface(Typeface.DEFAULT_BOLD);
        paint.setAntiAlias(true);
        paint.setTextSize(20);
        // 選中的狀態
        if (i == choose) {
            paint.setColor(Color.parseColor("#29333d"));
            paint.setFakeBoldText(true);
        }
        // x座標等於中間-字串寬度的一半.
        float xPos = width / 2 - paint.measureText(b[i]) / 2;
        float yPos = singleHeight * i + singleHeight;
        canvas.drawText(b[i], xPos, yPos, paint);
        paint.reset();// 重置畫筆
    }

}

最重要的是需要重寫dispatchTouchEvent(MotionEvent event)方法進行使用者點選監聽。event.getAction()獲取手勢狀態,這裡多說一個事件傳遞機制的知識點內容:
MotionEvent 常見的動作常量:

  • public static final int ACTION_DOWN = 0;單點觸控動作

  • public static final int ACTION_UP = 1;單點觸控離開動作

  • public static final int ACTION_MOVE = 2;觸控點移動動作

  • public static final int ACTION_CANCEL = 3;觸控動作取消

  • public static final int ACTION_OUTSIDE = 4;觸控動作超出邊界

  • public static final int ACTION_POINTER_DOWN = 5;多點觸控動作

  • public static final int ACTION_POINTER_UP = 6;多點離開動作

Android事件傳遞機制前,明確android的兩大基礎控制元件型別:View和ViewGroup。View即普通的控制元件,沒有子佈局的,如Button、TextView. ViewGroup繼承自View,表示可以有子控制元件,如Linearlayout、Listview這些。而事件即MotionEvent,最重要的有3個:
(1)MotionEvent.ACTION_DOWN 按下View,是所有事件的開始
(2)MotionEvent.ACTION_MOVE 滑動事件
(3)MotionEvent.ACTION_UP 與down對應,表示擡起

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    final int action = event.getAction();
    final float y = event.getY();// 點選y座標
    final int oldChoose = choose;
    final OnTouchingLetterChangedListener listener = onTouchingLetterChangedListener;
    // 點選y座標所佔總高度的比例*b陣列的長度就等於點選b中的個數.
    final int c = (int) (y / getHeight() * b.length);

    switch (action) {
        case MotionEvent.ACTION_UP:
            setBackgroundDrawable(new ColorDrawable(0x00000000));
            choose = -1;//
            invalidate();
            if (mTextDialog != null) {
                mTextDialog.setVisibility(View.INVISIBLE);
            }
            break;
        default:
            setBackgroundResource(R.drawable.sidebar_background);
            if (oldChoose != c) {
                if (c >= 0 && c < b.length) {
                    if (listener != null) {
                        //回撥介面
                        listener.onTouchingLetterChanged(b[c]);
                    }
                    if (mTextDialog != null) {
                        mTextDialog.setText(b[c]);
                        mTextDialog.setVisibility(View.VISIBLE);
                    }
                    choose = c;
                    invalidate();
                }
            }
            break;
    }
    return true;
}

對於View來說,事件傳遞機制有兩個函式:dispatchTouchEvent負責分發事件,在dispatchTouchEvent裡又會呼叫onTouchEvent表示執行事件,或者說消費事件,結合原始碼分析其流程。事件傳遞的入口是View的dispatchTouchEvent()函式:
本demo中只區分MotionEvent.ACTION_UP事件和預設狀態,在預設狀態下判斷當前點選觸控的字母,然後OnTouchingLetterChangedListener介面回調當前點選的字母listener.onTouchingLetterChanged(b[c]);在ListView中更新列表資料。

// 設定右側SideBar觸控監聽
sideBar.setOnTouchingLetterChangedListener(new SideBar.OnTouchingLetterChangedListener() {

    @Override
    public void onTouchingLetterChanged(String s) {
        // 該字母首次出現的位置
        int position = adapter.getPositionForSection(s.charAt(0));
        if (position != -1) {
            sortListView.setSelection(position);
        }

    }
});

自定義EditText,新增刪除按鈕的引用Drawable mClearDrawable,然後獲取EditText的DrawableRight,假如沒有設定我們就使用預設的圖片。
因為我們不能直接給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焦點發生變化的時候,判斷裡面字串長度設定清除圖示的顯示與隱藏,這裡沒有什麼實際操作,就是在輸入框右邊新增一個刪除按鈕,當輸入框內有內容時可以進行刪除操作,內容為空時不顯示刪除引用圖片。
設定清除圖示的顯示與隱藏,呼叫setCompoundDrawables為EditText繪製上去。

@Override
public void onFocusChange(View v, boolean hasFocus) {
    if (hasFocus) {
        setClearIconVisible(getText().length() > 0);
    } else {
        setClearIconVisible(false);
    }
}

protected void setClearIconVisible(boolean visible) {
    Drawable right = visible ? mClearDrawable : null;
    setCompoundDrawables(getCompoundDrawables()[0],
            getCompoundDrawables()[1], right, getCompoundDrawables()[3]);
}

// 根據輸入框輸入值的改變來過濾搜尋,並根據輸入框中的值來過濾資料並更新ListView,注意這邊開啟一個新的執行緒來操作更新,因為大家都知道android4.0之後不允許主執行緒更新UI,所以如果直接更新可能會導致程式崩潰,所以還是建議大家開啟一個新的執行緒或者非同步來操作過濾資料並更新ListView的步驟

mClearEditText.addTextChangedListener(new TextWatcher() {

    @Override
    public void onTextChanged(CharSequence s, int start, int before,
            int count) {
        // 當輸入框裡面的值為空,更新為原來的列表,否則為過濾資料列表
        final String str = s.toString();
        new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                filterData(str);
            }
        }) {

        }.start();
    }

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

    }

    @Override
    public void afterTextChanged(Editable s) {
    }
});
/**
 * 根據輸入框中的值來過濾資料並更新ListView
 * 
 * @param filterStr
 */
private void filterData(String filterStr) {
    filterStr = filterStr.replace(" ", "");
    List<SortModel> filterDateList = new ArrayList<SortModel>();
    if (TextUtils.isEmpty(filterStr.replace(" ", ""))) {
        filterDateList = SourceDateList;
    } else {
        filterDateList.clear();
        String letter = Chinese.getInstance()
                .getCharacterPinYin(filterStr.charAt(0)).charAt(0)
                + "";
        String key = letter.toUpperCase();
        List<SortModel> list = map.get(key);
        for (SortModel sortModel : list) {
            String name = sortModel.getName();
            String pinYin = sortModel.getPinYin();
            if (!"".equals(pinYin)) {
                if (name.indexOf(filterStr.toString()) != -1
                        || pinYin.indexOf(filterStr.toLowerCase()) != -1) {
                    filterDateList.add(sortModel);
                }
            }
        }
    }
    Message msg = new Message();
    msg.obj = filterDateList;
    msg.what = 0;
    handler.sendMessage(msg);
}

大家可以看到將輸入框中的資料拿到之後filterStr = filterStr.replace(” “,
“”);進行清空空格的操作,防止第一個字元使用者輸入空格搜尋不到無法更新ListView的情況。
通過Chinese.getInstance().getCharacterPinYin(filterStr.charAt(0)).charAt(0)+
“”;獲取到輸入框的內容並轉換成拼音,然後從Map中得到List,重新填充filterDateList資料,使用handler傳送handler.sendMessage(msg)更新資料adapter.updateListView((List)
msg.obj)

好了,今天的分享就到這裡,其實個人覺得很簡單,使用起來也很容易,能夠很輕鬆的應用到自己專案中。
可能其中有很多地方寫的不太到位或者文章方式有待提高,還希望各位朋友能給我提出一些意見,在日後的分享部落格中能夠提高自身的能力。歡迎互相交流!
我是一個不喜歡讀書的人,更別提寫文章了。以前上CSDN找文章就是hongyang大神、郭霖大神、夏大神等等,因為之前剛開始工作的時候遇到問題找demo,基本上也就是看著他們的文章成長的。所以現在自己下定決心也要堅持寫部落格,堅持寫一些有用的東西,希望能幫助所有android初學者和愛好者們!同事也是為了自己做記錄,防止日後需要該功能或效果時忘記。所以鼓勵每一位開發者都能養成寫部落格的習慣!
愛學習,愛程式設計,愛生活!