Android 自定義View 字母索引條

寫在開頭

這是自定義View的第三篇文章,第一篇是Android drawPath實現QQ拖拽泡泡,主要實現的是題目說的東西,第二篇是Android 自定義View 跳動的水果和文字,可能看這個題目不知道說的是撒,主要講的是Android drawTextOnPath()的相關方法,以及屬性動畫相關的使用。當然個人覺得動畫效果還是闊以的 嘻嘻。。
這篇主要還是說說在onDraw()drawText()相關的使用,實現的效果就是如圖所示!

index_靜態.png
index.gif

一個View從出生到你能看到的話,肯定是會經歷onMeasure()onLayout()onDraw(),這幾方法的,而自定義View無外乎也要涉及到這幾個相關的方法,這篇文章沒有那么復雜,主要涉及的就是onDraw()方法!

開門見山-IndexBar

不管是在QQ上,還是在163的郵箱中,或者自己手機的通訊錄中,右側都會躺著一個這個玩意兒,我姑且不造官方有沒有相關的東西,或者大家約定俗成的稱呼這個玩意兒叫什么,反正我就叫它索引條-IndexBar了吧!

IndexBar從整體樣式上(我觀察的哈),分為兩種,一種就是不管三七二十一,26個字母糊糊的貼上去的那種,還有一種就是根據當前的具體內容,只展示相關的首字母的!至于touch到IndexBar背景變為灰色,滑動時選中的字母呈現出選中的狀態,這些都搜easy滴!!當然你可能要說還有開頭是#號的,或者寫著熱門等等等的。。

實現思路

這個問題要一分為二來看,首先是怎么把26個字母畫出來,然后才是怎么去識別觸摸對應的是哪個字母!!

畫出對應的字母

這個不用多說,肯定是要調用 drawText()相關的方法,drawText(@NonNull String text, float x, float y, @NonNull Paint paint),這里我們需要注意的就是這里的x和y是撒意思了!
它就是控制這個文字開始的左下定位的坐標。文字就是從這個點的開始向右上繪制出來的!Demo中onDraw方法有對應的注釋了的方法,打開可以直接看相關的效果。

首先確定X軸的距離,就是(總的寬度-文字的寬度)/2,這樣每個文字水平就是居中顯示的了!!
然后確定Y軸的位置,就是(每個文字的總高度+文字的高度)/2,(文字是確定的左下方的坐標點,向下應該加起來!)這樣每個文字在豎直方向單位高度中也是居中顯示的了!!

那么問題來了,上面說的那些寬度高度等要怎么獲取呢?
獲取屏幕的高度,平分到26個字母,有'# '或者 ‘熱門’再把相關的東西加上!這個就是 每個文字的總高度!接下來就是涉及到這幾個方法:
onDraw() 這個不用說,你不draw怎么能展示出來呢?
onSizeChanged(),如果屏幕尺寸發生了變化,不如說虛擬按鍵隱藏或者展示之后,還有就是屏幕旋轉相關的。。
setLetters(),準備好了相關的字母之后,這里就需要去再去計算新的相關參數然后通知繪制。

    @Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (letters != null) {
        for (int i = 0; i < letters.size(); i++) {
            String text = letters.get(i);
            float textWidth = mPaint.measureText(text);
            mPaint.getTextBounds(text, 0, text.length(), mRect);
            float textHeight = mRect.height();
            float x = mCellWidth * 0.5f - textWidth * 0.5f;
            float y = mCellHeight * 0.5f + textHeight * 0.5f + mCellHeight * i + beginY;
            mPaint.setColor(mIndex == i ? selecColor : normalColor);
            canvas.drawText(text, x, y, mPaint);

        }
    }
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    mHeight = getMeasuredHeight()-getPaddingTop()-getPaddingBottom();
    mCellWidth = getMeasuredWidth();
    mCellHeight = mHeight * 1.0f / 26;
    if (letters != null) {
        beginY = (mHeight - mCellHeight * letters.size()) * 0.5f;
    }

}


public void setLetters(@Nullable List<String> letters) {

    if (letters == null) {
        setVisibility(GONE);
        return;
    }
    this.letters = letters;
    mHeight = getMeasuredHeight()-getPaddingTop()-getPaddingBottom();
    mCellWidth = getMeasuredWidth();
    mCellHeight = mHeight * 1.0f / 26;
    beginY = (mHeight - mCellHeight * letters.size()) * 0.5f;
    invalidate();
}

