Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android自定義View之仿通訊錄側邊欄滑動,實現A-Z字母檢索

android自定義View之仿通訊錄側邊欄滑動,實現A-Z字母檢索

編輯:關於Android編程

我們的手機通訊錄一般都有這樣的效果,如下圖:

\

OK,這種效果大家都見得多了,基本上所有的android手機通訊錄都有這樣的效果。那我們今天就來看看這個效果該怎麼實現。

一.概述

1.頁面功能分析

整體上來說,左邊是一個ListView,右邊是一個自定義View,但是左邊的ListView和我們平常使用的ListView還有一點點不同,就是在ListView中我對所有的聯系人進行了分組,那麼這種效果的實現最常見的就是兩種思路:

1.使用ExpandableListView來實現這種分組效果

2.使用普通ListView,在構造Adapter時實現SectionIndexer接口,然後在Adapter中做相應的處理

這兩種方式都不難,都屬於普通控件的使用,那麼這裡我們使用第二種方式來實現,第一種方式的實現方法大家可以自行研究,如果你還不熟悉ExpandableListView的使用,可以參考我的另外兩篇博客:

1.使用ExpandableListView實現一個時光軸

2.android開發之ExpandableListView的使用,實現類似QQ好友列表

OK,這是我們左邊ListView的實現思路,右邊這個東東就是我們今天的主角,這裡我通過自定義一個View來實現,View中的A、B......#這些字符我都通過canvas的drawText方法繪制上去。然後重寫onTouchEvent方法來實現事件監聽。

2.要實現的效果

要實現的效果如上圖所示,但是大家看圖片有些地方可能還不太清楚,所以這裡我再強調一下:

1.左邊的ListView對數據進行分組顯示

2.當左邊ListView滑動的時候,右邊滑動控件中的文字顏色能夠跟隨左邊ListView的滑動自動變化

3.當手指在右邊的滑動控件上滑動時,手指滑動到的地方的文字顏色應當發生變化,同時在整個頁面的正中央有一個TextView顯示手指目前按下的文字

4.當手指按下右邊的滑動控件時,右邊的滑動控件背景變為灰色,手指松開後,右邊的滑動控件又變為透明色

二.左邊ListView分組效果的實現

無論多大的工程,我們都要將之分解為一個個細小的功能塊分步來實現,那麼這裡我們就先來看看左邊的ListView的分組的實現,這個效果實現之後,我們再來看看右邊的滑動控件該怎麼實現。

首先我需要在布局文件中添加一個ListView,這個很簡單,和普通的ListView一模一樣,我就不貼代碼了,另外,針對ListView中的數據集,我需要自建一個實體類,該實體類如下:

 

/**
 * Created by wangsong on 2016/4/24.
 */
public class User {
    private int img;
    private String username;
    private String pinyin;
    private String firstLetter;

    public User() {
    }

    public String getFirstLetter() {
        return firstLetter;
    }

    public void setFirstLetter(String firstLetter) {
        this.firstLetter = firstLetter;
    }

    public int getImg() {
        return img;
    }

    public void setImg(int img) {
        this.img = img;
    }

    public String getPinyin() {
        return pinyin;
    }

    public void setPinyin(String pinyin) {
        this.pinyin = pinyin;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public User(String firstLetter, int img, String pinyin, String username) {
        this.firstLetter = firstLetter;
        this.img = img;
        this.pinyin = pinyin;
        this.username = username;
    }
}

username用來存儲用戶名,img表示用戶圖像的資源id(這裡我沒有准備相應的圖片,大家有興趣可以自行添加),pinyin表示用戶姓名的拼音,firstLetter表示用戶姓名拼音的首字母,OK ,就這麼簡單的幾個屬性。至於數據源,我在strings.xml文件中添加了許多數據,這裡就不貼出來了,大家可以直接在文末下載源碼看。知道了數據源,知道了實體類,我們來看看在MainActivity中怎麼樣來初始化數據:

 

 

    private void initData() {
        list = new ArrayList<>();
        String[] allUserNames = getResources().getStringArray(R.array.arrUsernames);
        for (String allUserName : allUserNames) {
            User user = new User();
            user.setUsername(allUserName);
            String convert = ChineseToPinyinHelper.getInstance().getPinyin(allUserName).toUpperCase();
            user.setPinyin(convert);
            String substring = convert.substring(0, 1);
            if (substring.matches("[A-Z]")) {
                user.setFirstLetter(substring);
            }else{
                user.setFirstLetter("#");
            }
            list.add(user);
        }
        Collections.sort(list, new Comparator() {
            @Override
            public int compare(User lhs, User rhs) {
                if (lhs.getFirstLetter().contains("#")) {
                    return 1;
                } else if (rhs.getFirstLetter().contains("#")) {
                    return -1;
                }else{
                    return lhs.getFirstLetter().compareTo(rhs.getFirstLetter());
                }
            }
        });
    }

首先創建一個List集合用來存放所有的數據,然後從strings.xml文件中讀取出來所有的數據,遍歷數據然後存儲到List集合中,在遍歷的過程中,我通過ChineseToPinyinHelper這個工具類來將中文轉為拼音,然後截取拼音的第一個字母,如果該字母是A~Z,那麼直接設置給user對象的firstLetter屬性,否則user對象的firstLetter屬性為一個#,這是由於我的數據源中有一些不是以漢字開頭的姓名,而是以其他字符開頭的姓名,那麼我將這些統一歸為#這個分組。

 

OK,數據源構造好之後,我還需要對List集合進行一個簡單的排序,那麼這個排序是Java中的操作,我這裡就不再贅述。

構造完數據源之後,接著就該是構造ListView的Adapter了,我們來看看這個怎麼做,先來看看源碼:

 

/**
 * Created by wangsong on 2016/4/24.
 */
public class MyAdapter extends BaseAdapter implements SectionIndexer {
    private List list;
    private Context context;
    private LayoutInflater inflater;

