關於RecyclerView的ItemDecoration


之前用 ItemDecoration 一直都是用的源碼裏唯一附帶的 DividerItemDecoration。 或者直接在每個Item裏寫分割線代碼了。
一直沒怎麼管 ItemDecoration。

這段時間因爲需要用到所以學習了下用法。
這個類需要繼承 RecyclerView。ItemDecoration。
有3種方法可以重寫
getItemOffsets 名字表示 item 的偏移,實際的工作就是按照需求對 item 的四個方向增加多少 padding
onDraw 表示畫 item 前進行的繪畫
onDrawOver 表示畫 item 後進行的繪畫

所以這三者的順序是  onDraw --》 畫Item內容 --》 onDrawOver
理論上,如果你將要畫的分割線區域不會佔據item的所要顯示的內容(廢話),直接用 drawOver 方法就行。
但是這會帶來『過度繪製』, 不符合我們開發規範。所以一般要用 getItemOffsets 產生偏移。讓item偏移出一些沒有繪製的padding區域。



寫了一個標準的網格佈局分割線
亮點是絕對是偏移和繪製分割線都對的上,『調試GPU過度繪製』檢查妥妥的完美不重疊。 不像網上找來的一大片過度繪製。

public class GridDividerItemDecoration extends RecyclerView.ItemDecoration {

    //使用系統自帶的分割線
//    private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
    private Drawable mDivider;
    private int spanCount = -1; //一行有多少列
    private int childCount = -1; //有多少個Item

    public GridDividerItemDecoration(Context context) {
//        final TypedArray a = context.obtainStyledAttributes(ATTRS);
//        mDivider = a.getDrawable(0);
//        a.recycle();

        //使用自己定義的分割線
        mDivider = context.getResources().getDrawable(R.drawable.my_divider);
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        int visibleItemCount = parent.getChildCount();
        for (int i=0; i<visibleItemCount; i++) {
            View view = parent.getChildAt(i);
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
            //畫豎線
            if (!isLastCol(i, getSpanCount(parent))) {
                int left = view.getRight() + params.rightMargin;
                int right = view.getRight() + mDivider.getIntrinsicWidth() + params.rightMargin;
                int top = view.getTop() - params.topMargin;
                int bottom = view.getBottom() + params.bottomMargin;
                mDivider.setBounds(left, top, right, bottom);
                mDivider.draw(c);
            }
            //畫橫線
            if (!isLastRow(i, getSpanCount(parent), getChildCount(parent))) {
                int left = view.getLeft() - params.leftMargin;
                int right = view.getRight() + mDivider.getIntrinsicWidth() + params.rightMargin;
                int top = view.getBottom() + params.bottomMargin;
                int bottom = view.getBottom() + mDivider.getIntrinsicHeight() + params.bottomMargin;
                mDivider.setBounds(left, top, right, bottom);
                mDivider.draw(c);
            }
        }
    }

    /**
     * 獲取列數
     * @param parent
     * @return
     */
    private int getSpanCount(RecyclerView parent) {
        if (spanCount == -1){
            RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
            spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
        }
        return spanCount;
    }

    /**
     * 獲取item總數
     * @param parent
     * @return
     */
    private int getChildCount(RecyclerView parent) {
        if (childCount == -1) {
            childCount = parent.getAdapter().getItemCount();
        }
        return childCount;
    }

    /**
     * 判斷是否最後一列
     * @param pos
     * @param spanCount
     * @return
     */
    private boolean isLastCol(int pos, int spanCount) {
        if ((pos + 1) % spanCount == 0) {
            return true;
        }
        return false;
    }

    /**
     * 判斷是否最後一行
     * @param pos
     * @param spanCount
     * @param childCount
     * @return
     */
    private boolean isLastRow(int pos, int spanCount, int childCount) {
        int lastCompletedCount;
        if (childCount % spanCount == 0){
            lastCompletedCount = (childCount / spanCount - 1) * spanCount;
        } else {
            lastCompletedCount = childCount - childCount % spanCount;
        }
        if (pos >= lastCompletedCount)
            return true;
        return false;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        int spanCount = getSpanCount(parent);
        int childCount = getChildCount(parent);
        int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();

        int right = mDivider.getIntrinsicWidth();
        int bottom = mDivider.getIntrinsicHeight();
        //最後一行bottom不用偏移
        if (isLastRow(itemPosition, spanCount, childCount)){
            bottom = 0;
        }
        //最後一列right不用偏移
        if (isLastCol(itemPosition, spanCount)){
            right = 0;
        }
        outRect.set(0, 0, right, bottom);
    }
}







對於分割線顏色和大小:

可以像 DividerItemDecoration 那樣,使用了默認的l分割線。 實際就是顏色是#DCDCDC,大小寬高都是2px的分割線。

也可以自定義,需要寫一個 drawable,比如

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <solid android:color="#32f2" />

    <size
        android:width="1dp"
        android:height="1dp" />

</shape>
填對應的了顏色和寬高值。寬代表豎分割線的寬度,高代表橫分割線的寬度。




對於 getItemOffsets 的偏移:

outRect.set(0, 0, right, bottom);是用一個Rect類來存放四個方向的偏移量。

然後會在 LayoutManager 和 RecyclerView 中的測量 measure 和放置 layout 每個 Item 邏輯裏用到(不同LayoutManager用到的地方不一樣).用以擴大整個item的區域.好給之後的draw方法有可繪畫的地方.

如果設置的是Vertical方向滾動。那就是在左右方向的outRect會是Item的向內收縮。上下方向是Item的向外擴張。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章