RecyclerView基本使用
1 2 3 4 5 6 7 8 9 10 | //首先设置RecyclerView的布局管理模式 mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); mAdapter = new MyAdapter(getData()); //设置Item项的UI装饰器 mRecyclerView.addItemDecoration(new RecyclerItemDecoration(LinearLayoutManager.VERTICAL)); //设置Item项的不同操作的动画 mRecyclerView.setItemAnimator(new DefaultItemAnimator()); //设置数据开始装配 mRecyclerView.setAdapter(mAdapter); RecyclerView原理分析 |
Adapter数据适配
RecyclerView.Adapter类中有一个很重要的属性:
1 2 3 | //Adapter中被观察对象 Observale<AdapterDataObserver> private final AdapterDataObservable mObservable = new AdapterDataObservable(); |
RecyclerView中也有一个很重要的属性
1 2 | //数据观察者, AdapterDataObserver实例 private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver(); |
当使用recyclerView.setAdapter(data)设置数据时,会调用以下方法使得RecyclerView成为Adapter的观察者(间接):
1 2 3 4 5 6 7 8 9 10 11 | private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) {...... if (adapter != null) {//通过此处添加观察者,此时RecyclerView就会对Adapter中的数据进行观察监听 adapter.registerAdapterDataObserver(mObserver); ...... } ...... } |
通常当我们改变Adapter中的数据源时,一般都会通过调用Adapter.notifyDataSetChanged()方法来刷新列表,我们来看看这个方法的实现,看看Adapter是如何通过这个方法来刷新列表的:
1 2 3 | public final void notifyDataSetChanged() {mObservable.notifyChanged(); } |
我们接着看AdapterDataObservable.notifyChanged()方法实现:
1 2 3 4 5 6 | //mObservers是Observable中的属性,是一个ArrayList<T> public void notifyChanged() { for (int i = mObservers.size() - 1; i >= 0; i--) {mObservers.get(i).onChanged(); } } |
我们再来看RecyclerViewDataObserver.onChanged()方法:
1 2 3 4 5 6 7 8 | @Override public void onChanged() {...... //Adapter目前没有待更新的数据 if (!mAdapterHelper.hasPendingUpdates()) {requestLayout(); } } |
看到requestLayout()这个方法,我们就明白了,调用此方法后系统会重新measure, layout, draw,这样列表视图就会被更新。
RecyclerView.onMeasure()
我们来看看RecyclerView的测量方法onMeasure:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | @Override protected void onMeasure(int widthSpec, int heightSpec) {...... if (mLayout.mAutoMeasure) {final int widthMode = MeasureSpec.getMode(widthSpec); final int heightMode = MeasureSpec.getMode(heightSpec); final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY; //委托给LayoutManager来进行测量 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); if (skipMeasure || mAdapter == null) {return; } if (mState.mLayoutStep == State.STEP_START) {dispatchLayoutStep1(); } mLayout.setMeasureSpecs(widthSpec, heightSpec); mState.mIsMeasuring = true; dispatchLayoutStep2(); mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); ...... } } ...... } |
上面onMeasure方法中mLayout变量就是我们上面设置的LinearLayoutManager实例,而LinearLayoutManager的构造函数中给变量mAutoMeasure值设置为true,因此测量时就会执行上面的代码,通过上面的方法我们可以看到此处测量分为两种:
当RecyclerView的宽高设置为match_parent或具体值的时候,skipMeasure=true,此时会只需要测量其自身的宽高就可以知道RecyclerView的大小,这时是onMeasure方法测量结束。
当RecyclerView的宽高设置为wrap_content时,skipMeasure=false,onMeasure会继续执行下面的dispatchLayoutStep2(),其实就是测量RecyclerView的子视图的大小最终确定RecyclerView的实际大小,这种情况真正的测量操作都是在方法dispatchLayoutStep2()里执行的:
1 2 3 4 5 6 7 8 | private void dispatchLayoutStep2() {...... mState.mItemCount = mAdapter.getItemCount(); // Step 2: Run layout mState.mInPreLayout = false; mLayout.onLayoutChildren(mRecycler, mState); ...... } |
从这里也可以看出RecyclerView真正的测量是委托给LayoutManager在处理,我们看看LinearLayoutManager的onLayoutChildren方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {...... if (mAnchorInfo.mLayoutFromEnd) {... fill(recycler, mLayoutState, state, false); ...... } else {...... fill(recycler, mLayoutState, state, false); ...... } ...... layoutForPredictiveAnimations(recycler, state, startOffset, endOffset); ...... } |
很明显可以看到,最终执行了fill()方法:
1 2 3 4 5 6 7 8 9 10 | int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) {...... LayoutChunkResult layoutChunkResult = new LayoutChunkResult(); while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {layoutChunkResult.resetInternal(); layoutChunk(recycler, state, layoutState, layoutChunkResult); ...... } ...... } |
上面的while判断条件中remainingSpace可以理解为当前列表中是否还有多余的位置可用于添加绘制child,而layoutState.hasMore(state)则是判断当前绘制的child索引位置是否在Adapter数据范围内
1 2 3 | boolean hasMore(RecyclerView.State state) {return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount(); } |
再来看上面的layoutChunk()方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {View view = layoutState.next(recycler); ...... 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); ...... // To calculate correct layout position, we subtract margins. layoutDecorated(view, left + params.leftMargin, top + params.topMargin, right - params.rightMargin, bottom - params.bottomMargin); ...... } |
上面的方法中addView与addDisappearingView最终都是调用的RecyclerView的addView方法,也就是将子child添加到RecyclerView中。
我们再来看看View view = layoutState.next(recycler);这行代码的实现:
1 2 3 4 5 6 7 8 9 10 11 | View next(RecyclerView.Recycler recycler) { if (mScrapList != null) {return nextViewFromScrapList(); } final View view = recycler.getViewForPosition(mCurrentPosition); //获取某个位置需要展示的View mCurrentPosition += mItemDirection; //将当前绘制的child的索引下移一位,配合while循环 return view; } |
我们看看上面的获取position位置的view是如何获取的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | View getViewForPosition(int position, boolean dryRun) {...... ViewHolder holder = null; // 0) If there is a changed scrap, try to find from there if (mState.isPreLayout()) {holder = getChangedScrapViewForPosition(position); fromScrap = holder != null; } // 1) Find from scrap by position if (holder == null) {holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun); ...... } if (holder == null) {final int offsetPosition = mAdapterHelper.findPositionOffset(position); ...... final int type = mAdapter.getItemViewType(offsetPosition); // 2) Find from scrap via stable ids, if exists if (mAdapter.hasStableIds()) {holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); ...... } //mViewCacheExtension的缓存是由开发者自己实现来控制ViewHolder的缓存策略 if (holder == null && mViewCacheExtension != null) {// We are NOT sending the offsetPosition because LayoutManager does not // know it. final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type); if (view != null) {holder = getChildViewHolder(view); ...... } } if (holder == null) { // fallback to recycler...... holder = getRecycledViewPool().getRecycledView(type); ...... } if (holder == null) {holder = mAdapter.createViewHolder(RecyclerView.this, type); ...... } } ...... boolean bound = false; if (mState.isPreLayout() && holder.isBound()) {// do not update unless we absolutely have to. holder.mPreLayoutPosition = position; } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {...... holder.mOwnerRecyclerView = RecyclerView.this; //此处就是调用Adapter中bindViewHolder方法 mAdapter.bindViewHolder(holder, offsetPosition); ...... } ...... return holder.itemView; } |
将指定位置的View获取得到之后添加到RecyclerView中,紧接着再来看后面执行的measureChildWithMargins方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 | public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {final LayoutParams lp = (LayoutParams) child.getLayoutParams(); //通过ItemDecorate获取offset final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); widthUsed += insets.left + insets.right; heightUsed += insets.top + insets.bottom; final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin + widthUsed, lp.width, canScrollHorizontally()); final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin + heightUsed, lp.height, canScrollVertically()); if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {child.measure(widthSpec, heightSpec); } } |
该方法中调用mRecyclerView.getItemDecorInsetsForChild(child);获取child的offset,然后对child重新测量绘制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Rect getItemDecorInsetsForChild(View child) {...... 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; } |
这个里面的mItemDecorations就是文章开头例子中我通过mRecyclerView.addItemDecoration(new RecyclerItemDecoration(LinearLayoutManager.VERTICAL));添加的Item装饰器
1 2 3 4 | 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); } |
可以看到layoutDecorated方法中直接调用了View的layout方法对child视图进行layout布局。
到此RecyclerView列表中Item项视图的measure和layout实际上已经完成。
RecyclerView.onLayout
看完onMeasure方法,再来看看onLayout方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | @Override protected void onLayout(boolean changed, int l, int t, int r, int b) {TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG); dispatchLayout(); TraceCompat.endSection(); mFirstLayoutComplete = true; } void dispatchLayout() {...... mState.mIsMeasuring = false; if (mState.mLayoutStep == State.STEP_START) {...... dispatchLayoutStep2(); } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()) {...... dispatchLayoutStep2(); } else {// always make sure we sync them (to ensure mode is exact) mLayout.setExactMeasureSpecsFrom(this); } dispatchLayoutStep3(); } |
通过dispatchLayout方法可以看到onLayout中又执行了我们前面分析过的dispatchLayoutStep2()方法,在最后又执行了一个dispatchLayoutStep3()方法,我们再来看看这个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | private void dispatchLayoutStep3() {...... if (mState.mRunSimpleAnimations) { for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); ...... final ItemHolderInfo animationInfo = mItemAnimator .recordPostLayoutInformation(mState, holder); ...... if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {...... if (oldDisappearing && oldChangeViewHolder == holder) {//此处会执行动画 // run disappear animation instead of change mViewInfoStore.addToPostLayout(holder, animationInfo); } else {...... if (preInfo == null) {handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder); } else {//此方法最终调用DefaultItemAnimate的相关动画 animateChange(oldChangeViewHolder, holder, preInfo, postInfo, oldDisappearing, newDisappearing); } } } else {mViewInfoStore.addToPostLayout(holder, animationInfo); } } // Step 4: Process view info lists and trigger animations mViewInfoStore.process(mViewInfoProcessCallback); } ...... } |
上面的方法中调用了ItemAnimation动画类的相关方法
RecyclerView.onDraw
1 2 3 4 5 6 7 8 9 | @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); } } |
可以看到ItemDecoration的onDraw方法是在此处调用
1 2 3 4 5 6 7 8 9 10 | @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); } ...... } |
这个地方可以看到ItemDecoration的onDrawOver方法是在此处调用
到这里,RecyclerView使用过程中比较常用的几个类(LayoutManager, ItemDecoration, ItemAnimation)的主要作用及使用场景有了个大概的了解。
在RecyclerView中是没有为我们内置Item的单击和长按事件监听接口的,一般为Item设置单击和长按监听都是是直接在Adapter初始化Item视图时,为我们的Item视图直接设置单击监听和长按监听,这种方式与Adapter的耦合度比较高,而且频繁的为view设置监听对象,感觉不太好。其实RecyclerView中为我们提供了一个类OnItemTouchListener通过这个类再结合手势GestureDetector完全可以实现一个耦合度更低复用度更高的单击和长按监听。我们再来看看OnItemTouchListener的实现方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | @Override public boolean onTouchEvent(MotionEvent e) {...... if (dispatchOnItemTouch(e)) {cancelTouch(); return true; } ...... } private boolean dispatchOnItemTouch(MotionEvent e) {final int action = e.getAction(); if (mActiveOnItemTouchListener != null) { if (action == MotionEvent.ACTION_DOWN) {// Stale state from a previous gesture, we're starting a new one. Clear it. mActiveOnItemTouchListener = null; } else {//此处即调用OnItemTouchListener的方法 mActiveOnItemTouchListener.onTouchEvent(this, e); if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {// Clean up for the next gesture. mActiveOnItemTouchListener = null; } return true; } } // Listeners will have already received the ACTION_DOWN via dispatchOnItemTouchIntercept // as called from onInterceptTouchEvent; skip it. if (action != MotionEvent.ACTION_DOWN) {final int listenerCount = mOnItemTouchListeners.size(); for (int i = 0; i < listenerCount; i++) {final OnItemTouchListener listener = mOnItemTouchListeners.get(i); //此处即调用OnItemTouchListener的方法 if (listener.onInterceptTouchEvent(this, e)) {mActiveOnItemTouchListener = listener; return true; } } } return false; } @Override public boolean onInterceptTouchEvent(MotionEvent e) {...... if (dispatchOnItemTouchIntercept(e)) {cancelTouch(); return true; } ...... } private boolean dispatchOnItemTouchIntercept(MotionEvent e) {final int action = e.getAction(); if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) {mActiveOnItemTouchListener = null; } final int listenerCount = mOnItemTouchListeners.size(); for (int i = 0; i < listenerCount; i++) {final OnItemTouchListener listener = mOnItemTouchListeners.get(i); if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {mActiveOnItemTouchListener = listener; return true; } } return false; } |
随时随地看视频