RV ItemDecoration 分割線 簡介 MD


Markdown版本筆記 我的GitHub首頁 我的博客 我的微信 我的郵箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

RV ItemDecoration 分割線 簡介 MD
Demo地址
RecyclerView-FlexibleDivider 庫


目錄

一個通用分割線

一個通用分割線,適用於GridView,當然也適用於普通的RecyclerView。
支持在item之間設置任何類型的間距,支持控制是否顯示上下左右間隔及是否繪制上下左右背景

ItemDecoration 分割線

/**
 * Desc:適用於GridView,當然也適用於普通的RecyclerView
 * 支持在item之間設置任何類型的間距,支持控制是否顯示上下左右間隔及是否繪制上下左右背景
 */
public class GridItemDecoration extends RecyclerView.ItemDecoration {

    private Drawable verticalDivider;
    private Drawable horizontalDivider;
    private int spanCount;
    private int verticalSpaceSize;
    private int horizontalSpaceSize;
    private boolean includeVerticalEdge;//是否繪制左右邊界,注意不繪制並不表示沒有預留邊界空間
    private boolean includeHorizontalEdge;//是否繪制上下邊界,注意不繪制並不表示沒有預留邊界空間

    private GridItemDecoration(Builder builder) {
        verticalDivider = builder.verticalDivider;
        horizontalDivider = builder.horizontalDivider;
        spanCount = builder.spanCount;
        verticalSpaceSize = builder.verticalSpaceSize;
        horizontalSpaceSize = builder.horizontalSpaceSize;
        includeVerticalEdge = builder.includeVerticalEdge;
        includeHorizontalEdge = builder.includeHorizontalEdge;
    }

    public static Builder newBuilder() {
        return new Builder();
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        int position = parent.getChildAdapterPosition(view); // item position
        int column = position % spanCount; // item column
        if (verticalSpaceSize > 0) {
            outRect.left = verticalSpaceSize - column * verticalSpaceSize / spanCount;
            outRect.right = (column + 1) * verticalSpaceSize / spanCount;
        } else {
            outRect.left = column * verticalSpaceSize / spanCount;
            outRect.right = verticalSpaceSize - (column + 1) * verticalSpaceSize / spanCount;
        }

        if (horizontalSpaceSize > 0) {
            if (position < spanCount) outRect.top = horizontalSpaceSize; // top edge
            outRect.bottom = horizontalSpaceSize; // item bottom
        } else {
            if (position >= spanCount) outRect.top = horizontalSpaceSize; // item top
        }

        log("確定邊界" + outRect.left + "  " + outRect.right + "  " + outRect.top + "  " + outRect.bottom);
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        log(toString());
        //在邊界上繪制圖形
        if (horizontalDivider != null && horizontalSpaceSize > 0) {
            drawHorizontalLineAtItemTop(c, parent);//在每一個item的上面繪制水平分割線
            drawLineAtTopAndBottom(c, parent);//繪制上下邊界,包括倒數第二行中的部分item
        }
        if (verticalDivider != null && verticalSpaceSize > 0) {
            drawVerticalLineAtItemLeft(c, parent);//繪制豎直分割線
            if (includeVerticalEdge) drawLR(c, parent);//繪制左右邊界
        }
    }

    //在每一個item的上面繪制水平分割線
    private void drawHorizontalLineAtItemTop(Canvas c, RecyclerView parent) {
        int childCount = parent.getChildCount();
        for (int position = 0; position < childCount; position++) {
            if (position >= spanCount) {//第一行不繪制
                View child = parent.getChildAt(position);
                int left = child.getLeft();
                int right = (position % spanCount == spanCount - 1) ? child.getRight() : (child.getRight() + verticalSpaceSize);
                int top = child.getTop() - horizontalSpaceSize;
                int bottom = top + horizontalSpaceSize;
                log("繪制水平分割線" + left + "  " + right + "  " + top + "  " + bottom);
                horizontalDivider.setBounds(left, top, right, bottom);
                horizontalDivider.draw(c);
            }
        }
    }

