1. 程式人生 > >ItemDecoration 的一些使用小技巧

ItemDecoration 的一些使用小技巧

code小生,一個專注 Android 領域的技術分享平臺

作者:saka
地址:https://juejin.im/post/5b38df5a51882574a36fc14e
宣告:本文來自 saka 投稿,轉發等請聯絡原作者授權

RecyclerView是安卓開發中常用的列表控制元件,當初google設計它的目的就是用來取代listview和gridview。這篇文章要講的主角是recyclerview的一個附屬品-ItemDecoration。講解的也都是基礎內容,主要有三個部分:

  1. 為每個item實現索引

  2. 為特定的item實現不同的分隔線

  3. 覆蓋在item上的一個可移動的icon

最終的效果如下(忽略毫無設計的ui):

640

github地址:https%3A%2F%2Fgithub.com%2Frangaofei%2FCustomViewDemo

簡介

ItemDecoration是一個非常簡單的抽象類,它沒有抽象方法,除去廢棄的api,共有三個方法可以重寫,分別是

//獲取當前view的位置資訊
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
//在item背後draw
public void onDraw(Canvas c, RecyclerView parent, State state)
//在item上邊draw

public void onDrawOver(Canvas c, RecyclerView parent, State state)

這三個方法可以說是三個非常重要的方法,他們之間有著強烈的因果關係。
首先需要注意的是三個方法的呼叫順序:

  1. 首先呼叫的是getItemOffsets會被多次呼叫,在layoutmanager每次測量可擺放的view的時候回撥用一次,在當前狀態下需要擺放多少個view這個方法就會回撥多少次。

  2. 其次會呼叫ondraw方法,ItemDecoration的ondraw方法是在recyclerview的ondraw方法中呼叫的,注意這時候傳入的canvas是recyclerview的canvas,要時刻注意這點,它是和recyclerview的邊界是一致的。這個時候繪製的內容相當於背景,會被item覆蓋。

  3. 最後呼叫的是ondrawover方法,ItemDecoration的ondrawover方法是在recyclerview的draw方法中呼叫的,同樣傳入的是recyclerview的canvas,這時候onlayout已經呼叫,所以此時繪製的內容會覆蓋item。

理解了以上三個方法,我們就可以隨意定製itemdecoration了。

為每個item實現索引

這個要引出的知識點是關於getItemOffsets的詳細用法,在這裡會傳過來四個引數(所有的方法中的state暫不討論,它和layoutmanager關係比較密切),主要關注前三個引數:

  1. outRect,核心引數,這個rect相當於item擺放的時候設定的margin,rect的left相當於item的marginleft,rect的right相當於item的marginright。

  2. view,當前繪製的view,可以用來獲取它在adapter中的位置

  3. parent,recyclerview,沒什麼好說的。

上一段簡單的程式碼,來實現為所有的左側新增一個空白

if (parent.getLayoutManager() instanceof LinearLayoutManager) {
    this.layoutOrientation = ((LinearLayoutManager) parent.getLayoutManager()).getOrientation();
}
switch (this.layoutOrientation) {
    case LinearLayoutManager.VERTICAL:
        outRect.set(40002);//bottom正常
        break;
    case LinearLayoutManager.HORIZONTAL://比較懶,沒寫
        break;
    default:
        break;
}

這樣,就在所有的item左側流出來一段空白,接下來就可以在這段空白上來繪製文字:
首先在ondraw方法中分發事件:

@Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        Log.d("---""onDraw");
        switch (this.layoutOrientation) {
            case LinearLayoutManager.VERTICAL:
                drawVertical(c, parent);
                break;
            case LinearLayoutManager.HORIZONTAL:
                drawHorizontal(c, parent);
                break;
            default:
                break;
        }
    }

然後看一下具體的繪製文字流程:

c.save();
final int left;
final int right;
if (parent.getClipToPadding()) {
    left = parent.getPaddingLeft();
    right = parent.getWidth() - parent.getPaddingRight();
    c.clipRect(left, parent.getPaddingTop(), right,
            parent.getHeight() - parent.getPaddingBottom());
else {
    left = 0;
    right = parent.getWidth();
}

for (int i = 0; i < parent.getChildCount(); i++) {
    View view = parent.getChildAt(i);
    int position = parent.getChildAdapterPosition(view);
    String text = String.valueOf(position+1);
    float w = textPaint.measureText(text);
    c.drawText(text, 20 - w / 2, view.getBottom() - view.getHeight() / 2 + textPaint.getFontMetri().descent, textPaint);
}
c.restore();

這裡要注意兩點,paren.getChildCount是獲取的當前顯示的view的數量,並不會獲取不顯示的view的數量。假如recyclerview裡共有30條資料,而當前螢幕內顯示的只有5條,這paren.getChildCount的值是5,不是30。
int position = parent.getChildAdapterPosition(view)
這段程式碼是獲取的當前這個view在30條資料中的位置。

c.drawText(text, 20 - w / 2, view.getBottom() - view.getHeight() / 2 + textPaint.getFontMetri().descent, textPaint);

這段程式碼繪製了文字,文字的位置就是20 - w / 2, view.getBottom() - view.getHeight() / 2 + textPaint.getFontMetri().descent這個座標,假如對繪製文字時座標的獲取不太熟悉的話,需要搜尋一下資料。

640?wx_fmt=other

為特定的item實現不同的分隔線

這個非常簡單,只需要在getitemoffset中判斷即可:

 switch (this.layoutOrientation) {
            case LinearLayoutManager.VERTICAL:
                if (parent.getChildAdapterPosition(view) % 3 == 0) {
                    outRect.set(4000100);

                } else {
                    outRect.set(40002);
                }
                break;
            case LinearLayoutManager.HORIZONTAL:
                break;
            default:
                break;
        }

通過上面程式碼,就為所有的索引能被3整除的位置添加了一個高度為100的分隔線。看一下效果。

640?wx_fmt=other

覆蓋在item上的一個可移動的icon

這個方法需要在ondrawover中實現,icon會覆蓋在item上邊,並隨螢幕比例來回移動,廢話不多,直接上程式碼:

 private void drawOverVertical(Canvas c, RecyclerView parent) {
        c.save();
        final int left;
        final int right;
        if (parent.getClipToPadding()) {
            left = parent.getPaddingLeft();
            right = parent.getWidth() - parent.getPaddingRight();
            c.clipRect(left, parent.getPaddingTop(), right,
                    parent.getHeight() - parent.getPaddingBottom());
        } else {
            left = 0;
            right = parent.getWidth();
        }

        for (int i = 0; i < parent.getChildCount(); i++) {

            View view = parent.getChildAt(i);
            int top = view.getTop();
            int totalHeight = parent.getHeight() - parent.getPaddingTop() - parent.getPaddingBottom();
            float percentT = (float) top / totalHeight;
            drawable.setBounds((int) (view.getLeft() + 10 + view.getWidth() * percentT),
                    view.getTop(),
                    (int) (view.getLeft() + 58 + view.getWidth() * percentT),
                    view.getTop() + 48);
            drawable.draw(c);
        }
    }

上面程式碼中測量了當前view的top位置相對於recyclerview的高度的比例,在item上實現隨位置滾動而左右滑動的一個icon。

640

一篇非常簡單的文章,希望能幫助到一些朋友。
另外分享一個自己正在編寫的基於註解的庫:
https%3A%2F%2Fgithub.com%2Frangaofei%2FTimeLine

歡迎提意見

640

640