android仿微信聯絡人索引列表
前言
因為自己在做的一個小軟體裡面需要用到從A-Z排序的ListView,所以自然而然的想到了微信的聯絡人,我想要的就是那樣的效果。本來沒打算自己去寫,想要第三方寫好的東西,搜了幾個之後發現有的太複雜了,有的簡單是簡單,但是不符合我的要求,所以我就來個整合,把複雜性和簡單性合二為一。
實現
先來看效果圖吧:
要點分析
要實現這樣的效果需要考慮下面的幾個問題:
- 右邊字母欄的繪製
- 點選效果的實現
- 漢字按A-Z的排序問題
- 正常的Item和字母分隔符的Item的實現
下面我們就解決這幾個問題,然後就可以出現上面的效果了。
【第一步】
我們需要先自定義一個類,就叫SlideBar吧,讓它繼承Button,然後我們覆蓋onDraw方法,繪製字母a-z就可以出現右邊字母欄的效果了。
看一下原始碼:
public class SlideBar extends Button{
public interface OnTouchAssortListener{
public void onTouchAssortListener(String s);
}
// 分類
private static final String[] ASSORT_TEXT = {"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 mPaint = new Paint();
private int mSelectIndex = -1;
private OnTouchAssortListener mListener = null;
private Activity mAttachActivity;
PopupWindow mPopupWindow = null;
View layoutView;
TextView text;
public SlideBar(Context context){
this(context,null);
}
public SlideBar(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public SlideBar(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
mAttachActivity = (Activity)context;
init(context);
}
private void init(Context context) {
layoutView = LayoutInflater.from(context).inflate(R.layout.alert_dialog_menu_layout, null);
text = (TextView) layoutView.findViewById(R.id.content);
}
public void setOnTouchAssortListener(OnTouchAssortListener listener) {
this.mListener = listener;
}
@Override
protected void onDraw(Canvas canvas){
super.onDraw(canvas);
int nHeight = getHeight();
int hWidth = getWidth();
int nAssortCount = ASSORT_TEXT.length;
int nInterval = nHeight / nAssortCount;
for (int i = 0; i < nAssortCount; i++){
mPaint.setAntiAlias(true); // 抗鋸齒
mPaint.setTypeface(Typeface.DEFAULT_BOLD); // 預設粗體
mPaint.setColor(Color.parseColor("#5f5f5f")); // 白色
if (i == mSelectIndex){
// 被選擇的字母改變顏色和粗體
mPaint.setColor(Color.parseColor("#3399ff"));
mPaint.setFakeBoldText(true);
mPaint.setTextSize(30);
}
float xPos = hWidth / 2 - mPaint.measureText(ASSORT_TEXT[i]) / 2; // 計算字母的X座標
float yPos = nInterval * i + nInterval; // 計算字母的Y座標
canvas.drawText(ASSORT_TEXT[i], xPos, yPos, mPaint);
mPaint.reset();
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
//判斷是哪一個字母被點選了
int nIndex = (int) (event.getY() / getHeight() * ASSORT_TEXT.length);
if (nIndex >= 0 && nIndex < ASSORT_TEXT.length){
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:
// 如果滑動改變
if (mSelectIndex != nIndex){
mSelectIndex = nIndex;
showCharacter(ASSORT_TEXT[mSelectIndex]);
if (mListener != null){
mListener.onTouchAssortListener(ASSORT_TEXT[mSelectIndex]);
}
}
break;
case MotionEvent.ACTION_DOWN:
mSelectIndex = nIndex;
showCharacter(ASSORT_TEXT[mSelectIndex]);
if (mListener != null){
mListener.onTouchAssortListener(ASSORT_TEXT[mSelectIndex]);
}
break;
case MotionEvent.ACTION_UP:
disShowCharacter();
mSelectIndex = -1;
break;
}
} else {
mSelectIndex = -1;
disShowCharacter();
}
invalidate();
return true;
}
private void disShowCharacter() {
if (mPopupWindow != null) {
mPopupWindow.dismiss();
mPopupWindow=null;
}
}
/**
* 顯示彈出的字元
* @param string
*/
private void showCharacter(String string){
if (mPopupWindow != null){
text.setText(string);
} else{
mPopupWindow = new PopupWindow(layoutView, 100, 100, false);
mPopupWindow.showAtLocation(mAttachActivity.getWindow().getDecorView(), Gravity.CENTER, 0, 0);
}
text.setText(string);
}
}
就先看onDraw方法,其他的內容先不看,首先得到控制元件的寬和高,然後計算每一個字母應該佔據的高度為多少,然後在每一個字母所佔空間的中間繪製該字母就可以了,程式碼比較簡單,這一部分就不需要詳解了。
【第二步】
我們需要新增一個點選事件,當點選SlideBar的時候,首先可以看到的是右邊欄被點選的字母變大來區別於沒被點選的字母,然後彈出一個類似Dialog的東西,顯示被點中的字母,這個效果也很好實現。
在SlideBar裡面我們還需要覆蓋的一個方法是dispatchTouchEvent(),我們點中SlideBar之後,接下來的動作可能是滑動和擡起,我們需要對點選之後的動作進行響應,如果是擡起的話,那麼顯示出來的類似dialog的東西就要消失,變大的字母也要回復原樣,如果是接著滑動,滑到某一個字母的時候,對應的字母就要變大和顯示出來。
從上面的原始碼可以看到,字母變大的效果很好實現,把繪製被選中的字母的Paint物件的textsize的值變大就可以了,然後那個類似dialog的東西是用PopupWindow來實現的,當點選和滑動的時候就顯示PopupWindow,擡起或者滑出SlideBar範圍的時候就讓PopupWindow消失。最後需要注意的是invalidate()這個方法千萬不要忘記呼叫了,這個是用來進行畫面的重繪。
【第三步】
我認為最重要也是最難的就是漢字按A-Z的排序了。不過還好,這個已經有人實現了,我們就來所謂的“拿來主義”吧。在工程裡面有一個CharacterParser類,這個類封裝了對漢字轉拼音的操作,其中getSelling(String s)方法的作用是傳入漢字字串得到漢字的拼音,果然是好方法,我喜歡!!這樣我們就得到了要顯示的漢字字串的拼音首字母,然後將所有的字串按照字母進行排序就可以得到一個從A-Z的有序的列表了。因為ListView一般都是繫結一個List物件,然後List物件裡面儲存一系列的物件,這裡我就用一個物件來說:
public class DataBean {
public static final int TYPE_CHARACTER = 0;
public static final int TYPE_DATA = 1;
private int item_type;
private String item_en;
private String name;
private String phone;
/*其他成員*/
public DataBean(String name,String phone,int type){
CharacterParser parser = CharacterParser.getInstance();
this.name = name;
this.phone = phone;
this.item_type = type;
this.item_en = parser.getSelling(name).toUpperCase().trim();
if(!item_en.matches("[A-Z]+")){
item_en = "#"+item_en;
}
}
/*
*省略geter和seter方法
*/
}
這個物件裡面需要注意的是兩個成員變數,item_type和item_en,分別表示該物件是要顯示的正常物件還是字母分隔符物件,根據item_type的不同,我們在寫Adapter的getView方法的時候就可以返回不同的View物件,然後就可以實現效果圖中的正常的Item和字母分割符Item了。item_en表示的是name變數也就是漢字字串的拼音字串,主要是用來獲取首字母和進行字串之間的比較。
現在假設已經有了一個List物件,裡面儲存了一些DataBean,那麼問題來了,如何把這些DataBean物件按拼音字串進行排序以及如何在List物件裡面新增表示字母分隔符的DataBean物件呢?
首先解決排序的問題,這個比較簡單:
這裡用到了Collections的sort方法,這個方法有兩個引數,一個就是帶排序的List物件,另一個是實現了Comparator介面的類的物件,用來說明如何進行排序,用哪一個成員變數來進行排序。
PinyinComparator這個類實現了Comparator:
public class PinyinComparator implements Comparator<DataBean>{
public int compare(DataBean o1, DataBean o2){
if (o1.getItem_en().equals("@")
|| o2.getItem_en().equals("#")){
return -1;
} else if (o1.getItem_en().equals("#")
|| o2.getItem_en().equals("@")) {
return 1;
} else {
return o1.getItem_en().compareTo(o2.getItem_en());
}
}
}
可以看到,兩個DataBean物件按照變數item_en也就是拼音字串來進行排序,這樣實現起來比較方便,不需要自己去寫排序的演算法了,當然也不反對大家自己去實現排序。
經過Collections的sort函式排序之後,現在List物件裡面儲存的DataBean物件已經是按照A-Z進行排序的了,現在我們要做的就是在這些物件裡面插入一些用來表示字母分隔符DataBean的物件,這個實現應該比較簡單,我用的方法比較笨/(ㄒoㄒ)/~~
public class ListUtil {
public static void sortList(List<DataBean> list){
List<DataBean> _List = new ArrayList<DataBean>();
Collections.sort(list, new PinyinComparator());
DataBean dataBean = new DataBean(getFirstCharacter(list.get(0).getItem_en()), "",DataBean.TYPE_CHARACTER);
String currentCharacter = getFirstCharacter(list.get(0).getItem_en());
_List.add(dataBean);
_List.add(list.get(0));
for(int i=1;i<list.size();i++){
if(getFirstCharacter(list.get(i).getItem_en()).compareTo(currentCharacter)!=0){
currentCharacter = getFirstCharacter(list.get(i).getItem_en());
dataBean = new DataBean(currentCharacter, "",DataBean.TYPE_CHARACTER);
_List.add(dataBean);
}
_List.add(list.get(i));
}
list.clear();
for(DataBean bean:_List){
list.add(bean);
}
}
public static String getFirstCharacter(String str){
return str.substring(0, 1);
}
}
這個類就實現了往List物件裡面新增一些表示字母分隔符的物件,通過設定item_type變數的值不同,在Adapter裡面根據這個值返回不同的View就可以實現不同的Item顯示。
好了,到現在只剩一個問題了,那就是點選了字母之後,ListView設定該字母對應的Item在第一個顯示,這個實現也不難,得到了被點中的字母之後,遍歷所有的DataBean物件,然後找到和當前字母匹配的第一個字母分隔符物件,然後得到該Item的position的值,設定ListView被選中的Item的position為找到的Item的position即可。
終於,所有的問題解決了,看看現在自己能不能實現這樣的效果了呢?如果不行的話可以參考一個我的原始碼。
小結
本來我也是對這個不是太懂,但是強迫自己去看原始碼,因為網上的大神寫的東西可能並不是100%滿足自己的要求,所以自己能看懂原始碼的話就可以自己去修改了。
【原始碼下載】