好久没有出来嫖了,哦不对是好久没有出来浪了。之前发了一个很简单的TestParallax
的小小的demo,经过自己不要脸的,丧心病狂的在几个群里面求大婶们的Star,终于到了10颗了。本来打算写一个Parallax
的讲解的,但是觉得太简单了(主要还是自己太懒了),请不要打我
俗话说时间就像乳沟挤一挤就会有的,终于今天不睡午觉,挤一点出来了!好了开始今天的RecyclerView
的初探吧,其实我主要是讲解一点很浅浅的源码姿势,毕竟源码我也只能看懂一些点
入门使用,请参看翔哥的友情链接http://blog.csdn.net/lmj623565791/article/details/45059587
假如你已经知道了它的最基本的用法了,看下简单的代码
public void doWallWork() {
final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
recyclerView.setHasFixedSize(true);
// if you use StaggeredGridLayoutManager.VERTICAL , you can set item height but set item width not working
// if you use StaggeredGridLayoutManager.HORIZONTAL , you can set item width but set item height not working
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
MyCommonAdapter adapter = new MyCommonAdapter(this);
recyclerView.setAdapter(adapter);
adapter.addEntity(DataProvider.getDataList());
}
和ListView
不一样,多了一些东西;但是多了这些东西你可以做很多事情,比如动画,自定义分割线,瀑布流等等很多特性。
上面可以看到RecyclerView
的初级使用很简单,先设置它的LayoutManager
,然后在设置它的Adapter
。
好了那我们还是去看下它的源码糗一糗,打开源码一看,有2W多行,真是日了狗了,不过看源码有时候也需要动下脑壳加点技巧的。
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild {
private static final String TAG = "RecyclerView";
private static final boolean DEBUG = false;
/** * On Kitkat and JB MR2, there is a bug which prevents DisplayList from being invalidated if * a View is two levels deep(wrt to ViewHolder.itemView). DisplayList can be invalidated by * setting View's visibility to INVISIBLE when View is detached. On Kitkat and JB MR2, Recycler * recursively traverses itemView and invalidates display list for each ViewGroup that matches * this criteria. */
可以看出来它其实是一个自定义的ViewGroup
,其实这篇博客的主要内容就是围绕这个展开的。
自定义ViewGroup,大家项目或多或少多用过的吧,如果没有用过可以去看下线性布局的代码或者去看aige的自定义view系列博客。
由于是自定义的,一般我们都要去重写onMeasure
和onLayout
,即是先知道大小,然后去布局里面的子控件。
先看他的测量方法,onMeasure
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
// 省略一些代码...
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
} else {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
}
mState.mInPreLayout = false; // clear
}
这里调用了mLayout.onMeasure
方法,最终会调用defaultOnMeasure
方法
/** * Used when onMeasure is called before layout manager is set */
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);
}
可以看到最终还是调用了setMeasuredDimension
方法
现在改去看他的onLayout
方法了:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
eatRequestLayout();
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
// 这里主要注意这里
dispatchLayout();
TraceCompat.endSection();
resumeRequestLayout(false);
mFirstLayoutComplete = true;
}
/** * Wrapper around layoutChildren() that handles animating changes caused by layout. * Animations work on the assumption that there are five different kinds of items * in play: * PERSISTENT: items are visible before and after layout * REMOVED: items were visible before layout and were removed by the app * ADDED: items did not exist before layout and were added by the app * DISAPPEARING: items exist in the data set before/after, but changed from * visible to non-visible in the process of layout (they were moved off * screen as a side-effect of other changes) * APPEARING: items exist in the data set before/after, but changed from * non-visible to visible in the process of layout (they were moved on * screen as a side-effect of other changes) * The overall approach figures out what items exist before/after layout and * infers one of the five above states for each of the items. Then the animations * are set up accordingly: * PERSISTENT views are animated via * {@link ItemAnimator#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)} * DISAPPEARING views are animated via * {@link ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} * APPEARING views are animated via * {@link ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)} * and changed views are animated via * {@link ItemAnimator#animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)}. */
void dispatchLayout() {
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;
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
if (mState.mRunSimpleAnimations) {
// Step 0: Find out where all non-removed items are, pre-layout
int count = mChildHelper.getChildCount();
for (int i = 0; i < count; ++i) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
continue;
}
final ItemHolderInfo animationInfo = mItemAnimator
.recordPreLayoutInformation(mState, holder,
ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
holder.getUnmodifiedPayloads());
mViewInfoStore.addToPreLayout(holder, animationInfo);
if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
&& !holder.shouldIgnore() && !holder.isInvalid()) {
long key = getChangedHolderKey(holder);
// This is NOT the only place where a ViewHolder is added to old change holders
// list. There is another case where:
// * A VH is currently hidden but not deleted
// * The hidden item is changed in the adapter
// * Layout manager decides to layout the item in the pre-Layout pass (step1)
// When this case is detected, RV will un-hide that view and add to the old
// change holders list.
mViewInfoStore.addToOldChangeHolders(key, holder);
}
}
}
if (mState.mRunPredictiveAnimations) {
// Step 1: run prelayout: This will use the old positions of items. The layout manager
// is expected to layout everything, even removed items (though not to add removed
// items back to the container). This gives the pre-layout position of APPEARING views
// which come into existence as part of the real layout.
// Save old positions so that LayoutManager can run its mapping logic.
saveOldPositions();
final boolean didStructureChange = mState.mStructureChanged;
mState.mStructureChanged = false;
// temporarily disable flag because we are asking for previous layout
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = didStructureChange;
for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
final View child = mChildHelper.getChildAt(i);
final ViewHolder viewHolder = getChildViewHolderInt(child);
if (viewHolder.shouldIgnore()) {
continue;
}
if (!mViewInfoStore.isInPreLayout(viewHolder)) {
int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
boolean wasHidden = viewHolder
.hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
if (!wasHidden) {
flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
}
final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
if (wasHidden) {
recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
} else {
mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
}
}
}
// we don't process disappearing list because they may re-appear in post layout pass.
clearOldPositions();
mAdapterHelper.consumePostponedUpdates();
} else {
clearOldPositions();
}
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
// onLayoutChildren may have caused client code to disable item animations; re-check
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
if (mState.mRunSimpleAnimations) {
// Step 3: Find out where things are now, and process change animations.
int count = mChildHelper.getChildCount();
for (int i = 0; i < count; ++i) {
ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
if (holder.shouldIgnore()) {
continue;
}
long key = getChangedHolderKey(holder);
final ItemHolderInfo animationInfo = mItemAnimator
.recordPostLayoutInformation(mState, holder);
ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
// run a change animation
final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(
oldChangeViewHolder);
animateChange(oldChangeViewHolder, holder, preInfo, animationInfo);
} else {
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
}
// Step 4: Process view info lists and trigger animations
mViewInfoStore.process(mViewInfoProcessCallback);
}
resumeRequestLayout(false);
mLayout.removeAndRecycleScrapInt(mRecycler);
mState.mPreviousLayoutItemCount = mState.mItemCount;
mDataSetHasChangedAfterLayout = false;
mState.mRunSimpleAnimations = false;
mState.mRunPredictiveAnimations = false;
onExitLayoutOrScroll();
mLayout.mRequestedSimpleAnimations = false;
if (mRecycler.mChangedScrap != null) {
mRecycler.mChangedScrap.clear();
}
mViewInfoStore.clear();
if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
dispatchOnScrolled(0, 0);
}
}
可以看到里面加入了动画的一些处理,但是我现在只是关心最主要的流程,从方法的注释上面可以看到layoutChildren
很重要,方法里面调用了mLayout.onLayoutChildren(mRecycler, mState);
说明了布局子控件相关的操作交给了mLayout
,其实mLayout
只是一个父类,真正的实体应该是recyclerView.setLayoutManager(layoutManager)
,是我们自己设置进去的布局管理器。看下比较简单的他的一个实现类LinearLayoutManager
,看下它里面的onLayoutChildren
方法:
/** * {@inheritDoc} */
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// 省略一大串代码...
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
final int firstElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForEnd += mLayoutState.mAvailable;
}
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
// end could not consume all. add more items towards start
extraForStart = mLayoutState.mAvailable;
updateLayoutStateToFillStart(firstElement, startOffset);
mLayoutState.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
}
// 省略辣么多代码...
看到了updateLayoutStateToFillStart
和fill
。对应一个ViewGroup
,addView
和child.layout()
,这2个方法应该都是必不可少的东西,好了进去看fill
:
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// 省略代码...
LayoutChunkResult layoutChunkResult = new LayoutChunkResult();
while (remainingSpace > 0 && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (layoutChunkResult.mFinished) {
break;
}
// 省略代码...
然后去看layoutChunk
:
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
// 这里很重要,会调用onCreateViewHolder方法
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);
}
}
measureChildWithMargins(view, 0, 0);
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();
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();
}
可以看到layoutDecorated
之前把LayoutParams params
做了一些处理,在看下layoutDecorated
:
public void layoutDecorated(View child, int left, int top, int right, int bottom) {
final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
child.layout(left + insets.left, top + insets.top, right - insets.right,
bottom - insets.bottom);
}
逻辑和我们的猜测也是一样的,显示调用了addView,然后在去调用child.layout。绕了大半圈终于还是看出它里面的纯朴的本质了,简直泪奔。。。
然而貌似我们的Adapter
里面的2个重要的方法还没找到被调用的地方,即是onCreateViewHolder
和onCreateViewHolder
方法。其实onCreateViewHolder
刚才我们已经很接近了,在View view = layoutState.next(recycler)
,然后我们进去看下
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
去看下getViewForPosition
:
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
// 省略代码...
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (DEBUG) {
Log.d(TAG, "getViewForPosition created new ViewHolder");
}
}
}
// 省略代码..
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
holder.mOwnerRecyclerView = RecyclerView.this;
mAdapter.bindViewHolder(holder, offsetPosition);
// 省略代码..
}
可以看到先是create,然后再是bind
进去看下mAdapter.createViewHolder
/** * This method calls {@link #onCreateViewHolder(ViewGroup, int)} to create a new * {@link ViewHolder} and initializes some private fields to be used by RecyclerView. * * @see #onCreateViewHolder(ViewGroup, int) */
public final VH createViewHolder(ViewGroup parent, int viewType) {
TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
// 熟悉的onCreateViewHolder。。。
final VH holder = onCreateViewHolder(parent, viewType);
holder.mItemViewType = viewType;
TraceCompat.endSection();
return holder;
}
然后再去看mAdapter.bindViewHolder
public final void bindViewHolder(VH holder, int position) {
// 省略代码..
onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
holder.clearPayload();
TraceCompat.endSection();
}
看下onBindViewHolder
实现
public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
onBindViewHolder(holder, position);
}
这里的onBindViewHolder(holder, position)
是一个抽象方法。终于把onMeasure
,onLayout
,onCreateViewHolder
,onBindViewHolder
的关系终于理清楚了。
菜鸟写的文章始终还是这么菜,友情链接一篇http://www.jianshu.com/p/17dfa01f6553。最后有兴趣的可以去看下我https://github.com/t2314862168/TestParallax上面的experiment分支里面的代码demo!!!!