    //繪制豎直分割線
    private void drawVerticalLineAtItemLeft(Canvas c, RecyclerView parent) {
        int childCount = parent.getChildCount();
        for (int position = 0; position < childCount; position++) {//第一列不繪制
            if (position % spanCount != 0) {
                View child = parent.getChildAt(position);
                int left = child.getLeft() - verticalSpaceSize;
                int right = left + verticalSpaceSize;
                int top = child.getTop() /*- horizontalSpaceSize*/;
                int bottom = child.getBottom();
                log("繪制豎直分割線" + left + "  " + right + "  " + top + "  " + bottom);
                verticalDivider.setBounds(left, top, right, bottom);
                verticalDivider.draw(c);
            }
        }
    }

    //繪制左右邊界
    private void drawLR(Canvas c, RecyclerView parent) {
        int childCount = parent.getChildCount();
        for (int position = 0; position < childCount; position++) {
            View child = parent.getChildAt(position);

            int maxLines = (childCount % spanCount == 0) ? childCount / spanCount : (childCount / spanCount + 1);//一共有多少行
            boolean isLastLineItem = position / spanCount == maxLines - 1;//倒數第一行的元素
            //最左邊那條線
            if (position % spanCount == 0) {
                int left = child.getLeft() - verticalSpaceSize;
                int right = left + verticalSpaceSize;
                int top = child.getTop() - horizontalSpaceSize;
                int bottom;
                if (isLastLineItem) {//【左下角】
                    bottom = child.getBottom() + horizontalSpaceSize;
                } else {
                    bottom = child.getBottom();
                }
                verticalDivider.setBounds(left, top, right, bottom);
                verticalDivider.draw(c);
                log("繪制最左邊那條線" + left + "  " + right + "  " + top + "  " + bottom);
            }

            //最右邊那條線
            if ((position + 1) % spanCount == 0) {
                int left = child.getRight();
                int right = left + verticalSpaceSize;
                int top = child.getTop() - horizontalSpaceSize;

                boolean isLastSecondLineItem = (position / spanCount == maxLines - 2) && (position + spanCount >= childCount);//倒數第二行的部分元素
                int bottom;
                if (isLastLineItem || isLastSecondLineItem) {
                    bottom = child.getBottom() + horizontalSpaceSize;//【右下角】
                } else {
                    bottom = child.getBottom();
                }
                verticalDivider.setBounds(left, top, right, bottom);
                verticalDivider.draw(c);
                log("繪制最右邊那條線" + left + "  " + right + "  " + top + "  " + bottom);
            }
        }
    }

    //繪制上下邊界,包括倒數第二行中的部分item
    private void drawLineAtTopAndBottom(Canvas c, RecyclerView parent) {
        int childCount = parent.getChildCount();
        for (int position = 0; position < childCount; position++) {
            View child = parent.getChildAt(position);

            //最上邊那條線
            if (includeHorizontalEdge && position < spanCount) {
                int top = child.getTop() - horizontalSpaceSize;
                int bottom = top + horizontalSpaceSize;
                int left = child.getLeft();

                int right;
                boolean isRightEdgeItem = (position + 1) % spanCount == 0;//最右側的元素
                boolean isFirstLineItem = childCount < spanCount && (position == childCount - 1);//只有一行的元素
                if (isRightEdgeItem || isFirstLineItem) {//【右上角】
                    right = child.getRight();
                } else {
                    right = child.getRight() + verticalSpaceSize;//如果同時有豎直分割線,則需要包含豎直分割線的寬度
                }

                horizontalDivider.setBounds(left, top, right, bottom);
                horizontalDivider.draw(c);
                log("繪制最上邊那條線" + left + "  " + right + "  " + top + "  " + bottom);
            }

            //最下邊那條線
            int maxLines = (childCount % spanCount == 0) ? childCount / spanCount : (childCount / spanCount + 1);//一共有多少行
            boolean isLastLineItem = position / spanCount == maxLines - 1;//倒數第一行的元素
            boolean isLastSecondLineItem = (position / spanCount == maxLines - 2) && (position + spanCount >= childCount);//倒數第二行的部分元素
            if ((includeHorizontalEdge && isLastLineItem) || isLastSecondLineItem) {
                int top = child.getBottom();
                int bottom = top + horizontalSpaceSize;
                int left = child.getLeft();
                int right;

                boolean isLastSecondLineEdgeItem = isLastSecondLineItem && ((position + 1) % spanCount == 0);//倒數第二行最后一個元素
                if (position == childCount - 1 || isLastSecondLineEdgeItem) {//【右下角】
                    right = child.getRight();
                } else {
                    right = child.getRight() + verticalSpaceSize;//如果同時有豎直分割線,則需要包含豎直分割線的寬度
                }

                horizontalDivider.setBounds(left, top, right, bottom);
                horizontalDivider.draw(c);
                log("繪制最下邊那條線" + left + "  " + right + "  " + top + "  " + bottom);
            }
        }
    }

