您好,登錄后才能下訂單哦!
前言
因為自己在做的一個小軟件里面需要用到從A-Z排序的ListView,所以自然而然的想到了微信的聯(lián)系人,我想要的就是那樣的效果。本來沒打算自己去寫,想要第三方寫好的東西,搜了幾個之后發(fā)現(xiàn)有的太復(fù)雜了,有的簡單是簡單,但是不符合我的要求,所以我就來個整合,把復(fù)雜性和簡單性合二為一。
實現(xiàn)
先來看效果圖吧:
要點分析
要實現(xiàn)這樣的效果需要考慮下面的幾個問題:
下面我們就解決這幾個問題,然后就可以出現(xiàn)上面的效果了。
【第一步】
我們需要先自定義一個類,就叫SlideBar吧,讓它繼承Button,然后我們覆蓋onDraw方法,繪制字母a-z就可以出現(xiàn)右邊字母欄的效果了。
看一下源碼:
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); // 默認(rèn)粗體 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坐標(biāo) float yPos = nInterval * i + nInterval; // 計算字母的Y坐標(biāo) 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方法,其他的內(nèi)容先不看,首先得到控件的寬和高,然后計算每一個字母應(yīng)該占據(jù)的高度為多少,然后在每一個字母所占空間的中間繪制該字母就可以了,代碼比較簡單,這一部分就不需要詳解了。
【第二步】
我們需要添加一個點擊事件,當(dāng)點擊SlideBar的時候,首先可以看到的是右邊欄被點擊的字母變大來區(qū)別于沒被點擊的字母,然后彈出一個類似Dialog的東西,顯示被點中的字母,這個效果也很好實現(xiàn)。
在SlideBar里面我們還需要覆蓋的一個方法是dispatchTouchEvent(),我們點中SlideBar之后,接下來的動作可能是滑動和抬起,我們需要對點擊之后的動作進(jìn)行響應(yīng),如果是抬起的話,那么顯示出來的類似dialog的東西就要消失,變大的字母也要回復(fù)原樣,如果是接著滑動,滑到某一個字母的時候,對應(yīng)的字母就要變大和顯示出來。
從上面的源碼可以看到,字母變大的效果很好實現(xiàn),把繪制被選中的字母的Paint對象的textsize的值變大就可以了,然后那個類似dialog的東西是用PopupWindow來實現(xiàn)的,當(dāng)點擊和滑動的時候就顯示PopupWindow,抬起或者滑出SlideBar范圍的時候就讓PopupWindow消失。最后需要注意的是invalidate()這個方法千萬不要忘記調(diào)用了,這個是用來進(jìn)行畫面的重繪。
【第三步】
我認(rèn)為最重要也是最難的就是漢字按A-Z的排序了。不過還好,這個已經(jīng)有人實現(xiàn)了,我們就來所謂的“拿來主義”吧。在工程里面有一個CharacterParser類,這個類封裝了對漢字轉(zhuǎn)拼音的操作,其中g(shù)etSelling(String s)方法的作用是傳入漢字字符串得到漢字的拼音,果然是好方法,我喜歡!!這樣我們就得到了要顯示的漢字字符串的拼音首字母,然后將所有的字符串按照字母進(jìn)行排序就可以得到一個從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,分別表示該對象是要顯示的正常對象還是字母分隔符對象,根據(jù)item_type的不同,我們在寫Adapter的getView方法的時候就可以返回不同的View對象,然后就可以實現(xiàn)效果圖中的正常的Item和字母分割符Item了。item_en表示的是name變量也就是漢字字符串的拼音字符串,主要是用來獲取首字母和進(jìn)行字符串之間的比較。
現(xiàn)在假設(shè)已經(jīng)有了一個List對象,里面保存了一些DataBean,那么問題來了,如何把這些DataBean對象按拼音字符串進(jìn)行排序以及如何在List對象里面添加表示字母分隔符的DataBean對象呢?
首先解決排序的問題,這個比較簡單:
這里用到了Collections的sort方法,這個方法有兩個參數(shù),一個就是帶排序的List對象,另一個是實現(xiàn)了Comparator接口的類的對象,用來說明如何進(jìn)行排序,用哪一個成員變量來進(jìn)行排序。
PinyinComparator這個類實現(xiàn)了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也就是拼音字符串來進(jìn)行排序,這樣實現(xiàn)起來比較方便,不需要自己去寫排序的算法了,當(dāng)然也不反對大家自己去實現(xiàn)排序。
經(jīng)過Collections的sort函數(shù)排序之后,現(xiàn)在List對象里面保存的DataBean對象已經(jīng)是按照A-Z進(jìn)行排序的了,現(xiàn)在我們要做的就是在這些對象里面插入一些用來表示字母分隔符DataBean的對象,這個實現(xiàn)應(yīng)該比較簡單,我用的方法比較笨/(ㄒ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); } }
這個類就實現(xiàn)了往List對象里面添加一些表示字母分隔符的對象,通過設(shè)置item_type變量的值不同,在Adapter里面根據(jù)這個值返回不同的View就可以實現(xiàn)不同的Item顯示。
好了,到現(xiàn)在只剩一個問題了,那就是點擊了字母之后,ListView設(shè)置該字母對應(yīng)的Item在第一個顯示,這個實現(xiàn)也不難,得到了被點中的字母之后,遍歷所有的DataBean對象,然后找到和當(dāng)前字母匹配的第一個字母分隔符對象,然后得到該Item的position的值,設(shè)置ListView被選中的Item的position為找到的Item的position即可。
終于,所有的問題解決了,看看現(xiàn)在自己能不能實現(xiàn)這樣的效果了呢?如果不行的話可以參考一個我的源碼。
小結(jié)
本來我也是對這個不是太懂,但是強(qiáng)迫自己去看源碼,因為網(wǎng)上的大神寫的東西可能并不是100%滿足自己的要求,所以自己能看懂源碼的話就可以自己去修改了。
源碼下載在這里。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。