美文网首页Android开发
RecyclerView获取指定位置的ItemView

RecyclerView获取指定位置的ItemView

作者: 深耕项目管理 | 来源:发表于2018-04-18 15:25 被阅读1666次

    tips:观察AndroidStudio的调用堆栈,对看源码分析问题很有帮助

    业务场景

    RecyclerView+LinearLayoutManager实现视频播放列表。当前视频播放完成后,自动向上滚动一个item,并且开始播放。

    遇到问题

    1.问题代码

    //滑动一个item
    recyclerView.scrollToPosition((msg.arg1 + 1));
    View childAt = layoutManager.findViewByPosition((msg.arg1 + 1));
     if (childAt!=null){
        //视频开始播放
        NiceVideoPlayer niceVideoPlayer=childAt.findViewById(R.id.nice_video_player);
        niceVideoPlayer.start();
      }
    

    当前屏幕上有3个item,从第0个开始播放。第0个播放完成,播放第1个。第1个播放完成,播放第2个,layoutManager.findViewByPosition都可以获取到item。加载position为3的item时,layoutManager.findViewByPosition返回null。奇怪的事,position为3的item已经显示在屏幕上。
    2.修改后的代码

    //742是一个item的高度
    recyclerView.scrollBy(0,742);
    View childAt = layoutManager.findViewByPosition((msg.arg1 + 1));
     if (childAt!=null){
        //视频开始播放
        NiceVideoPlayer niceVideoPlayer=childAt.findViewById(R.id.nice_video_player);
        niceVideoPlayer.start();
      }
    

    原因分析

    1.recyclerView.scrollToPosition((msg.arg1 + 1))为啥不行(未完待续)

    RecyclerView
    public void scrollToPosition(int position) {
            if (mLayoutFrozen) {
                return;
            }
            stopScroll();
            if (mLayout == null) {
                Log.e(TAG, "Cannot scroll to position a LayoutManager set. "
                        + "Call setLayoutManager with a non-null argument.");
                return;
            }
            mLayout.scrollToPosition(position);
            awakenScrollBars();
        }
    
    LinearLayoutManager
    @Override
        public void scrollToPosition(int position) {
            mPendingScrollPosition = position;
            mPendingScrollPositionOffset = INVALID_OFFSET;
            if (mPendingSavedState != null) {
                mPendingSavedState.invalidateAnchor();
            }
            requestLayout();
        }
    
    LinearLayoutManager
    public void requestLayout() {
                if (mRecyclerView != null) {
                    mRecyclerView.requestLayout();
                }
            }
    

    RecyclerView.scrollToPosition最终调用了RecyclerView的request()方法,RecyclerVeiw的onMeasure(),onLayout()都会被调用,onDraw()方法可能别调用,这没分析完,下次再写。

    2.recyclerView.scrollBy(0,742)为啥可以

    RecyclerView
    @Override
        public void scrollBy(int x, int y) {
            if (mLayout == null) {
                Log.e(TAG, "Cannot scroll without a LayoutManager set. "
                        + "Call setLayoutManager with a non-null argument.");
                return;
            }
            if (mLayoutFrozen) {
                return;
            }
            final boolean canScrollHorizontal = mLayout.canScrollHorizontally();
            final boolean canScrollVertical = mLayout.canScrollVertically();
            if (canScrollHorizontal || canScrollVertical) {
                scrollByInternal(canScrollHorizontal ? x : 0, canScrollVertical ? y : 0, null);
            }
        }
    
    RecyclerView
    boolean scrollByInternal(int x, int y, MotionEvent ev) {
            int unconsumedX = 0, unconsumedY = 0;
            int consumedX = 0, consumedY = 0;
    
            consumePendingUpdateOperations();
            if (mAdapter != null) {
                eatRequestLayout();
                onEnterLayoutOrScroll();
                TraceCompat.beginSection(TRACE_SCROLL_TAG);
                fillRemainingScrollValues(mState);
                if (x != 0) {
                    consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
                    unconsumedX = x - consumedX;
                }
                if (y != 0) {
                    consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
                    unconsumedY = y - consumedY;
                }
                TraceCompat.endSection();
                repositionShadowingViews();
                onExitLayoutOrScroll();
                resumeRequestLayout(false);
            }
            if (!mItemDecorations.isEmpty()) {
                invalidate();
            }
    
            if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,
                    TYPE_TOUCH)) {
                // Update the last touch co-ords, taking any scroll offset into account
                mLastTouchX -= mScrollOffset[0];
                mLastTouchY -= mScrollOffset[1];
                if (ev != null) {
                    ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
                }
                mNestedOffsets[0] += mScrollOffset[0];
                mNestedOffsets[1] += mScrollOffset[1];
            } else if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
                if (ev != null && !MotionEventCompat.isFromSource(ev, InputDevice.SOURCE_MOUSE)) {
                    pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY);
                }
                considerReleasingGlowsOnScroll(x, y);
            }
            if (consumedX != 0 || consumedY != 0) {
                dispatchOnScrolled(consumedX, consumedY);
            }
            if (!awakenScrollBars()) {
                invalidate();
            }
            return consumedX != 0 || consumedY != 0;
        }
    
    LinearLayoutManager
    @Override
        public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
                RecyclerView.State state) {
            if (mOrientation == HORIZONTAL) {
                return 0;
            }
            return scrollBy(dy, recycler, state);
        }
    
    LinearLayoutManager
    int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
            if (getChildCount() == 0 || dy == 0) {
                return 0;
            }
            mLayoutState.mRecycle = true;
            ensureLayoutState();
            final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
            final int absDy = Math.abs(dy);
            updateLayoutState(layoutDirection, absDy, true, state);
            mLayoutState.mScrollingOffset显示下一个itemView前还可以滑多远
            fill()方法负责填充滑动过程中需要显示的item
            final int consumed = mLayoutState.mScrollingOffset
                    + fill(recycler, mLayoutState, state, false);
            if (consumed < 0) {
                if (DEBUG) {
                    Log.d(TAG, "Don't have any more elements to scroll");
                }
                return 0;
            }
            算出滑动的距离
            final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
            执行滑动操作
            mOrientationHelper.offsetChildren(-scrolled);
            if (DEBUG) {
                Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
            }
            mLayoutState.mLastScrollDelta = scrolled;
            return scrolled;
        }
    
    LinearLayoutManager
    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
                RecyclerView.State state, boolean stopOnFocusable) {
            // max offset we should set is mFastScroll + available
           start是需要填充的高度
            start除以每个item的高度,可以算出需要填充几个item
            final int start = layoutState.mAvailable;
            if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
                // TODO ugly bug fix. should not happen
                if (layoutState.mAvailable < 0) {
                    layoutState.mScrollingOffset += layoutState.mAvailable;
                }
                recycleByLayoutState(recycler, layoutState);
            }
            remainingSpace需要填充的总高度,与start是相等的
            int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
            LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
            while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
                layoutChunkResult.resetInternal();
                if (VERBOSE_TRACING) {
                    TraceCompat.beginSection("LLM LayoutChunk");
                }
                加载下一个ViewHolder显示在屏幕上
                layoutChunk(recycler, state, layoutState, layoutChunkResult);
                if (VERBOSE_TRACING) {
                    TraceCompat.endSection();
                }
                if (layoutChunkResult.mFinished) {
                    break;
                }
                layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
                /**
                 * Consume the available space if:
                 * * layoutChunk did not request to be ignored
                 * * OR we are laying out scrap children
                 * * OR we are not doing pre-layout
                 */
                if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
                        || !state.isPreLayout()) {
                    layoutState.mAvailable -= layoutChunkResult.mConsumed;
                    // we keep a separate remaining space because mAvailable is important for recycling
                    layoutChunkResult.mConsumed是消耗了多少高度,大部分时候都等于item的高度。加载完成下一个item之后,remainingSpace相应减少
                    remainingSpace -= layoutChunkResult.mConsumed;
                }
    
                if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
                    layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
                    if (layoutState.mAvailable < 0) {
                        layoutState.mScrollingOffset += layoutState.mAvailable;
                    }
                    recycleByLayoutState(recycler, layoutState);
                }
                if (stopOnFocusable && layoutChunkResult.mFocusable) {
                    break;
                }
            }
            if (DEBUG) {
                validateChildOrder();
            }
            return start - layoutState.mAvailable;
        }
    
    LinearLayoutManager
    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                LayoutState layoutState, LayoutChunkResult result) {
            获取下一个View
            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();
    RecyclerVeiw虽然比较复杂,但它毕竟还是ViewGroup,所以显示在它上面的View还是要add
            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 = 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.
            layoutDecoratedWithMargins(view, left, top, right, bottom);
            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.hasFocusable();
        }
    
    LinearLayoutManager.LayoutState
     View next(RecyclerView.Recycler recycler) {
                if (mScrapList != null) {
                    return nextViewFromScrapList();
                }
                到Recycler中获取下一个View
                final View view = recycler.getViewForPosition(mCurrentPosition);
                positon加1
                mCurrentPosition += mItemDirection;
                return view;
            }
    
    RecyclerView.Recycler
    ViewHolder的获取顺序
     ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                    boolean dryRun, long deadlineNs) {
                if (position < 0 || position >= mState.getItemCount()) {
                    throw new IndexOutOfBoundsException("Invalid item position " + position
                            + "(" + position + "). Item count:" + mState.getItemCount()
                            + exceptionLabel());
                }
                boolean fromScrapOrHiddenOrCache = false;
                ViewHolder holder = null;
                // 0) If there is a changed scrap, try to find from there
                if (mState.isPreLayout()) {
                    holder = getChangedScrapViewForPosition(position);
                    fromScrapOrHiddenOrCache = holder != null;
                }
                // 1) Find by position from scrap/hidden list/cache
                if (holder == null) {
                    holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
                    if (holder != null) {
                        if (!validateViewHolderForOffsetPosition(holder)) {
                            // recycle holder (and unscrap if relevant) since it can't be used
                            if (!dryRun) {
                                // we would like to recycle this but need to make sure it is not used by
                                // animation logic etc.
                                holder.addFlags(ViewHolder.FLAG_INVALID);
                                if (holder.isScrap()) {
                                    removeDetachedView(holder.itemView, false);
                                    holder.unScrap();
                                } else if (holder.wasReturnedFromScrap()) {
                                    holder.clearReturnedFromScrapFlag();
                                }
                                recycleViewHolderInternal(holder);
                            }
                            holder = null;
                        } else {
                            fromScrapOrHiddenOrCache = true;
                        }
                    }
                }
                if (holder == null) {
                    final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                    if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
                        throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                                + "position " + position + "(offset:" + offsetPosition + ")."
                                + "state:" + mState.getItemCount() + exceptionLabel());
                    }
    
                    final int type = mAdapter.getItemViewType(offsetPosition);
                    // 2) Find from scrap/cache via stable ids, if exists
                    if (mAdapter.hasStableIds()) {
                        holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                                type, dryRun);
                        if (holder != null) {
                            // update position
                            holder.mPosition = offsetPosition;
                            fromScrapOrHiddenOrCache = true;
                        }
                    }
                    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) {
                                throw new IllegalArgumentException("getViewForPositionAndType returned"
                                        + " a view which does not have a ViewHolder"
                                        + exceptionLabel());
                            } else if (holder.shouldIgnore()) {
                                throw new IllegalArgumentException("getViewForPositionAndType returned"
                                        + " a view that is ignored. You must call stopIgnoring before"
                                        + " returning this view." + exceptionLabel());
                            }
                        }
                    }
                    if (holder == null) { // fallback to pool
                        if (DEBUG) {
                            Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
                                    + position + ") fetching from shared pool");
                        }
                        holder = getRecycledViewPool().getRecycledView(type);
                        if (holder != null) {
                            holder.resetInternal();
                            if (FORCE_INVALIDATE_DISPLAY_LIST) {
                                invalidateDisplayListInt(holder);
                            }
                        }
                    }
                    if (holder == null) {
                        long start = getNanoTime();
                        if (deadlineNs != FOREVER_NS
                                && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
                            // abort - we have a deadline we can't meet
                            return null;
                        }
                        创建ViewHolder
                        holder = mAdapter.createViewHolder(RecyclerView.this, type);
                        if (ALLOW_THREAD_GAP_WORK) {
                            // only bother finding nested RV if prefetching
                            RecyclerView innerView = findNestedRecyclerView(holder.itemView);
                            if (innerView != null) {
                                holder.mNestedRecyclerView = new WeakReference<>(innerView);
                            }
                        }
    
                        long end = getNanoTime();
                        mRecyclerPool.factorInCreateTime(type, end - start);
                        if (DEBUG) {
                            Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
                        }
                    }
                }
    
                // This is very ugly but the only place we can grab this information
                // before the View is rebound and returned to the LayoutManager for post layout ops.
                // We don't need this in pre-layout since the VH is not updated by the LM.
                if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder
                        .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
                    holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
                    if (mState.mRunSimpleAnimations) {
                        int changeFlags = ItemAnimator
                                .buildAdapterChangeFlagsForAnimations(holder);
                        changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
                        final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
                                holder, changeFlags, holder.getUnmodifiedPayloads());
                        recordAnimationInfoIfBouncedHiddenView(holder, info);
                    }
                }
    
                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()) {
                    if (DEBUG && holder.isRemoved()) {
                        throw new IllegalStateException("Removed holder should be bound and it should"
                                + " come here only in pre-layout. Holder: " + holder
                                + exceptionLabel());
                    }
                    final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                    ViewHolder数据绑定
                    bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
                }
    
                final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
                final LayoutParams rvLayoutParams;
                if (lp == null) {
                    rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
                    holder.itemView.setLayoutParams(rvLayoutParams);
                } else if (!checkLayoutParams(lp)) {
                    rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
                    holder.itemView.setLayoutParams(rvLayoutParams);
                } else {
                    rvLayoutParams = (LayoutParams) lp;
                }
                rvLayoutParams.mViewHolder = holder;
                rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
                return holder;
            }
    
    RecyclerView.Rcycler
    private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition,
                    int position, long deadlineNs) {
                holder.mOwnerRecyclerView = RecyclerView.this;
                final int viewType = holder.getItemViewType();
                long startBindNs = getNanoTime();
                if (deadlineNs != FOREVER_NS
                        && !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) {
                    // abort - we have a deadline we can't meet
                    return false;
                }
                mAdapter.bindViewHolder调用
                mAdapter.bindViewHolder(holder, offsetPosition);
                long endBindNs = getNanoTime();
                mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);
                attachAccessibilityDelegateOnBind(holder);
                if (mState.isPreLayout()) {
                    holder.mPreLayoutPosition = position;
                }
                return true;
            }
    

    RecyclerView的scrollBy()方法完成后,所有的item已经绑定了新的数据,在界面上显示好了。那么多数据,在我的项目中RecyclerView只用了3个item。回归到View体系,就是ViewGroup+3个子View。为了更少的findViewById(),添加了ViewHolder。对ViewHolder的缓存比较复杂。

    思考

    scrollToPosition()方法为什么不使用这种实现,而要requestLayout()呢?有清楚的小伙伴可以告诉我哦。

    相关文章

      网友评论

        本文标题:RecyclerView获取指定位置的ItemView

        本文链接:https://www.haomeiwen.com/subject/pbgvkftx.html