    public static final class Builder {
        private Drawable horizontalDivider;
        private Drawable verticalDivider;
        private int spanCount;
        private int horizontalSpaceSize;
        private int verticalSpaceSize;
        private boolean includeHorizontalEdge;
        private boolean includeVerticalEdge;

        private Builder() {
        }

        public Builder horizontalDivider(Drawable horizontalDivider, int horizontalSpaceSize, boolean includeHorizontalEdge) {
            this.horizontalDivider = horizontalDivider;
            this.horizontalSpaceSize = horizontalSpaceSize;
            this.includeHorizontalEdge = includeHorizontalEdge;
            return this;
        }

        public Builder verticalDivider(Drawable verticalDivider, int verticalSpaceSize, boolean includeVerticalEdge) {
            this.verticalDivider = verticalDivider;
            this.verticalSpaceSize = verticalSpaceSize;
            this.includeVerticalEdge = includeVerticalEdge;
            return this;
        }

        public Builder spanCount(int val) {
            spanCount = val;
            return this;
        }

        public GridItemDecoration build() {
            return new GridItemDecoration(this);
        }
    }

    private void log(String msg) {
        Log.i("bqt", "【】" + msg);
    }

}

測試代碼

public class MainActivity extends Activity implements RecyclerAdapter.MyOnItemClickLitener {
    private RecyclerView mRecyclerView;
    private int spanCount;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String[] array = {"0、水平分割線,包含上下邊界",
                "1、水平分割線,不包含上下邊界",
                "2、豎直分割線,包含左右邊界",
                "3、豎直分割線,不包含左右邊界",

                "4、水平分割線+透明豎直分割線,包含邊界",
                "5、水平分割線+透明豎直分割線,不包含邊界",
                "6、透明水平分割線+豎直分割線,包含邊界",
                "7、透明水平分割線+豎直分割線,不包含邊界",

                "8、【水平分割線+豎直分割線,包含邊界】",
                "9、【水平分割線+豎直分割線,不包含邊界】",
                "10、圖片分割線+豎直分割線,包含邊界",
                "11、圖片分割線+豎直分割線,不包含邊界",

                "12、重置",
                "13、重置",
                "14、重置",};
        spanCount = 1 + new Random().nextInt(5);
        RecyclerAdapter mRecyclerViewAdapter = new RecyclerAdapter(this, Arrays.asList(array));
        mRecyclerViewAdapter.setOnItemClickLitener(this);

