自定義View系列三 仿網(wǎng)易云音樂(lè)列表字母索引條

聽(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)

  1. 定義自定義屬性

      <declare-styleable name="LetterIndexView">
            <!--字母文字顏色-->
            <attr name="defaultTextColor" format="color"/>
            <!--選中時(shí)字母文字顏色-->
            <attr name="selectTextColor" format="color"/>
            <!--字母文字大小-->
            <attr name="letterTextSize" format="dimension"/>
        </declare-styleable>
    
  2. 確定控件的寬高

    • 寬度值為左右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);
    
        }
    
  3. 循環(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]);
            }
        }
    
  4. 定義接口回調(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;
        }
    
  5. 監(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;
        }
    
  6. 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)行下一步也不是難事了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容