聯絡人列表字母排序索引(一)
阿新 • • 發佈:2019-01-06
很久沒有寫部落格了,對自己又放鬆了很多。這篇部落格本來早就要寫了,遲遲拖到現在。今天這裡要說的是,聯絡人列表,分組帶索引,即聯絡人按字母順序排列並分組,右邊還有索引條。先看下效果:
就是這麼一個效果,想必大家都想知道是怎麼實現的吧。其實很簡單,接下來我會慢慢講解這個效果的實現。
首先我們分析這個聯絡人列表的組成:
1.聯絡人列表 ----- ListView
2.右邊索引-------自定義View
3.浮動視窗------WindowManager
4.獲取名稱的首字母----PinYin4j(已經將github上的封裝成了依賴包)
下面我將按照我的實現順序進行說明:
1.先自定義右邊索引。
2.設定浮動視窗。
3.新增列表資料,對資料進行排序,對列表進行分組。
4.新增列表與索引間的監聽。
一、自定義索引
這裡的索引,我把它命名為IndexView,IndexView 包含26個英文字母和“#”,每個字母所佔據的區域位置,IndexView按下時顯示黑色背景,每個字母都有文字顏色,觸控時浮動視窗顯示不同的字母。這裡,我們就有概念了,於是可以開始寫程式碼了:
1.先定義基本的成員變數
/*字母表陣列*/ private String letter = "ABCDEFGHIJKLMNOPQRSTUVWXYZ#"; private Paint mPaint; private int mTextColor; private int mTextSize; private int mPadding; private int mLetterWidth; private int mLetterHeight; //當被觸控時,繪製背景 private boolean isTouched; private int mBackgroundColor; private int mTransparentColor; //除了用於繪製外,更涉及到觸控事件 private ArrayList<Rect> mRects; private OnCharTouchEvent mListener; //上一次獲取的字母 private String mPreLetter;
2.然後開始寫邏輯,首先,我們必須繼承View,然後重寫構造方法,在構造方法中都呼叫一個init()方法:
public class IndexView extends View
public IndexView(Context context) { super(context); init(context); } public IndexView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public IndexView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); }
init裡面完成了什麼工作呢?主要是對一些成員變數的初始化,其中畫筆用來繪製文字的:
private void init(Context context) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mTextColor = Color.parseColor("#666666");
mBackgroundColor = Color.parseColor("#bbbbbb");
mTransparentColor = Color.parseColor("#00000000");
mRects = new ArrayList<>();
}
3.設定每個字母的位置,每個字母的位置都是一個矩形區域,我們用Rect表示,Rects裡面儲存所有的字母位置,因此,重寫onSizeChanged方法,onSizeChanged方法,是在measure之後呼叫的,因為在onMeasure裡面,我們會得到每個字母的寬和高,
因此這裡能夠得到每個字母的矩形位置:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mRects.clear();
for (int i = 0; i < letter.length(); i++) {
int x = 0;
int y = mLetterHeight * i;
Rect rect = new Rect(x, y, x + mLetterWidth, y + mLetterHeight);
mRects.add(rect);
}
}
4.重寫 onMeasure方法,調用於onSizeChanged之前,這裡面我們要完成測量當前自定義View的寬和高,以及每個字母的寬和高,這裡我們設定字母所佔的寬和高是一樣的,因此,當前自定義view的寬也就是字母的寬,然後按比例設定了字母的大小,間距等。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//widthMeasureSpec是父類中,呼叫該類的measure方法設定的
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
mLetterWidth = mLetterHeight = heightSize / letter.length();
mPadding = mLetterHeight / 5;
mTextSize = mLetterHeight - mPadding * 2;
setMeasuredDimension(mLetterWidth, heightSize);
}
5.重寫onDraw()方法,主要完成字母的繪製工作。先通過一個變數,判斷當前是否是觸控狀態,由此設定繪製背景;接著迴圈繪製每一個字母。
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(isTouched ? mBackgroundColor : mTransparentColor);
for (int i = 0; i < letter.length(); i++) {
char c = letter.charAt(i);
String s = String.valueOf(c);
mPaint.setColor(mTextColor);
mPaint.setTextSize(mTextSize);
mPaint.setTextAlign(Paint.Align.CENTER);
drawTextCenter(canvas, mPaint, s, mRects.get(i));
}
}
/**
* 將文字繪製在居中位置
*
* @param canvas
* @param paint
* @param s
* @param rect
*/
private void drawTextCenter(Canvas canvas, Paint paint, String s, Rect rect) {
Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
int baseline = rect.top + (rect.bottom - rect.top - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top;
paint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(s, rect.centerX(), baseline, paint);
}
drawTextCenter使用之前,需要設定畫筆居中,這個方法是根據文字的繪製原理,來將文字繪製在居中位置的。不懂的可以自行百度,谷歌。
到這裡,我們已經繪製完一個索引了。但是這是不夠的,我們還需要為它新增觸控事件。
6.重寫onTouchEvent方法。
/**
* 接收到事件,進行處理,並消費掉
*
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
doDown(event);
break;
case MotionEvent.ACTION_MOVE:
doMove(event);
break;
case MotionEvent.ACTION_UP:
doCancelOrUp();
break;
case MotionEvent.ACTION_CANCEL:
doCancelOrUp();
break;
}
return true;
}
直接返回了true,即所有的觸控事件到這裡,都將被消費掉。然後針對不同的事件進行處理。
ActionDown 事件:
private void doDown(MotionEvent event) {
isTouched = true;
int downX = (int) event.getX();
int downY = (int) event.getY();
String s = getTouchedLetter(downX, downY);
Log.v("@s", s + "");
if (mListener != null)
mListener.onTouch(s);
invalidate();
}
記錄位置,根據位置獲取對應的字母,然後設定監聽事件。(這裡,先理解,getTouchedLetter為通過座標位置獲取字母,mListener為監聽事件,用於回撥浮動視窗的)。
ActionMove事件:
/**
* 移動時,判斷選中的字母是否發生了變化,並進行回撥
*
* @param event
*/
private void doMove(MotionEvent event) {
isTouched = true;
int downX = (int) event.getX();
int downY = (int) event.getY();
String s = getTouchedLetter(downX, downY);
if (s == null)
return;
if (mListener != null) {
if (mPreLetter == null || (!mPreLetter.equalsIgnoreCase(s))) {
mListener.onLetterChanged(mPreLetter, s);
mPreLetter = s;
}
}
invalidate();
}
這裡每次都要記錄上一個的字母,如果字母發生變化,才回調。ActionUp|ActionCancel事件:
isTouched = false;
if (mListener != null)
mListener.onRelease();
invalidate();
觸控標誌位設定為false,並回調關閉浮動視窗
通過位置獲取字母,迴圈判斷座標位置,包含在哪個字母的Rect矩形位置中,通過這個位置獲取字母。
private String getTouchedLetter(int x, int y) {
for (int i = 0; i < mRects.size(); i++) {
Rect rect = mRects.get(i);
if (rect.contains(x, y)) {
return String.valueOf(letter.charAt(i));
}
}
return null;
}
最後一個是監聽介面:
public interface OnCharTouchEvent {
void onTouch(String s);
void onLetterChanged(String preLetter, String letter);
void onRelease();
}
到這裡,我們的效果是這樣的:
今天先到這裡,原始碼提前放出