        mRecyclerView = new RecyclerView(this);
        mRecyclerView.setBackgroundColor(Color.BLACK);
        mRecyclerView.setAdapter(mRecyclerViewAdapter);//設置adapter
        mRecyclerView.setLayoutManager(new GridLayoutManager(this, spanCount));//設置布局管理器
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());//設置Item增加、移除動畫
        setContentView(mRecyclerView);
    }

    @Override
    public void onItemClick(View view, int position) {
        GridItemDecoration decoration = null;
        switch (position) {
            case 0:
                decoration = GridItemDecoration.newBuilder().spanCount(spanCount)
                        .horizontalDivider(new ColorDrawable(0x88ff0000), 60, true)
                        .build();
                break;
            case 1:
                decoration = GridItemDecoration.newBuilder().spanCount(spanCount)
                        .horizontalDivider(new ColorDrawable(0x88ff0000), 60, false)
                        .build();
                break;
            case 2:
                decoration = GridItemDecoration.newBuilder().spanCount(spanCount)
                        .verticalDivider(new ColorDrawable(0x88ff0000), 60, true)
                        .build();
                break;
            case 3:
                decoration = GridItemDecoration.newBuilder().spanCount(spanCount)
                        .verticalDivider(new ColorDrawable(0x88ff0000), 60, false)
                        .build();
                break;
            //******************************************************************************************
            case 4:
                decoration = GridItemDecoration.newBuilder().spanCount(spanCount)
                        .horizontalDivider(new ColorDrawable(0x88ff0000), 20, true)
                        .verticalDivider(new ColorDrawable(Color.TRANSPARENT), 60, true)
                        .build();
                break;
            case 5:
                decoration = GridItemDecoration.newBuilder().spanCount(spanCount)
                        .horizontalDivider(new ColorDrawable(0x88ff0000), 20, false)
                        .verticalDivider(new ColorDrawable(Color.TRANSPARENT), 60, false)
                        .build();
                break;
            case 6:
                decoration = GridItemDecoration.newBuilder().spanCount(spanCount)
                        .horizontalDivider(new ColorDrawable(Color.TRANSPARENT), 20, true)
                        .verticalDivider(new ColorDrawable(0x88ff0000), 60, true)
                        .build();
                break;
            case 7:
                decoration = GridItemDecoration.newBuilder().spanCount(spanCount)
                        .horizontalDivider(new ColorDrawable(Color.TRANSPARENT), 20, false)
                        .verticalDivider(new ColorDrawable(0x88ff0000), 60, false)
                        .build();
                break;
            //******************************************************************************************
            case 8:
                decoration = GridItemDecoration.newBuilder().spanCount(spanCount)
                        .horizontalDivider(new ColorDrawable(0x88ff0000), 20, true)
                        .verticalDivider(new ColorDrawable(0x88ff0000), 60, true)
                        .build();
                break;
            case 9:
                decoration = GridItemDecoration.newBuilder().spanCount(spanCount)
                        .horizontalDivider(new ColorDrawable(0x88ff0000), 20, false)
                        .verticalDivider(new ColorDrawable(0x88ff0000), 60, false)
                        .build();
                break;
            case 10:
                decoration = GridItemDecoration.newBuilder().spanCount(spanCount)
                        .horizontalDivider(getResources().getDrawable(R.drawable.icon2), 20, true)
                        .verticalDivider(new ColorDrawable(0x88ff0000), 60, true)
                        .build();
                break;
            case 11:
                decoration = GridItemDecoration.newBuilder().spanCount(spanCount)
                        .horizontalDivider(getResources().getDrawable(R.drawable.icon2), 20, false)
                        .verticalDivider(new ColorDrawable(0x88ff0000), 60, false)
                        .build();
                break;
            case 12:
            case 13:
            case 14:
                recreate();
                return;
        }
        mRecyclerView.addItemDecoration(decoration);
    }

    @Override
    public void onItemLongClick(View view, int position) {
        recreate();
    }
}