    public MyAdapter(Context context, List list) {
        this.context = context;
        this.list = list;
        inflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        return list.size();
    }

    @Override
    public Object getItem(int position) {
        return list.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if (convertView == null) {
            convertView = inflater.inflate(R.layout.listview_item, null);
            holder = new ViewHolder();
            holder.showLetter = (TextView) convertView.findViewById(R.id.show_letter);
            holder.username = (TextView) convertView.findViewById(R.id.username);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        User user = list.get(position);
        holder.username.setText(user.getUsername());
        //獲得當前position是屬於哪個分組
        int sectionForPosition = getSectionForPosition(position);
        //獲得該分組第一項的position
        int positionForSection = getPositionForSection(sectionForPosition);
        //查看當前position是不是當前item所在分組的第一個item
        //如果是,則顯示showLetter,否則隱藏
        if (position == positionForSection) {
            holder.showLetter.setVisibility(View.VISIBLE);
            holder.showLetter.setText(user.getFirstLetter());
        } else {
            holder.showLetter.setVisibility(View.GONE);
        }
        return convertView;
    }

    @Override
    public Object[] getSections() {
        return new Object[0];
    }

    //傳入一個分組值[A....Z],獲得該分組的第一項的position
    @Override
    public int getPositionForSection(int sectionIndex) {
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).getFirstLetter().charAt(0) == sectionIndex) {
                return i;
            }
        }
        return -1;
    }

    //傳入一個position,獲得該position所在的分組
    @Override
    public int getSectionForPosition(int position) {
        return list.get(position).getFirstLetter().charAt(0);
    }

    class ViewHolder {
        TextView username, showLetter;
    }
}

這個Adapter大部分還是和我們之前的Adapter一樣的,只不過這裡實現了SectionIndexer接口,實現了這個接口,我們就要實現該接口中的三個方法,分別是getSections(),getPositionForSection(),getSectionForPosition()這三個方法,我們這裡用到的主要是後面這兩個方法,那我來詳細說一下:

 

1.getPositionForSection(int sectionIndex)

這個方法接收一個int類型的參數,該參數實際上就是指我們的分組,我們在這裡傳入分組的值【A.....Z】,然後我們在方法中通過自己的計算,返回該分組中第一個item的position。

2.getSectionForPosition(int position)

這個方法接收一個int類型的參數,該參數實際上就是我們的ListView即將要顯示的item的position,我們通過傳入這個position,可以獲得該position的item所屬的分組,然後再將這個分組的值返回。

說了這麼多,大家可能有疑問了,我為什麼要實現這個接口呢?大家來看看我的item的布局文件:

 




    

    

        

        
    

在我的item的布局文件中,我所有的item實際上都是一樣的,都有一個顯示分組數據的TextView,因此我需要在Adapter的getView方法中根據所顯示的item的不同來確定是否將顯示分組的TextView隱藏掉。所以我們再回過頭來看看我的ListView中的getView方法,getView前面的寫法沒啥好說的,和普通ListView都一樣,我們主要來看看這幾行:

 

 

//獲得當前position是屬於哪個分組
        int sectionForPosition = getSectionForPosition(position);
        //獲得該分組第一項的position
        int positionForSection = getPositionForSection(sectionForPosition);
        //查看當前position是不是當前item所在分組的第一個item
        //如果是,則顯示showLetter,否則隱藏
        if (position == positionForSection) {
            holder.showLetter.setVisibility(View.VISIBLE);
            holder.showLetter.setText(user.getFirstLetter());
        } else {
            holder.showLetter.setVisibility(View.GONE);
        }

我首先判斷當前顯示的item是屬於哪個分組的,然後獲得這個分組中第一個item的位置,最後判斷我當前顯示的item的position到底是不是它所在分組的第一個item,如果是的話,那麼就將showLetter這個TextView顯示出來,同時顯示出相應的分組信息,否則將這個showLetter隱藏。就是這麼簡單。做完這些之後,我們在Activity中再來簡單的添加兩行代碼:

 

 

ListView listView = (ListView) findViewById(R.id.lv);
        MyAdapter adapter = new MyAdapter(this, list);
        listView.setAdapter(adapter);

這個時候左邊的分組ListView就可以顯示出來了。就是這麼簡單。

 

三.右邊滑動控件的實現