setLetters()onSizeChanged()里面的代碼基本上是重復的,只是在setLetters()里面調用了 invalidate()去通知重新繪制。

觸摸的相關狀態添加

首先是觸摸到這個索引條,背景加深,這個肯定就是走touch事件了嘛,在ACTION_DOWN的時候修改相關狀態,在ACTION_UP的時候,再次刷新相關狀態咯。

這里要使用refreshDrawableState()onCreateDrawableState()這兩個方法,如果你知道了,就當我在這里瞎比比吧!哈哈。。如果不清楚,可以看看我之前寫的一篇自定義狀態選擇器

//定義一個狀態
private static final int[] STATE_FOCUSED = new int[]{android.R.attr.state_focused};
//DOWN 設為true UP 設為false
private void refreshState(boolean state) {
    if (pressed != state) {
        pressed = state;
        refreshDrawableState();
    }
}

@Override
public int[] onCreateDrawableState(int extraSpace) {
    int[] states = super.onCreateDrawableState(extraSpace + 1);
    if (pressed) {
        mergeDrawableStates(states, STATE_FOCUSED);
    }
    return states;
}

背景選擇基本歐克了!

然后是選中的字母的顏色,這個其實就是更換畫筆的顏色就好了!!這個就放在下面的一塊內容中。

點擊相關回調

用戶看到的都是表象,觸摸到的肯定是某一個坐標值,這個坐標應該對應這26個字母中的某一個字母的所在的坐標!比如說總高度是2600,然后每個字母Y軸所占的區域就是100,你觸摸的坐標是(x,520),那這個明顯就是第六個字母了嘛,獲取到了對應的position,這個問題就解決完了!

       @Override
public boolean onTouchEvent(MotionEvent event) {
    float y;
    invalidate();
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i("TAG", "onTouchEvent:Down ");
            getParent().requestDisallowInterceptTouchEvent(true);
            refreshState(true);
            y = event.getY();
            checkIndex(y);
            break;
        case MotionEvent.ACTION_MOVE:
            y = event.getY();
            checkIndex(y);
            break;
        case MotionEvent.ACTION_UP:
            refreshState(false);
            mIndex = -1;
            break;

        default:
            break;
    }
    return true;
}



private void checkIndex(float y) {
    int currentIndex;
    if (y < beginY+getPaddingTop()) {
        return;
    }
    currentIndex = (int) ((y - beginY-getPaddingTop()) / mCellHeight);
    if (currentIndex != mIndex) {
        if (mOnLetterChangeListener != null) {
            if (letters != null && currentIndex < letters.size()) {
                mIndex = currentIndex;
                mOnLetterChangeListener.onLetterChange(letters.get(currentIndex));
    //          Log.i(TAG, "checkIndex: "+letters.get(currentIndex));
            }
        }
       
    }
}

然后就是上面遺留的那個問題,選中字母顏色的更改就是通過這個mIndex來實現的,在draw方法中的這行代碼:

mPaint.setColor(mIndex == i ? selecColor : normalColor);
            canvas.drawText(text, x, y, mPaint);

那到這里可以看到,那就是View的展現和相關邏輯的確是分開的,你看到的都是一些表面現象。

滾動到指定的位置

這個是最終的要求了,這里要區分實現機制了,如果你是使用了ListView,那么直接調用setSelection()就可以滾動到指定的位置了。
如果你是使用了RecycleView的話,那么就是使用LayoutManager的manager.scrollToPositionWithOffset(pos,0)
我在測驗中發現直接使用manager.scrollToPosition()的話,的確可以滾動,但是不是出現在頂部位置!

總結

本次Indexbar的話,繪制部分主要涉及到了onDraw()方法,canvas.drawText()
細節的話,就是onSizeChanged() 和 setLetters()之后的通知重新繪制。
還有就是狀態選擇器,兩個方法refreshDrawableState()onCreateDrawableState()
需要注意的是,有兩個偏移量 beginY 和 topPadding,beginY是用居中的一個偏移量,topPadding就不用多說了!

回調部分,就是onTouch相關處理,根據getY()獲取相關Y軸的值推算出對應的position,然后再回調到對應的ListView或者RecycleView

Gif所示的Demo地址:IndexDemo

自定義View的Demo相關地址自定義View

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容