普通 Adapter

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.MyViewHolder> {
    private Context context;
    private List<String> mDatas;
    private MyOnItemClickLitener mOnItemClickLitener;

    public RecyclerAdapter(Context context, List<String> mDatas) {
        this.context = context;
        this.mDatas = mDatas;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.item, parent, false));
    }

    @Override
    public void onBindViewHolder(@NonNull final MyViewHolder holder, int position) {
        holder.tv.setText(mDatas.get(position));

        // 如果設置了回調,則設置點擊事件
        holder.itemView.setOnClickListener(v -> {
            if (mOnItemClickLitener != null) mOnItemClickLitener.onItemClick(holder.itemView, holder.getAdapterPosition());
        });
        holder.itemView.setOnLongClickListener(v -> {
            if (mOnItemClickLitener != null) mOnItemClickLitener.onItemLongClick(holder.itemView, holder.getAdapterPosition());
            return false;
        });
    }

    @Override
    public int getItemCount() {
        return mDatas.size();
    }

    /**
     * 添加並更新數據,同時具有動畫效果
     */
    public void addDataAt(int position, String data) {
        mDatas.add(position, data);
        notifyItemInserted(position);//更新數據集,注意如果用adapter.notifyDataSetChanged()將沒有動畫效果
    }

    /**
     * 移除並更新數據,同時具有動畫效果
     */
    public void removeDataAt(int position) {
        mDatas.remove(position);
        notifyItemRemoved(position);
    }

    public void setOnItemClickLitener(MyOnItemClickLitener mOnItemClickLitener) {
        this.mOnItemClickLitener = mOnItemClickLitener;
    }

    class MyViewHolder extends RecyclerView.ViewHolder {
        TextView tv;

        public MyViewHolder(View view) {
            super(view);
            tv = view.findViewById(R.id.tv_name);
        }
    }

    public interface MyOnItemClickLitener {
        void onItemClick(View view, int position);

        void onItemLongClick(View view, int position);
    }
}

item 布局

<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tv_name"
    android:layout_width="match_parent"
    android:layout_height="80dp"
    android:background="#00f"
    android:gravity="center"
    android:text="包青天"
    android:textColor="#fff"
    android:textSize="12sp"></TextView>

RecyclerView.ItemDecoration 簡介

RecyclerView 不支持divider這樣的屬性,雖然你可以通過給Item的布局設置 margin、background 等方式來間接添加分割線,但這種方式不夠優雅,也不好控制。使用RecyclerView時,我們的分割線可以在代碼中通過.addItemDecoration(RecyclerView.ItemDecoration)添加。

作用:控制Item間的間隔
已知子類:DividerItemDecoration,ItemTouchHelper
ItemDecoration允許應用程序向來自 adapter 的數據集中的特定項目 views 添加特殊 drawing 和布局 offset。這可用於在項目之間繪制分隔符、highlights、視覺分組邊界等。

All ItemDecorations are drawn in the order they were added, before the item views (在onDraw()方法中) and after the items (在onDrawOver(Canvas, RecyclerView, RecyclerView.State)方法中).

API

getItemOffsets

void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)

Retrieve any offsets for the given item.檢索給定項目的任何偏移量。

  • Each field of outRect specifies the number of pixels that the item view should be inset by, similar to padding or margin. The default implementation sets the bounds of outRect to 0 and returns.
    outRect的每個字段指定項目視圖應該插入的像素數,類似於填充或邊距。默認實現將outRect的范圍設置為0並返回。
  • If this ItemDecoration does not affect the positioning of item views, it should set all four fields of outRect (left, top, right, bottom) to zero before returning.
    如果此ItemDecoration不影響項目視圖的定位,則應將outRect(左,上,右,下)的所有四個字段設置為零,然后返回。
  • If you need to access Adapter for additional data, you can call getChildAdapterPosition(View) to get the adapter position of the View.
    如果您需要訪問適配器以獲取其他數據,可以調用getChildAdapterPosition獲取視圖的適配器位置。

參數:

  • outRec: Rect to receive the output.
  • view: The child view to decorate裝飾
  • parent: RecyclerView this ItemDecoration is decorating裝飾
  • state: The current state of RecyclerView.

onDraw