右邊這個東東很明顯是一個自定義View,那我們就一起來看看這個自定義View吧。

首先這個自定義控件繼承自View,繼承自View,需要實現它裡邊的構造方法,關於這三個構造方法的解釋大家可以查看我的另一篇博客android自定義View之鐘表誕生記,這裡對於構造方法我不再贅述。在這個自定義View中,我需要首先聲明5個變量,如下:

 

    //當前手指滑動到的位置
    private int choosedPosition = -1;
    //畫文字的畫筆
    private Paint paint;
    //右邊的所有文字
    private String[] letters = new String[]{"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", "#"};
    //頁面正中央的TextView,用來顯示手指當前滑動到的位置的文本
    private TextView textViewDialog;
    //接口變量,該接口主要用來實現當手指在右邊的滑動控件上滑動時ListView能夠跟著滾動
    private UpdateListView updateListView;

五個變量的作用我在注釋中已經說的很詳細了。OK,變量聲明完成之後,我還要初始化一些變量,變量的初始化當然放在構造方法中來進行了:

 

 

public LetterIndexView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setTextSize(24);
    }

OK,這裡要初始化的實際上只有paint一個變量。

 

准備工作做完之後,接下來就是onDraw了,代碼如下:

 

    @Override
    protected void onDraw(Canvas canvas) {
        int perTextHeight = getHeight() / letters.length;
        for (int i = 0; i < letters.length; i++) {
            if (i == choosedPosition) {
                paint.setColor(Color.RED);
            } else {
                paint.setColor(Color.BLACK);
            }
            canvas.drawText(letters[i], (getWidth() - paint.measureText(letters[i])) / 2, (i + 1) * perTextHeight, paint);
        }
    }

在繪制的時候,我需要首先獲得每一個文字所占空間的大小,每一個文本的可用高度應該是總高度除以文字的總數,然後,通過一個for循環將26個字母全都畫出來。在畫的時候,如果這個文本所處的位置剛好就是我手指按下的位置,那麼該文本的顏色為紅色,否則為黑色,最後的drawText不需要我再說了吧。

 

繪制完成之後,就是重寫onTouchEvent了,如下:

 

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int perTextHeight = getHeight() / letters.length;
        float y = event.getY();
        int currentPosition = (int) (y / perTextHeight);
        String letter = letters[currentPosition];
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                setBackgroundColor(Color.TRANSPARENT);
                if (textViewDialog != null) {
                    textViewDialog.setVisibility(View.GONE);
                }
                break;
            default:
                setBackgroundColor(Color.parseColor("#cccccc"));
                if (currentPosition > -1 && currentPosition < letters.length) {
                    if (textViewDialog != null) {
                        textViewDialog.setVisibility(View.VISIBLE);
                        textViewDialog.setText(letter);
                    }
                    if (updateListView != null) {
                        updateListView.updateListView(letter);
                    }
                    choosedPosition = currentPosition;
                }
                break;
        }
        invalidate();
        return true;
    }

對於右邊的滑動控件的事件操作我整體上可以分為兩部分,手指抬起分為一類,其他所有的操作歸為一類。那麼當控件感知到我手指的操作事件之後,它首先需要知道我手指當前所點擊的item是什麼,那麼這個值要怎麼獲取呢?我可以先獲得到手指所在位置的Y坐標,然後除以每一個文字的高度,就知道當前手指點擊位置的position,然後從letters數組中讀取出相應的值即可。知道了當前點擊了哪個字母之後,剩下的工作就很簡單了,修改控件的背景顏色,然後將相應的字母顯示在TextView上即可,然後把當前的position傳給choosedPosition,最後調用invalidate()方法重繪控件。重繪控件時由於choosedPosition的值已經發生了變化,所以相應的文本顏色也會改變。另外,我希望手指在右邊控件滑動時,ListView也能跟著滾動,這個毫無疑問使用接口回調,具體大家看代碼,簡單的東西不贅述。最後,我希望ListView滾動時,右邊控件中文本的顏色應該實時更新,那麼這個也很簡單,在自定義View中公開一個方法即可,如下:

 

 

    public void updateLetterIndexView(int currentChar) {
        for (int i = 0; i < letters.length; i++) {
            if (currentChar == letters[i].charAt(0)) {
                choosedPosition = i;
                invalidate();
                break;
            }
        }
    }

最後再來看一眼Activity中現在的代碼:

 

 

TextView textView = (TextView) findViewById(R.id.show_letter_in_center);
        final LetterIndexView letterIndexView = (LetterIndexView) findViewById(R.id.letter_index_view);
        letterIndexView.setTextViewDialog(textView);
        letterIndexView.setUpdateListView(new LetterIndexView.UpdateListView() {
            @Override
            public void updateListView(String currentChar) {
                int positionForSection = adapter.getPositionForSection(currentChar.charAt(0));
                listView.setSelection(positionForSection);
            }
        });
        listView.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {

            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                int sectionForPosition = adapter.getSectionForPosition(firstVisibleItem);
                letterIndexView.updateLetterIndexView(sectionForPosition);
            }
        });

就是這麼簡單。

 

以上。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved