RecyclerView之ItemDecoration的學(xué)習(xí)

最近在項(xiàng)目項(xiàng)目中要做個(gè)類似于美團(tuán)城市列表選擇的頁(yè)面,也就是我們常說(shuō)的帶索引并且粘性頭部的列表。

如下圖是最終效果
2017-09-25 14_27_54.gif

大家能直觀的看出:每個(gè)字母開(kāi)頭相同的城市分為了一組,并且滾動(dòng)的時(shí)候那個(gè)頭部都是是固定在其頂部,以此替換榛搔。

在這之前的一個(gè)項(xiàng)目,做過(guò)手機(jī)聯(lián)系人的目錄和這個(gè)需求很類似东揣,本想把之前的東西直接套過(guò)來(lái)践惑。但發(fā)現(xiàn)當(dāng)時(shí)的做法有點(diǎn)不夠優(yōu)雅而且item 的效率很低,實(shí)現(xiàn)方式:

每個(gè)item 都設(shè)有灰色頭部的索引嘶卧,只不過(guò)是 當(dāng)只在每組的第一個(gè)位置可見(jiàn)其他時(shí)候隱藏尔觉。這樣就把整個(gè)列表進(jìn)行了分類處理,至于那個(gè)粘性頭部芥吟,是在父布局外加了一個(gè)頭部View固定在頂部并做滾動(dòng)的處理侦铜。

后來(lái)在網(wǎng)上查看到一種很不錯(cuò)的做法专甩,通過(guò)給recyclerview添加ItemDecoration裝飾的效果。網(wǎng)上關(guān)于這個(gè)知識(shí)的文章很多钉稍,也有很多現(xiàn)成的代碼配深。但是作為一個(gè)合格的程序猿一定不是復(fù)制粘貼,要明白其所以然嫁盲,并運(yùn)用到自己實(shí)際需求當(dāng)中。那我們就通過(guò)代碼一起學(xué)習(xí)ItemDecoration的使用吧烈掠。

通過(guò)繼承ItemDecoration實(shí)現(xiàn)三個(gè)很重要的方法

/**
 * 對(duì)itemview矩形區(qū)域進(jìn)行的裝飾羞秤, Rect的上下左右,類似padding的效果
 * @param outRect
 * @param view
 * @param parent
 * @param state
 */
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    super.getItemOffsets(outRect, view, parent, state);

}

/**
 * 就像自定義View中onDraw繪制作用 只不過(guò)是在ItemView的指定位置繪制指定的內(nèi)容
 *  當(dāng)然要和onDrawOver有個(gè)細(xì)微的區(qū)分左敌,它繪制的范圍在itemview的下面
 *  比如:所繪制的區(qū)域和itemView有重疊的則會(huì)被遮擋瘾蛋。而在onDrawOver中繪制的范圍則相反
 * @param c
 * @param parent
 * @param state
 */
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    super.onDraw(c, parent, state);
}

/**
 * @param c
 * @param parent
 * @param state
 */
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
    super.onDrawOver(c, parent, state);

}

那我們就通過(guò)具體的實(shí)現(xiàn)看看到底有效果

recyclerview.addItemDecoration(new CustomItemDecoration(this));

然后在自定義的Decoration中簡(jiǎn)單的添加的做一下處理,注意:

    outRect.bottom = dividerHeight;
    outRect.right = dividerHeight;
    outRect.left = dividerHeight;

給每個(gè)item的是三個(gè)方向添加個(gè)類似10dp高的padding

public class CustomItemDecoration extends RecyclerView.ItemDecoration {

private final int dividerHeight;
private final int dividerColor;
private final Paint dividerPaint;
private final Paint dividerPaintL;
private final Paint dividerPaintR;

public CustomItemDecoration(Context context) {
    dividerPaint =new Paint();
    dividerPaintL = new Paint();
    dividerPaintR = new Paint();
    dividerHeight =context.getResources().getDimensionPixelOffset(R.dimen.divider_height);
    dividerColor = context.getResources().getColor(R.color.colorPrimary);
    dividerPaint.setColor(dividerColor);
    dividerPaintL.setColor(context.getResources().getColor(R.color.colorAccent));
    dividerPaintR.setColor(context.getResources().getColor(R.color.colorAccent));

}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
    super.onDrawOver(c, parent, state);
}

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    super.getItemOffsets(outRect, view, parent, state);
    outRect.bottom = dividerHeight;
    outRect.right = dividerHeight;
    outRect.left = dividerHeight;
}

@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    super.onDraw(c, parent, state);
    int childCount = parent.getChildCount();
    int left = parent.getPaddingLeft()+dividerHeight;
    int right = parent.getWidth() - parent.getPaddingRight()+parent.getPaddingLeft()-dividerHeight;

    int lLeft=parent.getPaddingLeft() ;
    int lRight = lLeft + dividerHeight;


    for (int i = 0; i < childCount; i++) {
        View view = parent.getChildAt(i);
        float top = view.getBottom();
        float bottom = view.getBottom() + dividerHeight;
        float lTop = view.getTop();
        float lBottom = view.getBottom();


        int left1 = view.getRight();
        int right1 = left1 + dividerHeight;
        int top1 = view.getTop();
        int bottom1 = view.getBottom();
        //底部
        c.drawRect(left, top, right, bottom, dividerPaint);
        //左側(cè)
        c.drawRect(lLeft, lTop, lRight, lBottom, dividerPaintL);
        //右側(cè)
        c.drawRect(left1, top1, right1, bottom1, dividerPaintR);

    }
}

}

那我們來(lái)看效果

AirMore_Screen_20170925_173922.png

可以看到在每個(gè)itemView的左右和下方多出了間隙矫限,并且在onDraw()方法中繪制了不同的顏色哺哼,我們能很直觀的了解每個(gè)方法的作用和它工作的原理。
然后我們運(yùn)用到需求當(dāng)中叼风。

  • 只有在同一組中才添加ItemDecoration
  • 滾動(dòng)的時(shí)候固定頭部

看下面的代碼實(shí)現(xiàn):

public class SectionDecoration extends RecyclerView.ItemDecoration {

private final String TAG = SectionDecoration.class.getName();


private final Paint paint;
private final TextPaint textPaint;
private final Paint.FontMetrics fontMetrics;
private final int topGap;
private DecorationCallback decorationCallback;

public SectionDecoration(Context context, DecorationCallback callback) {

    this.decorationCallback = callback;
    Resources resources = context.getResources();

    paint = new Paint();
    paint.setColor(resources.getColor(R.color.colorf5));

    textPaint = new TextPaint();
    textPaint.setTypeface(Typeface.DEFAULT_BOLD);
    textPaint.setAntiAlias(false);
    textPaint.setTextSize(80);

    textPaint.setColor(Color.BLACK);
    textPaint.setTextAlign(Paint.Align.LEFT);
    fontMetrics = new Paint.FontMetrics();
    textPaint.getFontMetrics(fontMetrics);

    topGap = resources.getDimensionPixelOffset(R.dimen.section_top);

}

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    super.getItemOffsets(outRect, view, parent, state);
    int position = parent.getChildAdapterPosition(view);
    Log.e(TAG, "getItemOffsets :" + position);

    long groupId = decorationCallback.getGroupId(position);

    if(groupId<0) return;
    if (position == 0 || isFirstInGroup(position)) {//同組的第一個(gè)才添加padding
        outRect.top = topGap;
    } else {
        outRect.top = 0;
    }

}

@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    super.onDraw(c, parent, state);
    int left = parent.getPaddingLeft();
    int right = parent.getWidth() - parent.getPaddingRight();

    int childCount = parent.getChildCount();
    for (int i = 0; i < childCount; i++) {
        View view = parent.getChildAt(i);
        int position = parent.getChildAdapterPosition(view);
        long groupId = decorationCallback.getGroupId(position);
        if(groupId<0) return;
        String textLine = decorationCallback.getGroupFirstLine(position).toUpperCase();
        if (position == 0 || isFirstInGroup(position)) {
            int top = view.getTop() - topGap;
            int bottom = view.getTop();
            c.drawRect(left,top,right,bottom,paint);//繪制矩形區(qū)
            c.drawText(textLine, left, bottom, textPaint);
        }
    }
}

@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
    super.onDrawOver(c, parent, state);
    int itemCount = state.getItemCount();
    int childCount = parent.getChildCount();

    int left = parent.getPaddingLeft();
    int right = parent.getWidth() - parent.getPaddingRight();
    float lineHeight = textPaint.getTextSize() + fontMetrics.descent;

    long preGroupId, groupId = -1;

    for (int i = 0; i < childCount; i++) {
        View view = parent.getChildAt(i);
        int position = parent.getChildAdapterPosition(view);

        preGroupId = groupId;
        groupId = decorationCallback.getGroupId(position);
        if (groupId < 0 || groupId == preGroupId) continue;

        String textLine = decorationCallback.getGroupFirstLine(position).toUpperCase();
        
        if(TextUtils.isEmpty(textLine)) continue;

        int viewBottom = view.getBottom();

        int textY = Math.max(topGap, view.getTop());

        if (position + 1 < itemCount) {
            long nextGroupId = decorationCallback.getGroupId(position + 1);
            if (nextGroupId != groupId && viewBottom < textY) {
                textY = viewBottom;
            }
        }
        c.drawRect(left, textY - topGap, right, textY, paint);
        c.drawText(textLine, left, textY, textPaint);
    }
}

private boolean isFirstInGroup(int position) {
    if (position == 0) {
        return true;
    } else {
        long prevGroupId = decorationCallback.getGroupId(position - 1);
        long groupId = decorationCallback.getGroupId(position);
        return prevGroupId != groupId;
    }
}

}

定義一個(gè)回調(diào)

public interface DecorationCallback {

  long getGroupId(int position);

  String getGroupFirstLine(int position);
}

這就完成了簡(jiǎn)單的效果

2017-09-25 18_03_54.gif

說(shuō)實(shí)話剛開(kāi)始使用itemDecorartion是比較麻煩的取董,但是當(dāng)掌握上手以后,用起來(lái)真的很爽无宿,避免了很多在代碼中邏輯判斷茵汰,比如有的時(shí)候在列表中只需要在某個(gè)位置才需要添加灰色的間距,如果不用itemDecoration孽鸡,則通常的就是整加視圖的層級(jí)蹂午,通過(guò)if else判斷顯示和隱藏。現(xiàn)在既然在RecyclerView中又itemDecorartion這個(gè)裝飾彬碱,沒(méi)什么不用讓你的代碼實(shí)現(xiàn)的更優(yōu)雅豆胸。

這是寫(xiě)的第一篇博客,也希望自己能堅(jiān)持這個(gè)習(xí)慣巷疼,記錄自己的學(xué)習(xí)過(guò)程


風(fēng)后面是風(fēng)晚胡,天空上面是天空,而你的生活可以與眾不同

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末皮迟,一起剝皮案震驚了整個(gè)濱河市搬泥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌伏尼,老刑警劉巖忿檩,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異爆阶,居然都是意外死亡燥透,警方通過(guò)查閱死者的電腦和手機(jī)沙咏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)班套,“玉大人肢藐,你說(shuō)我怎么就攤上這事≈ň拢” “怎么了吆豹?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)理盆。 經(jīng)常有香客問(wèn)我痘煤,道長(zhǎng),這世上最難降的妖魔是什么猿规? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任衷快,我火速辦了婚禮,結(jié)果婚禮上姨俩,老公的妹妹穿的比我還像新娘蘸拔。我一直安慰自己,他們只是感情好环葵,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布调窍。 她就那樣靜靜地躺著,像睡著了一般积担。 火紅的嫁衣襯著肌膚如雪陨晶。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,084評(píng)論 1 291
  • 那天帝璧,我揣著相機(jī)與錄音先誉,去河邊找鬼。 笑死的烁,一個(gè)胖子當(dāng)著我的面吹牛褐耳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播渴庆,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼铃芦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了襟雷?” 一聲冷哼從身側(cè)響起刃滓,我...
    開(kāi)封第一講書(shū)人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎耸弄,沒(méi)想到半個(gè)月后咧虎,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡计呈,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年砰诵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了征唬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡茁彭,死狀恐怖总寒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情理肺,我是刑警寧澤摄闸,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站妹萨,受9級(jí)特大地震影響贪薪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜眠副,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望竣稽。 院中可真熱鬧囱怕,春花似錦、人聲如沸毫别。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)岛宦。三九已至台丛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間砾肺,已是汗流浹背挽霉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留变汪,地道東北人侠坎。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像裙盾,于是被迫代替她去往敵國(guó)和親实胸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351

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