聽(tīng)音樂(lè)一直用的是網(wǎng)易云音樂(lè),原來(lái)我就吐槽為什么列表沒(méi)有字母索引,導(dǎo)致找一首歌很麻煩。不過(guò)最近滑動(dòng)列表時(shí),發(fā)現(xiàn)右邊有了字母索引條,不知是原來(lái)沒(méi)發(fā)現(xiàn)呢,還是網(wǎng)易新加的。既然醬紫,那就動(dòng)手來(lái)實(shí)現(xiàn)一下這個(gè)字母索引條效果吧
網(wǎng)易云音樂(lè)字母索引條效果

網(wǎng)易云音樂(lè)列表.gif
國(guó)際慣例,先上我們自己實(shí)現(xiàn)的效果圖
- 實(shí)現(xiàn)了網(wǎng)易云音樂(lè)字母索引的效果基礎(chǔ)上,添加了選中字母變色的效果

LetterIndexView.gif
效果實(shí)現(xiàn)分析
其實(shí)這個(gè)效果和我自定義View第二篇 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的RatingBar評(píng)分條的效果大同小異,一點(diǎn)區(qū)別就是RatingBar那個(gè)是用圖片繪制并且是水平方向的,而這個(gè)效果是垂直方向的畫(huà)文字。實(shí)現(xiàn)思路可以去看下上一篇。
Talk is Cheap,下面直接上代碼。看一下關(guān)鍵的實(shí)現(xiàn)
-
定義自定義屬性
<declare-styleable name="LetterIndexView"> <!--字母文字顏色--> <attr name="defaultTextColor" format="color"/> <!--選中時(shí)字母文字顏色--> <attr name="selectTextColor" format="color"/> <!--字母文字大小--> <attr name="letterTextSize" format="dimension"/> </declare-styleable> -
確定控件的寬高
- 寬度值為左右padding值 + 字母的寬度
- 高度值一般為match_parent,這里不做處理
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int oneLetterWidth = (int) mDefaultPaint.measureText("A"); //字母的寬度取決于畫(huà)筆 int width = getPaddingLeft() + getPaddingRight() + oneLetterWidth; int height = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(width, height); } -
循環(huán)繪制26個(gè)英文字母
- itemHeight是每個(gè)字母區(qū)域的高度,控件高減去padding值 / 字母?jìng)€(gè)數(shù)
- 繪制字母時(shí)要讓字母都水平居中顯示,這里要計(jì)算每個(gè)字母繪制的x坐標(biāo)
- 觸摸的字母和未觸摸的字母使用兩種顏色的畫(huà)筆繪制,實(shí)現(xiàn)觸摸變色
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); itemHeight = (getHeight() - getPaddingBottom() - getPaddingTop()) / letters.length; for (int i = 0; i < letters.length; i++) { //每個(gè)字母x的坐標(biāo) int x = (int) (getWidth() / 2 - mDefaultPaint.measureText(letters[i]) / 2); Paint.FontMetricsInt fontInt = mDefaultPaint.getFontMetricsInt(); //字母中心位置 int letterCenterY = itemHeight * i + itemHeight / 2 + getPaddingTop(); int dy = (fontInt.bottom - fontInt.top) / 2 - fontInt.bottom; int baseLine = letterCenterY + dy; if (i == mTouchIndex) { canvas.drawText(letters[i], x, baseLine, mSelectPaint); } else { canvas.drawText(letters[i], x, baseLine, mDefaultPaint); } Log.d("m1Ku", letters[i]); } } -
定義接口回調(diào),回調(diào)結(jié)果和狀態(tài)
- letter表示選中的字母
- isShow表示用戶是否在觸摸滑動(dòng)
public interface OnLetterTouchListener { void onTouchIndex(String letter, boolean isShow); } public void setOnLetterTouchListener(OnLetterTouchListener onLetterTouchListener) { this.onLetterTouchListener = onLetterTouchListener; } -
監(jiān)聽(tīng)用戶的觸摸和滑動(dòng),并回調(diào)結(jié)果
- 觸摸滑動(dòng)時(shí)isShow返回true,抬起時(shí)返回false。以便界面中處理提示字母顯示和隱藏
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: float y = event.getY(); int index = (int) (y / itemHeight); if (index < 0) { index = 0; } if (index > letters.length - 1) { index = letters.length - 1; } Log.d("m1Ku", "index = " + index); if (index == mTouchIndex) { return true; } mTouchIndex = index; if (onLetterTouchListener != null) onLetterTouchListener.onTouchIndex(letters[mTouchIndex], true); Log.e("m1Ku", "mTouchIndex = " + mTouchIndex); invalidate(); break; case MotionEvent.ACTION_UP: if (onLetterTouchListener != null) onLetterTouchListener.onTouchIndex(letters[mTouchIndex], false); break; } return true; } -
Activity中使用和測(cè)試
- 根據(jù)isShow控制界面提示字母顯示和隱藏,并做了延遲消失
LetterIndexView letterIndexView = (LetterIndexView) findViewById(R.id.letterIndexView); letterIndexView.setOnLetterTouchListener(new LetterIndexView.OnLetterTouchListener() { @Override public void onTouchIndex(String letter, boolean isShow) { if (isShow) { tvIndicator.setVisibility(View.VISIBLE); tvIndicator.setText(letter); } else { handler.postDelayed(new Runnable() { @Override public void run() { tvIndicator.setVisibility(View.GONE); } }, 300); } } });
項(xiàng)目Github地址:https://github.com/m1Koi/CustomViewPractice
小結(jié)
類似這種效果主要是對(duì)寬高以及觸摸位置的計(jì)算處理。當(dāng)然,如果要實(shí)現(xiàn)和列表聯(lián)動(dòng),還需要對(duì)列表數(shù)據(jù)進(jìn)行排序等處理。不過(guò)在界面中我們已經(jīng)可以拿到觸摸的字母和索引,在進(jìn)行下一步也不是難事了