看源码之前,先看一下ItemDecoration能给我们实现的效果图:
看静态图标记的这条分割线,每个子itemView如果想空开距离或者想产生明显的边界的话,就应该写ItemDecoration类的实现类,也就是说RecyclerView没有像ListView一样为我们实现了分割线功能,先看一下实现的代码
mRecyclerView.addItemDecoration(new DividerItemDecoration(this,LinearLayoutManager.VERTICAL));
@Override
public void onDrawOver(Canvas c, RecyclerView parent, State state) {
// TODO Auto-generated method stub
super.onDrawOver(c, parent, state);
}
/**
* 分垂直方向画和水平方向画
*/
@Override
public void onDraw(Canvas c, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
/**
* 垂直方向画线
*
* @param c
* @param parent
*/
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++) {
if (i == childCount - 1)
break;
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
/*
* 水平方向画线
*/
public void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
if (i == childCount - 1)
break;
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
/**
* 获得分割线的大小
*/
@Override
public void getItemOffsets(Rect outRect, int itemPosition,
RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
public void addItemDecoration(ItemDecoration decor, int index) {
if (mLayout != null) {
mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or"
+ " layout");
}
// 将decor加入集合中
if (mItemDecorations.isEmpty()) {
setWillNotDraw(false);
}
if (index < 0) {
mItemDecorations.add(decor);
} else {
mItemDecorations.add(index, decor);
}
//标记当前的显示的子view的params中的mInsetsDirty为true
markItemDecorInsetsDirty();
//重新测量,布局重画
requestLayout();
}
这个方法意思很明了了,就是向mItemDecorations加入ItemDecoration 对象,然后将在屏幕中的itemView的params中的mInsetsDirty设为true,将缓存中的view的params中的mInsetsDirty设为true,最后执行View树的重新测量、重写布局位置、重写画。根据最后的View树的重画后,那么大体可以推测出ItemDecoration应该在重绘的重要的方法中都有用到。好,首先看一下测量
protected void onMeasure(int widthSpec, int heightSpec) {
// 正在测量的时候数据通知改变
if (mAdapterUpdateDuringMeasure) {
eatRequestLayout();
processAdapterUpdatesAndSetAnimationFlags();
/**
* 如果设置了动画的话mState.mInPreLayout为true
*/
if (mState.mRunPredictiveAnimations) {
// TODO: try to provide a better approach.
// When RV decides to run predictive animations, we need to
// measure in pre-layout
// state so that pre-layout pass results in correct layout.
// On the other hand, this will prevent the layout manager from
// resizing properly.
mState.mInPreLayout = true;
} else {
// consume remaining updates to provide a consistent state with
// the layout pass.
mAdapterHelper.consumeUpdatesInOnePass();
mState.mInPreLayout = false;
}
mAdapterUpdateDuringMeasure = false;
resumeRequestLayout(false);
}
if (mAdapter != null) {
// 为状态添加子View的数量
mState.mItemCount = mAdapter.getItemCount();
} else {
mState.mItemCount = 0;
}
// 假如布局文件为空,那么采用默认测量
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
} else {
// 否则用布局文件测量
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
}
mState.mInPreLayout = false; // clear
}
void dispatchLayout() {
// 没有adapter和mLayout直接返回
if (mAdapter == null) {
Log.e(TAG, "No adapter attached; skipping layout");
return;
}
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
return;
}
// 储存动画信息的类
mViewInfoStore.clear();
eatRequestLayout();
// 布局和滚动的累加器
onEnterLayoutOrScroll();
// 设置一些必要的参数
processAdapterUpdatesAndSetAnimationFlags();
mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations
&& mItemsChanged;
mItemsAddedOrRemoved = mItemsChanged = false;
// 如果有动画的话mInPreLayout为true
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
此处省去若干行……
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// Step 2: Run layout
mState.mInPreLayout = false;
//最终调用mLayout的onLayoutChildren进行布局
mLayout.onLayoutChildren(mRecycler, mState);
此处省去若干行…….
// Step 3: Find out where things are now, and process change
}
}
public void onMeasure(Recycler recycler, State state, int widthSpec,
int heightSpec) {
mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
}
private void defaultOnMeasure(int widthSpec, int heightSpec) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
final int widthSize = MeasureSpec.getSize(widthSpec);
final int heightSize = MeasureSpec.getSize(heightSpec);
int width = 0;
int height = 0;
switch (widthMode) {
case MeasureSpec.EXACTLY:
case MeasureSpec.AT_MOST:
width = widthSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
width = ViewCompat.getMinimumWidth(this);
break;
}
switch (heightMode) {
case MeasureSpec.EXACTLY:
case MeasureSpec.AT_MOST:
height = heightSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
height = ViewCompat.getMinimumHeight(this);
break;
}
setMeasuredDimension(width, height);
}
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler);
if (view == null) {
if (DEBUG && layoutState.mScrapList == null) {
throw new RuntimeException("received null view when unexpected");
}
// if we are laying out views in scrap, this may return null which
// means there is
// no more items to layout.
result.mFinished = true;
return;
}
LayoutParams params = (LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
//测量子View、
measureChildWithMargins(view, 0, 0);
//result.mConsumed=子View总够需要的空间
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
int left, top, right, bottom;
if (mOrientation == VERTICAL) {
if (isLayoutRTL()) {
right = getWidth() - getPaddingRight();
left = right
- mOrientationHelper
.getDecoratedMeasurementInOther(view);
} else {
left = getPaddingLeft();
right = left
+ mOrientationHelper
.getDecoratedMeasurementInOther(view);
}
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
bottom = layoutState.mOffset;
top = layoutState.mOffset - result.mConsumed;
} else {
top = layoutState.mOffset;
bottom = layoutState.mOffset + result.mConsumed;
}
} else {
top = getPaddingTop();
//需加上分割线的距离定义top的位置
bottom = top
+ mOrientationHelper.getDecoratedMeasurementInOther(view);
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
right = layoutState.mOffset;
left = layoutState.mOffset - result.mConsumed;
} else {
left = layoutState.mOffset;
right = layoutState.mOffset + result.mConsumed;
}
}
// We calculate everything with View's bounding box (which includes
// decor and margins)
// To calculate correct layout position, we subtract margins.
layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
right - params.rightMargin, bottom - params.bottomMargin);
if (DEBUG) {
Log.d(TAG, "laid out child at position " + getPosition(view)
+ ", with l:" + (left + params.leftMargin) + ", t:"
+ (top + params.topMargin) + ", r:"
+ (right - params.rightMargin) + ", b:"
+ (bottom - params.bottomMargin));
}
// Consume the available space if the view is not removed OR changed
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
result.mFocusable = view.isFocusable();
}
public void measureChildWithMargins(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(),
getPaddingLeft() + getPaddingRight() + lp.leftMargin
+ lp.rightMargin + widthUsed, lp.width,
canScrollHorizontally());
final int heightSpec = getChildMeasureSpec(getHeight(),
getPaddingTop() + getPaddingBottom() + lp.topMargin
+ lp.bottomMargin + heightUsed, lp.height,
canScrollVertically());
child.measure(widthSpec, heightSpec);
}
Rect getItemDecorInsetsForChild(View child) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.mInsetsDirty) {
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;
}
/**
* 获得分割线的大小
*/
@Override
public void getItemOffsets(Rect outRect, int itemPosition,
RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
/**
* 返回水平方向上上此控件加上间距和分割线总的距离(所需要占用的空间)
*/
@Override
public int getDecoratedMeasurementInOther(View view) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view
.getLayoutParams();
return mLayoutManager.getDecoratedMeasuredWidth(view)
+ params.leftMargin + params.rightMargin;
}
@Override
public void draw(Canvas c) {
super.draw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDrawOver(c, this, mState);
}
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState);
}
}
draw和onDraw这两个方法可
以看出ItemDecoration的onDraw方法会在绘制完自身之后再回调,而onDrawOver方法会在绘制完子view之后再回调,所以ItemDecoration进行绘制的时候先回调onDraw再回调onDrawOver,也就是说我们画分割线的时候覆写onDraw或onDrawOver都可以,那么看一下实现public void onDraw(Canvas c, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
/**
* 垂直方向画线
*
* @param c
* @param parent
*/
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++) {
if (i == childCount - 1)
break;
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
总结:1、RecycleView通过ItemDecoration的getItemOffsets辅助测量子View的大小
2、RecycleView通过ItemDecoration的getItemOffsets方法将间距扩大
3、RecycleView通过ItemDecoration的onDraw方法或onDrawOver方法将分割线画到屏幕上