Android 自定義View 字母索引條

寫在開頭

這是自定義View的第三篇文章白群,第一篇是Android drawPath實(shí)現(xiàn)QQ拖拽泡泡,主要實(shí)現(xiàn)的是題目說的東西娃殖,第二篇是Android 自定義View 跳動的水果和文字,可能看這個題目不知道說的是撒,主要講的是Android drawTextOnPath()的相關(guān)方法少辣,以及屬性動畫相關(guān)的使用赵哲。當(dāng)然個人覺得動畫效果還是闊以的 嘻嘻总处。。
這篇主要還是說說在onDraw()drawText()相關(guān)的使用盏缤,實(shí)現(xiàn)的效果就是如圖所示!

index_靜態(tài).png
index.gif

一個View從出生到你能看到的話砰蠢,肯定是會經(jīng)歷onMeasure()onLayout()唉铜、onDraw(),這幾方法的台舱,而自定義View無外乎也要涉及到這幾個相關(guān)的方法,這篇文章沒有那么復(fù)雜,主要涉及的就是onDraw()方法竞惋!

開門見山-IndexBar

不管是在QQ上柜去,還是在163的郵箱中,或者自己手機(jī)的通訊錄中拆宛,右側(cè)都會躺著一個這個玩意兒嗓奢,我姑且不造官方有沒有相關(guān)的東西,或者大家約定俗成的稱呼這個玩意兒叫什么浑厚,反正我就叫它索引條-IndexBar了吧股耽!

IndexBar從整體樣式上(我觀察的哈),分為兩種钳幅,一種就是不管三七二十一物蝙,26個字母糊糊的貼上去的那種,還有一種就是根據(jù)當(dāng)前的具體內(nèi)容敢艰,只展示相關(guān)的首字母的诬乞!至于touch到IndexBar背景變?yōu)榛疑瑒訒r選中的字母呈現(xiàn)出選中的狀態(tài)钠导,這些都搜easy滴U鸺怠!當(dāng)然你可能要說還有開頭是#號的牡属,或者寫著熱門等等等的票堵。。

實(shí)現(xiàn)思路

這個問題要一分為二來看湃望,首先是怎么把26個字母畫出來换衬,然后才是怎么去識別觸摸對應(yīng)的是哪個字母!证芭!

畫出對應(yīng)的字母

這個不用多說瞳浦,肯定是要調(diào)用 drawText()相關(guān)的方法,drawText(@NonNull String text, float x, float y, @NonNull Paint paint),這里我們需要注意的就是這里的x和y是撒意思了废士!
它就是控制這個文字開始的左下定位的坐標(biāo)叫潦。文字就是從這個點(diǎn)的開始向右上繪制出來的!Demo中onDraw方法有對應(yīng)的注釋了的方法官硝,打開可以直接看相關(guān)的效果矗蕊。

首先確定X軸的距離,就是(總的寬度-文字的寬度)/2,這樣每個文字水平就是居中顯示的了G饧堋傻咖!
然后確定Y軸的位置,就是(每個文字的總高度+文字的高度)/2岖研,(文字是確定的左下方的坐標(biāo)點(diǎn)卿操,向下應(yīng)該加起來>臁)這樣每個文字在豎直方向單位高度中也是居中顯示的了!害淤!

那么問題來了扇雕,上面說的那些寬度高度等要怎么獲取呢?
獲取屏幕的高度窥摄,平分到26個字母镶奉,有'# '或者 ‘熱門’再把相關(guān)的東西加上!這個就是 每個文字的總高度崭放!接下來就是涉及到這幾個方法:
onDraw() 這個不用說哨苛,你不draw怎么能展示出來呢?
onSizeChanged(),如果屏幕尺寸發(fā)生了變化币砂,不如說虛擬按鍵隱藏或者展示之后移国,還有就是屏幕旋轉(zhuǎn)相關(guān)的。道伟。
setLetters(),準(zhǔn)備好了相關(guān)的字母之后,這里就需要去再去計算新的相關(guān)參數(shù)然后通知繪制使碾。

    @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()里面的代碼基本上是重復(fù)的蜜徽,只是在setLetters()里面調(diào)用了 invalidate()去通知重新繪制。

觸摸的相關(guān)狀態(tài)添加

首先是觸摸到這個索引條票摇,背景加深拘鞋,這個肯定就是走touch事件了嘛,在ACTION_DOWN的時候修改相關(guān)狀態(tài)矢门,在ACTION_UP的時候,再次刷新相關(guān)狀態(tài)咯。