void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)
  • Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
    在提供給RecyclerView的Canvas中繪制任何適當的裝飾。
  • Any content drawn by this method will be drawn before the item views are drawn, and will thus appear underneath the views.
    在繪制項目視圖之前,將繪制通過此方法繪制的任何內容,因此將顯示在視圖下。

onDrawOver

void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)
  • Draw any appropriate decorations into the Canvas supplied to the RecyclerView.
    在提供給RecyclerView的Canvas中繪制任何適當的裝飾。
  • Any content drawn by this method will be drawn after the item views are drawn and will thus appear over the views.
    在繪制項目視圖之后,將繪制通過此方法繪制的任何內容,並將出現在視圖上。

廢棄方法

  • void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent)
    This method was deprecated in API level 22.0.0. Use getItemOffsets(Rect, View, RecyclerView, State)
  • void onDraw(Canvas c, RecyclerView parent)
    This method was deprecated in API level 22.0.0. Override onDraw(Canvas, RecyclerView, RecyclerView.State)
  • void onDrawOver(Canvas c, RecyclerView parent)
    This method was deprecated in API level 22.0.0. Override onDrawOver(Canvas, RecyclerView, RecyclerView.State)

官方案例 DividerItemDecoration

適合用在 LinearLayoutManager 上,不適合 GridLayoutManager,因為如果一行有多個 Item,則間隔線會多次繪制,並且如果 Item 為最后一列則右邊無間隔線,如果 Item 為最后一行則底部無分割線。

DividerItemDecoration is a RecyclerView.ItemDecoration that can be used as a divider between items of a LinearLayoutManager. It supports both HORIZONTAL and VERTICAL orientations.

mRecyclerView.addItemDecoration(new DividerItemDecoration(context, mLayoutManager.getOrientation()));

getItemOffsets 方法解析

getItemOffsets 主要用於繪制Decorator,可以通過outRect.set()為每個Item設置一定的偏移量。
DividerItemDecoration中getItemOffsets方法是這樣實現的:

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    if (mOrientation == VERTICAL) {
        outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
    } else {
        outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
    }
}

這個outRect設置的四個值是什么意思呢?先來看看它是在哪里調用的,它在RecyclerView中唯一被調用的地方就是 getItemDecorInsetsForChild(View child) 函數。

Rect getItemDecorInsetsForChild(View child) {
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    if (!lp.mInsetsDirty) {
        return lp.mDecorInsets;
    }
    if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
        // changed/invalid items should not be updated until they are rebound.
        return lp.mDecorInsets;
    }
    final Rect insets = lp.mDecorInsets;
    insets.set(0, 0, 0, 0);
    final int decorCount = mItemDecorations.size();
    for (int i = 0; i < decorCount; i++) {
        mTempRect.set(0, 0, 0, 0);
        mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
        insets.left += mTempRect.left;
        insets.top += mTempRect.top;
        insets.right += mTempRect.right;
        insets.bottom += mTempRect.bottom;
    }
    lp.mInsetsDirty = false;
    return insets;
}

可以看到,getItemDecorInsetsForChild 函數中會重置 insets 的值,並重新計算,計算方式就是把所有 ItemDecoration 的 getItemOffsets 中設置的值累加起來,而這個 insets 實際上是 RecyclerView 的 child 的 LayoutParams 中的一個屬性,它會在 getTopDecorationHeight, getBottomDecorationHeight 等函數中被返回,那么這個 insets 的意義就很明顯了,它記錄的是所有 ItemDecoration 所需要的尺寸的總和。

而在 RecyclerView 的 measureChild函數中,調用了 getItemDecorInsetsForChild,並把它算在了 child view 的 padding 中。

public void measureChild(View child, int widthUsed, int heightUsed) {
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
    widthUsed += insets.left + insets.right;
    heightUsed += insets.top + insets.bottom;
    final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
            getPaddingLeft() + getPaddingRight() + widthUsed, lp.width, canScrollHorizontally());
    final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
            getPaddingTop() + getPaddingBottom() + heightUsed, lp.height, canScrollVertically());
    if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
        child.measure(widthSpec, heightSpec);
    }
}

