// Gets the view for the next element that we should layout. // Also updates current item index to the next item, based on {@link #mItemDirection} // // @return The next element that we should layout. public FlexibleView.ViewHolder Next(FlexibleView.Recycler recycler) { FlexibleView.ViewHolder itemView = recycler.GetViewForPosition(CurrentPosition); CurrentPosition += ItemDirection; return(itemView); }
private float ScrollBy(float dy, FlexibleView.Recycler recycler, bool immediate) { if (ChildCount == 0 || dy == 0) { return(0); } mLayoutState.Recycle = true; int layoutDirection = dy < 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START; float absDy = Math.Abs(dy); UpdateLayoutState(layoutDirection, absDy, true); float consumed = mLayoutState.ScrollingOffset + Fill(recycler, mLayoutState, false, immediate); if (consumed < 0) { return(0); } float scrolled = absDy > consumed ? -layoutDirection * consumed : dy; Cache(recycler, mLayoutState, immediate, scrolled); mOrientationHelper.OffsetChildren(scrolled, immediate); return(scrolled); }
private void Cache(FlexibleView.Recycler recycler, LayoutState layoutState, bool immediate, float scrolled = 0) { if (layoutState.LayoutDirection == LayoutState.LAYOUT_END) { // get the first child in the direction we are going FlexibleView.ViewHolder child = GetChildClosestToEnd(); if (child != null) { if (child.ItemView.Focusable == false || mOrientationHelper.GetViewHolderEnd(child) + scrolled < mOrientationHelper.GetEnd()) { layoutState.Available = MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace(); layoutState.Extra = 0; layoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN; layoutState.Recycle = false; Fill(recycler, layoutState, true, immediate); } } } else { FlexibleView.ViewHolder child = GetChildClosestToStart(); if (child != null) { if (child.ItemView.Focusable == false || mOrientationHelper.GetViewHolderStart(child) + scrolled > 0) { layoutState.Available = MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace(); layoutState.Extra = 0; layoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN; layoutState.Recycle = false; Fill(recycler, layoutState, true, immediate); } } } }
public override float ScrollVerticallyBy(float dy, FlexibleView.Recycler recycler, bool immediate) { if (mOrientation == HORIZONTAL) { return(0); } return(ScrollBy(dy, recycler, immediate)); }
public override float ScrollHorizontallyBy(float dx, FlexibleView.Recycler recycler, bool immediate) { if (mOrientation == VERTICAL) { return(0); } return(ScrollBy(dx, recycler, immediate)); }
internal virtual void LayoutChunk(FlexibleView.Recycler recycler, LayoutState layoutState, LayoutChunkResult result) { FlexibleView.ViewHolder holder = layoutState.Next(recycler); if (holder == null) { // if we are laying out views in scrap, this may return null which means there is // no more items to layout. result.Finished = true; return; } if (mShouldReverseLayout == (layoutState.LayoutDirection == LayoutState.LAYOUT_START)) { AddView(holder); } else { AddView(holder, 0); } result.Consumed = mOrientationHelper.GetViewHolderMeasurement(holder); float left, top, width, height; if (mOrientation == VERTICAL) { width = Width - PaddingLeft - PaddingRight; height = result.Consumed; left = PaddingLeft; if (layoutState.LayoutDirection == LayoutState.LAYOUT_END) { top = layoutState.Offset; } else { top = layoutState.Offset - height; } LayoutChild(holder, left, top, width, height); } else { width = result.Consumed; height = Height - PaddingTop - PaddingBottom; top = PaddingTop; if (layoutState.LayoutDirection == LayoutState.LAYOUT_END) { left = layoutState.Offset; } else { left = layoutState.Offset - width; } LayoutChild(holder, left, top, width, height); } result.Focusable = true; }
public override void OnLayoutChildren(FlexibleView.Recycler recycler) { mLayoutState.Recycle = false; if (!mAnchorInfo.Valid || mPendingScrollPosition != NO_POSITION) { mAnchorInfo.Reset(); mAnchorInfo.LayoutFromEnd = mShouldReverseLayout; // calculate anchor position and coordinate UpdateAnchorInfoForLayout(recycler, mAnchorInfo); mAnchorInfo.Valid = true; } int firstLayoutDirection; if (mAnchorInfo.LayoutFromEnd) { firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL : LayoutState.ITEM_DIRECTION_HEAD; } else { firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD : LayoutState.ITEM_DIRECTION_TAIL; } EnsureAnchorReady(recycler, mAnchorInfo, firstLayoutDirection); ScrapAttachedViews(recycler); if (mAnchorInfo.LayoutFromEnd == true) { UpdateLayoutStateToFillStart(mAnchorInfo.Position, mAnchorInfo.Coordinate); Fill(recycler, mLayoutState, false, true); Cache(recycler, mLayoutState, true); UpdateLayoutStateToFillEnd(mAnchorInfo.Position, mAnchorInfo.Coordinate); mLayoutState.CurrentPosition += mLayoutState.ItemDirection; Fill(recycler, mLayoutState, false, true); Cache(recycler, mLayoutState, true); } else { UpdateLayoutStateToFillEnd(mAnchorInfo.Position, mAnchorInfo.Coordinate); Fill(recycler, mLayoutState, false, true); Cache(recycler, mLayoutState, true); UpdateLayoutStateToFillStart(mAnchorInfo.Position, mAnchorInfo.Coordinate); mLayoutState.CurrentPosition += mLayoutState.ItemDirection; Fill(recycler, mLayoutState, false, true); Cache(recycler, mLayoutState, true); } OnLayoutCompleted(); }
/** * Finds an anchor child from existing Views. Most of the time, this is the view closest to * start or end that has a valid position (e.g. not removed). * If a child has focus, it is given priority. */ private bool UpdateAnchorFromChildren(FlexibleView.Recycler recycler, AnchorInfo anchorInfo) { if (GetChildCount() == 0) { return(false); } FlexibleView.ViewHolder anchorChild = FindFirstCompleteVisibleItemView(); anchorInfo.Position = anchorChild.LayoutPosition; anchorInfo.Coordinate = mOrientationHelper.GetViewHolderStart(anchorChild); return(true); }
private void UpdateAnchorInfoForLayout(FlexibleView.Recycler recycler, AnchorInfo anchorInfo) { if (UpdateAnchorFromPendingData(anchorInfo)) { return; } if (UpdateAnchorFromChildren(recycler, anchorInfo)) { return; } anchorInfo.Position = FocusPosition != NO_POSITION ? FocusPosition : 0; anchorInfo.Coordinate = anchorInfo.LayoutFromEnd ? mOrientationHelper.GetEndAfterPadding() : mOrientationHelper.GetStartAfterPadding(); }
private void RecycleByLayoutState(FlexibleView.Recycler recycler, LayoutState layoutState, bool immediate) { if (!layoutState.Recycle) { return; } if (layoutState.LayoutDirection == LayoutState.LAYOUT_START) { RecycleViewsFromEnd(recycler, layoutState.ScrollingOffset, immediate); } else { RecycleViewsFromStart(recycler, layoutState.ScrollingOffset, immediate); } }
// Finds an anchor child from existing Views. Most of the time, this is the view closest to // start or end that has a valid position (e.g. not removed). // If a child has focus, it is given priority. private bool UpdateAnchorFromChildren(FlexibleView.Recycler recycler, AnchorInfo anchorInfo) { if (ChildCount == 0) { return(false); } FlexibleView.ViewHolder anchorChild = FindFirstVisibleItemView(); if (anchorChild == null) { Log.Error("flexibleview", $"exception occurs when updating anchor information!"); anchorChild = GetChildAt(0); } anchorInfo.Position = anchorChild.LayoutPosition; anchorInfo.Coordinate = mOrientationHelper.GetViewHolderStart(anchorChild); return(true); }
private void RecycleViewsFromStart(FlexibleView.Recycler recycler, float dt, bool immediate) { if (dt < 0) { return; } // ignore padding, ViewGroup may not clip children. float limit = dt; int childCount = ChildCount; if (mShouldReverseLayout) { for (int i = childCount - 1; i >= 0; i--) { FlexibleView.ViewHolder child = GetChildAt(i); if (mOrientationHelper.GetViewHolderEnd(child) > limit) { // stop here RecycleChildren(recycler, childCount - 1, i, immediate); return; } } } else { for (int i = 0; i < childCount; i++) { FlexibleView.ViewHolder child = GetChildAt(i); if (mOrientationHelper.GetViewHolderEnd(child) > limit) { // stop here RecycleChildren(recycler, 0, i, immediate); return; } } } }
private void RecycleViewsFromEnd(FlexibleView.Recycler recycler, float dt, bool immediate) { int childCount = GetChildCount(); if (dt < 0) { return; } float limit = mOrientationHelper.GetEnd() - dt; if (mShouldReverseLayout) { for (int i = 0; i < childCount; i++) { FlexibleView.ViewHolder child = GetChildAt(i); if (mOrientationHelper.GetViewHolderStart(child) < limit) { // stop here RecycleChildren(recycler, 0, i, immediate); return; } } } else { for (int i = childCount - 1; i >= 0; i--) { FlexibleView.ViewHolder child = GetChildAt(i); if (mOrientationHelper.GetViewHolderStart(child) < limit) { // stop here RecycleChildren(recycler, childCount - 1, i, immediate); return; } } } }
internal override void EnsureAnchorReady(FlexibleView.Recycler recycler, AnchorInfo anchorInfo, int itemDirection) { bool layingOutInPrimaryDirection = (itemDirection == LayoutState.ITEM_DIRECTION_TAIL); int span = anchorInfo.Position % mSpanCount; if (layingOutInPrimaryDirection) { // choose span 0 while (span > 0 && anchorInfo.Position > 0) { anchorInfo.Position--; span = anchorInfo.Position; } } else { // choose the max span we can get. hopefully last one int indexLimit = ChildCount - 1; int pos = anchorInfo.Position; int bestSpan = span; while (pos < indexLimit) { int next = (pos + 1) % mSpanCount; if (next > bestSpan) { pos += 1; bestSpan = next; } else { break; } } anchorInfo.Position = pos; } }
internal override void LayoutChunk(FlexibleView.Recycler recycler, LayoutState layoutState, LayoutChunkResult result) { bool layingOutInPrimaryDirection = layoutState.ItemDirection == LayoutState.ITEM_DIRECTION_TAIL; int count = mSpanCount; for (int i = 0; i < count; i++) { FlexibleView.ViewHolder holder = layoutState.Next(recycler); if (holder == null) { result.Finished = true; return; } if (layingOutInPrimaryDirection) { AddView(holder); } else { AddView(holder, 0); } result.Consumed = mOrientationHelper.GetViewHolderMeasurement(holder); float left, top, width, height; if (mOrientation == VERTICAL) { width = (Width - PaddingLeft - PaddingRight) / count; height = result.Consumed; if (layoutState.LayoutDirection == LayoutState.LAYOUT_END) { left = PaddingLeft + width * i; top = layoutState.Offset; } else { left = PaddingLeft + width * (count - 1 - i); top = layoutState.Offset - height; } LayoutChild(holder, left, top, width, height); } else { width = result.Consumed; height = (Height - PaddingTop - PaddingBottom) / count; if (layoutState.LayoutDirection == LayoutState.LAYOUT_END) { top = PaddingTop + height * i; left = layoutState.Offset; } else { top = PaddingTop + height * (count - 1 - i); left = layoutState.Offset - width; } LayoutChild(holder, left, top, width, height); } } }
internal virtual void EnsureAnchorReady(FlexibleView.Recycler recycler, AnchorInfo anchorInfo, int itemDirection) { }
internal override FlexibleView.ViewHolder OnFocusSearchFailed(FlexibleView.ViewHolder focused, FlexibleView.LayoutManager.Direction direction, FlexibleView.Recycler recycler) { if (ChildCount == 0) { return(null); } int layoutDir = ConvertFocusDirectionToLayoutDirection(direction); if (layoutDir == LayoutState.INVALID_LAYOUT) { return(null); } int maxScroll = (int)(MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace()); UpdateLayoutState(layoutDir, maxScroll, false); mLayoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN; mLayoutState.Recycle = false; Fill(recycler, mLayoutState, true, true); FlexibleView.ViewHolder nextFocus; if (layoutDir == LayoutState.LAYOUT_START) { nextFocus = GetChildAt(0); } else { nextFocus = GetChildAt(ChildCount - 1); } return(nextFocus); }
private float Fill(FlexibleView.Recycler recycler, LayoutState layoutState, bool stopOnFocusable, bool immediate) { float start = layoutState.Available; if (layoutState.ScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) { // TODO ugly bug fix. should not happen if (layoutState.Available < 0) { layoutState.ScrollingOffset += layoutState.Available; } if (immediate == true) { RecycleByLayoutState(recycler, layoutState, true); } } float remainingSpace = layoutState.Available + layoutState.Extra; LayoutChunkResult layoutChunkResult = mLayoutChunkResult; while ((remainingSpace > 0) && layoutState.HasMore(ItemCount)) { layoutChunkResult.ResetInternal(); LayoutChunk(recycler, layoutState, layoutChunkResult); if (layoutChunkResult.Finished) { break; } layoutState.Offset += layoutChunkResult.Consumed * layoutState.LayoutDirection; // 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.IgnoreConsumed) { layoutState.Available -= layoutChunkResult.Consumed; // we keep a separate remaining space because mAvailable is important for recycling remainingSpace -= layoutChunkResult.Consumed; } if (layoutState.ScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) { layoutState.ScrollingOffset += layoutChunkResult.Consumed; if (layoutState.Available < 0) { layoutState.ScrollingOffset += layoutState.Available; } if (immediate == true) { RecycleByLayoutState(recycler, layoutState, true); } } if (stopOnFocusable && layoutChunkResult.Focusable) { break; } } if (immediate == false) { RecycleByLayoutState(recycler, layoutState, false); } return(start - layoutState.Available); }