這里要使用refreshDrawableState()onCreateDrawableState()這兩個方法殴泰,如果你知道了芒涡,就當(dāng)我在這里瞎比比吧!哈哈物延。宣旱。如果不清楚,可以看看我之前寫的一篇自定義狀態(tài)選擇器叛薯。

//定義一個狀態(tài)
private static final int[] STATE_FOCUSED = new int[]{android.R.attr.state_focused};
//DOWN 設(shè)為true UP 設(shè)為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;
}

背景選擇基本歐克了浑吟!

然后是選中的字母的顏色,這個其實(shí)就是更換畫筆的顏色就好了:牧铩组力!這個就放在下面的一塊內(nèi)容中。

點(diǎn)擊相關(guān)回調(diào)

用戶看到的都是表象抖拴,觸摸到的肯定是某一個坐標(biāo)值燎字,這個坐標(biāo)應(yīng)該對應(yīng)這26個字母中的某一個字母的所在的坐標(biāo)!比如說總高度是2600,然后每個字母Y軸所占的區(qū)域就是100轩触,你觸摸的坐標(biāo)是(x,520),那這個明顯就是第六個字母了嘛寞酿,獲取到了對應(yīng)的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來實(shí)現(xiàn)的,在draw方法中的這行代碼:

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

那到這里可以看到榨为,那就是View的展現(xiàn)和相關(guān)邏輯的確是分開的惨好,你看到的都是一些表面現(xiàn)象。

滾動到指定的位置

這個是最終的要求了随闺,這里要區(qū)分實(shí)現(xiàn)機(jī)制了日川,如果你是使用了ListView,那么直接調(diào)用setSelection()就可以滾動到指定的位置了矩乐。
如果你是使用了RecycleView的話龄句,那么就是使用LayoutManager的manager.scrollToPositionWithOffset(pos,0)
我在測驗(yàn)中發(fā)現(xiàn)直接使用manager.scrollToPosition()的話散罕,的確可以滾動分歇,但是不是出現(xiàn)在頂部位置!

總結(jié)

本次Indexbar的話欧漱,繪制部分主要涉及到了onDraw()方法职抡,canvas.drawText()
細(xì)節(jié)的話误甚,就是onSizeChanged() 和 setLetters()之后的通知重新繪制缚甩。
還有就是狀態(tài)選擇器,兩個方法refreshDrawableState()onCreateDrawableState()窑邦。
需要注意的是擅威,有兩個偏移量 beginY 和 topPadding,beginY是用居中的一個偏移量冈钦,topPadding就不用多說了裕寨!

回調(diào)部分,就是onTouch相關(guān)處理派继,根據(jù)getY()獲取相關(guān)Y軸的值推算出對應(yīng)的position,然后再回調(diào)到對應(yīng)的ListView或者RecycleView

Gif所示的Demo地址:IndexDemo

自定義View的Demo相關(guān)地址自定義View

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末宾袜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子驾窟,更是在濱河造成了極大的恐慌庆猫,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绅络,死亡現(xiàn)場離奇詭異月培,居然都是意外死亡嘁字,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進(jìn)店門杉畜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纪蜒,“玉大人,你說我怎么就攤上這事此叠〈啃” “怎么了?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵灭袁,是天一觀的道長猬错。 經(jīng)常有香客問我,道長茸歧,這世上最難降的妖魔是什么倦炒? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮软瞎,結(jié)果婚禮上逢唤,老公的妹妹穿的比我還像新娘。我一直安慰自己涤浇,他們只是感情好智玻,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著芙代,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盖彭。 梳的紋絲不亂的頭發(fā)上纹烹,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天,我揣著相機(jī)與錄音召边,去河邊找鬼铺呵。 笑死,一個胖子當(dāng)著我的面吹牛隧熙,可吹牛的內(nèi)容都是我干的片挂。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼贞盯,長吁一口氣:“原來是場噩夢啊……” “哼音念!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起躏敢,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤闷愤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后件余,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體讥脐,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡遭居,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了旬渠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俱萍。...
    茶點(diǎn)故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖告丢,靈堂內(nèi)的尸體忽然破棺而出枪蘑,到底是詐尸還是另有隱情,我是刑警寧澤芋齿,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布腥寇,位于F島的核電站,受9級特大地震影響觅捆,放射性物質(zhì)發(fā)生泄漏赦役。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一栅炒、第九天 我趴在偏房一處隱蔽的房頂上張望掂摔。 院中可真熱鬧,春花似錦赢赊、人聲如沸乙漓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叭披。三九已至,卻和暖如春玩讳,著一層夾襖步出監(jiān)牢的瞬間涩蜘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工熏纯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留同诫,地道東北人。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓樟澜,卻偏偏與公主長得像误窖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子秩贰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內(nèi)容