上面這段代碼中調用的 getChildMeasureSpec 函數的第三個參數就是 child view 的 padding,而這個參數就把 insets 的值算進去了。

那么現在就可以確認了:getItemOffsets 中為 outRect 設置的4個方向的值,將被計算進所有 decoration 的尺寸中,而這個尺寸,被計入了 RecyclerView 每個 item view 的 padding 中。

onDraw 方法解析

DividerItemDecoration 中 onDraw 方法是這樣實現的:

@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    if (parent.getLayoutManager() == null) return;
    if (mOrientation == VERTICAL) drawVertical(c, parent);
    else drawHorizontal(c, parent);
}

drawVertical 是為縱向的 RecyclerView 繪制 divider,我們看一下 drawVertical 的具體實現:

public void drawVertical(Canvas c, RecyclerView parent) {
    final int left = parent.getPaddingLeft();
    final int right = parent.getWidth() - parent.getPaddingRight();
    final int childCount = parent.getChildCount();
    for (int i = 0; i < childCount; i++) {
        final View child = parent.getChildAt(i);
        final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
        final int top = child.getBottom() + params.bottomMargin + Math.round(ViewCompat.getTranslationY(child));
        final int bottom = top + mDivider.getIntrinsicHeight();
        mDivider.setBounds(left, top, right, bottom);
        mDivider.draw(c);
    }
}

簡單說就是:遍歷每個 child view ,把 divider 繪制到 canvas 上,而 mDivider.setBounds 則設置了 divider 的繪制范圍。
其中:

  • left 設置為 parent.getPaddingLeft(),也就是左邊是 parent 也就是 RecyclerView 的左邊界加上 paddingLeft 之后的位置
  • right 設置為了 RecyclerView 的右邊界減去 paddingRight 之后的位置,那這里左右邊界就是 RecyclerView 的內容區域了
  • top 設置為了 child 的 bottom 加上 marginBottom 再加上 translationY,這其實就是 child view 的下邊界
  • bottom 就是簡單地 top 加上 divider 的高度

在 onDraw 為 divider 設置繪制范圍,並繪制到 canvas 上,而這個繪制范圍可以超出在 getItemOffsets 中設置的范圍,但由於 decoration 是繪制在 child view 的底下,所以並不可見,但是會存在 overdraw。

有一點需要注意:decoration 的 onDraw,child view 的 onDraw,decoration 的 onDrawOver,這三者是依次發生的。而由於 onDrawOver 是繪制在最上層的,所以它的繪制位置並不受限制(當然,decoration 的 onDraw 繪制范圍也不受限制,只不過不可見),所以利用 onDrawOver 可以做很多事情,例如為 RecyclerView 整體頂部繪制一個蒙層,或者為特定的 item view 繪制蒙層。一般我們選擇復寫其中一個即可。

總結

  • getItemOffsets 中為 outRect 設置的4個方向的值,將被計算進所有 decoration 的尺寸中,而這個尺寸,被計入了 RecyclerView 每個 item view 的 padding 中
  • decoration 的 onDraw,child view 的 onDraw,decoration 的 onDrawOver,這三者是依次發生的
  • 在 onDraw 為 divider 設置繪制范圍,並繪制到 canvas 上。雖然這個繪制范圍可以超出在 getItemOffsets 中設置的范圍,但由於onDraw 方法是在drawChildren之前調用的, 所以 decoration 是繪制在 Children 的底下,所以超出在 getItemOffsets 中設置的范圍的部分並不可見(被Children覆蓋了),這就造成了 overdraw(過度繪制)的問題,影響性能。
  • onDrawOver 是在drawChildren之后繪制的,是繪制在最上層的,所以不會被Children覆蓋,所以它的繪制位置並不受限制

2018-5-18


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM