protected override Line CreateLine(FillDirection direction, int extentOffset, int breadthOffset, int availableBreadth, RecyclerView.Recycler recycler, RecyclerView.State state, IndexPath nextVisibleItem, bool isNewGroup ) { var itemsInLine = ResolveMaximumItemsInLine(availableBreadth); var firstItemInLine = nextVisibleItem; //Find first item in line, since the item we are passed is the last if (direction == FillDirection.Back) { // We are recreating the last line of the group - it may be truncated (if the total items are not an even multiple // of the items-per-line). if (isNewGroup) { itemsInLine = XamlParent.GetItemsOnLastLine(firstItemInLine.Section, itemsInLine); } for (int i = 0; i < itemsInLine - 1; i++) { firstItemInLine = GetNextUnmaterializedItem(FillDirection.Back, firstItemInLine).Value; var isCorrectGroup = firstItemInLine.Section == nextVisibleItem.Section; if (!isCorrectGroup) { //TODO: fix bug that makes this happen (#47229) } Debug.Assert(isCorrectGroup, GetAssertMessage("First item should not be from a different group")); } } IndexPath lastItemInLine = firstItemInLine; IndexPath?currentItem = firstItemInLine; var availableWidth = ResolveAvailableWidth(availableBreadth); var availableHeight = ResolveAvailableHeight(availableBreadth); int usedBreadth = 0; for (int i = 0; i < itemsInLine; i++) { var view = recycler.GetViewForPosition(GetFlatItemIndex(currentItem.Value), state); Debug.Assert(view is SelectorItem, "view is SelectorItem (we should never be given a group header)"); //Add view before we measure it, this ensures that DP inheritances are correctly applied AddView(view, direction); var slotSize = new Windows.Foundation.Size(availableWidth, availableHeight).PhysicalToLogicalPixels(); var measuredSize = _layouter.MeasureChild(view, slotSize); var physicalMeasuredSize = measuredSize.LogicalToPhysicalPixels(); var measuredWidth = (int)physicalMeasuredSize.Width; var measuredHeight = (int)physicalMeasuredSize.Height; if (_implicitItemWidth == null) { //Set these values to dimensions of first materialised item _implicitItemWidth = measuredWidth; _implicitItemHeight = measuredHeight; availableWidth = ResolveAvailableWidth(availableBreadth); availableHeight = ResolveAvailableHeight(availableBreadth); itemsInLine = ResolveMaximumItemsInLine(availableBreadth); } LayoutChild(view, FillDirection.Forward, //We always lay out view 'top down' so that it is aligned correctly if its height is less than the line height direction == FillDirection.Forward ? extentOffset : extentOffset - ResolveItemExtent().Value, breadthOffset + usedBreadth, new Foundation.Size(width: slotSize.Width, height: slotSize.Height) ); usedBreadth += ResolveItemBreadth().Value; lastItemInLine = currentItem.Value; currentItem = GetNextUnmaterializedItem(FillDirection.Forward, currentItem); if (currentItem == null || currentItem.Value.Section != firstItemInLine.Section) { itemsInLine = i + 1; break; } } return(new Line { NumberOfViews = itemsInLine, Extent = ResolveItemExtent().Value, Breadth = usedBreadth, FirstItem = firstItemInLine, LastItem = lastItemInLine }); }
/// <summary> /// Remove all views not in the target range from the buffer. /// </summary> private void UnbufferViews(RecyclerView.Recycler recycler) { TrimEmpty(); UnbufferTrailing(); UnbufferLeading(); TrimEmpty(); //Empty items may have been exposed by unbuffering CheckValidSteadyState(); void UnbufferTrailing() { if (TrailingBufferTargetSize == 0) { while (_trailingBuffer.Count > 0) { var record = _trailingBuffer.RemoveFromBack(); CheckValidState(); SendToIntermediateCache(recycler, record); } return; } while (TrailingBufferStart < TrailingBufferTargetStart) { if (_trailingBuffer.Count == 0) { return; } var record = _trailingBuffer.RemoveFromFront(); TrimEmpty(); CheckValidState(); SendToIntermediateCache(recycler, record); } while (TrailingBufferEnd > TrailingBufferTargetEnd) { if (_trailingBuffer.Count == 0) { return; } var record = _trailingBuffer.RemoveFromBack(); TrimEmpty(); CheckValidState(); SendToIntermediateCache(recycler, record); } } void UnbufferLeading() { if (LeadingBufferTargetSize == 0) { while (_leadingBuffer.Count > 0) { var record = _leadingBuffer.RemoveFromBack(); CheckValidState(); SendToIntermediateCache(recycler, record); } return; } while (LeadingBufferStart < LeadingBufferTargetStart) { if (_leadingBuffer.Count == 0) { return; } var record = _leadingBuffer.RemoveFromFront(); TrimEmpty(); CheckValidState(); CheckValidSteadyState(); SendToIntermediateCache(recycler, record); } while (LeadingBufferEnd > LeadingBufferTargetEnd) { if (_leadingBuffer.Count == 0) { return; } var record = _leadingBuffer.RemoveFromBack(); TrimEmpty(); CheckValidState(); CheckValidSteadyState(); SendToIntermediateCache(recycler, record); } } }
/// <summary> /// Prefetch views in the target range that aren't yet in the buffer. /// </summary> private void PrefetchViews(RecyclerView.Recycler recycler, RecyclerView.State state) { if (Layout.ItemCount == 0 || CacheHalfLength == 0) { return; } PrefetchTrailing(); PrefetchLeading(); if (!_isInitiallyPopulated) { PrefetchExtra(); _isInitiallyPopulated = true; } CheckValidSteadyState(); void PrefetchTrailing() { if (TrailingBufferTargetSize == 0) { return; } // Seed buffer; otherwise succeeding logic fails if (_trailingBuffer.Count == 0) { var record = PrefetchView(recycler, state, TrailingBufferTargetStart); _trailingBuffer.AddToBack(record); CheckValidState(); } while (TrailingBufferStart > TrailingBufferTargetStart) { var record = PrefetchView(recycler, state, TrailingBufferStart - 1); _trailingBuffer.AddToFront(record); CheckValidState(); } while (TrailingBufferEnd < TrailingBufferTargetEnd) { var record = PrefetchView(recycler, state, TrailingBufferEnd); _trailingBuffer.AddToBack(record); CheckValidState(); } } void PrefetchLeading() { if (LeadingBufferTargetSize == 0) { return; } // Seed buffer if (_leadingBuffer.Count == 0) { var record = PrefetchView(recycler, state, LeadingBufferTargetStart); _leadingBuffer.AddToBack(record); CheckValidState(); } while (LeadingBufferStart > LeadingBufferTargetStart) { var record = PrefetchView(recycler, state, LeadingBufferStart - 1); _leadingBuffer.AddToFront(record); CheckValidState(); } while (LeadingBufferEnd < LeadingBufferTargetEnd) { var record = PrefetchView(recycler, state, LeadingBufferEnd); _leadingBuffer.AddToBack(record); CheckValidState(); } } // Initially pre-cache a half-width of items directly to the intermediate cache. This is an optimization so that new views // aren't created to fill the trailing buffer when the user first scrolls. void PrefetchExtra() { if (TrailingBufferTargetSize > 0) { // Only want to perform this step when scroll position is at start of list return; } if (LeadingBufferTargetSize == 0) { // No need for extra items return; } var targetEnd = Math.Min(NumberOfItems, LeadingBufferEnd + CacheHalfLength + 1); try { _shouldBlockIntermediateCache = true; for (int i = LeadingBufferEnd; i < targetEnd; i++) { var record = PrefetchView(recycler, state, i); SendToIntermediateCache(recycler, record); } } finally { _shouldBlockIntermediateCache = false; } } }
/// <summary> /// Retrieve view for <paramref name="displayPosition"/> from intermediate cache. /// </summary> private View GetViewFromIntermediateCache(RecyclerView.Recycler recycler, int displayPosition) { if (_shouldBlockIntermediateCache) { return(null); } UnoViewHolder result = null; List <UnoViewHolder> views; var type = _owner.CurrentAdapter.GetItemViewType(displayPosition); if (!_intermediateCache.TryGetValue(type, out views)) { return(null); } //Remove views that are animating out views.RemoveAll(RemoveUnrecyclable); foreach (var holder in views) { //Look for an exact match if (holder.LayoutPosition == displayPosition) { result = holder; views.Remove(result); _owner.CurrentAdapter.RegisterPhaseBinding(result); //Restart phase binding, since this won't be rebound by adapter break; } } if (result == null && views.Count > 0) { // Get any match of correct type except views that could be reused without rebinding for (int i = views.Count - 1; i >= 0; i--) { var view = views[i]; if (!IsInTargetRange(view.LayoutPosition)) { result = view; views.RemoveAt(i); break; } } } if (result != null) { recycler.BindViewToPosition(result.ItemView, displayPosition); if (this.Log().IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) { this.Log().Debug($"Returning cached view for position={displayPosition} and view type={type}. {views.Count} cached views remaining."); } return(result.ItemView); } return(null); bool RemoveUnrecyclable(UnoViewHolder holderInner) { var isUnrecyclable = !holderInner.IsRecyclable; if (isUnrecyclable) { Layout.RemoveAndRecycleView(holderInner.ItemView, recycler); } return(isUnrecyclable); } }
private void RecycleView(RecyclerView.Recycler recycler, UnoViewHolder holder) { Layout.RemoveDetachedView(holder.ItemView); recycler.RecycleView(holder.ItemView); }
protected override Line CreateLine(GeneratorDirection direction, int extentOffset, int breadthOffset, int availableBreadth, RecyclerView.Recycler recycler, RecyclerView.State state, Uno.UI.IndexPath nextVisibleItem, bool isNewGroup ) { var itemsInLine = ResolveMaximumItemsInLine(availableBreadth); var firstItemInLine = nextVisibleItem; //Find first item in line, since the item we are passed is the last if (direction == GeneratorDirection.Backward) { // We are recreating the last line of the group - it may be truncated (if the total items are not an even multiple // of the items-per-line). if (isNewGroup) { itemsInLine = XamlParent.GetItemsOnLastLine(firstItemInLine.Section, itemsInLine); } for (int i = 0; i < itemsInLine - 1; i++) { firstItemInLine = GetNextUnmaterializedItem(GeneratorDirection.Backward, firstItemInLine).Value; var isCorrectGroup = firstItemInLine.Section == nextVisibleItem.Section; if (!isCorrectGroup) { //TODO: fix bug that makes this happen (#47229) } Debug.Assert(isCorrectGroup, GetAssertMessage("First item should not be from a different group")); } } Uno.UI.IndexPath lastItemInLine = firstItemInLine; Uno.UI.IndexPath?currentItem = firstItemInLine; var availableWidth = ResolveAvailableWidth(availableBreadth); var availableHeight = ResolveAvailableHeight(availableBreadth); int usedBreadth = 0; for (int i = 0; i < itemsInLine; i++) { var view = recycler.GetViewForPosition(GetFlatItemIndex(currentItem.Value), state); if (!(view is SelectorItem)) { throw new InvalidOperationException($"Expected {nameof(SelectorItem)} but received {view?.GetType().ToString() ?? "<null>"}"); } //Add view before we measure it, this ensures that DP inheritances are correctly applied AddView(view, direction); var slotSize = new Windows.Foundation.Size(availableWidth, availableHeight).PhysicalToLogicalPixels(); var measuredSize = _layouter.MeasureChild(view, slotSize); var physicalMeasuredSize = measuredSize.LogicalToPhysicalPixels(); var measuredWidth = (int)physicalMeasuredSize.Width; var measuredHeight = (int)physicalMeasuredSize.Height; if (_implicitItemWidth == null) { //Set these values to dimensions of first materialised item _implicitItemWidth = measuredWidth; _implicitItemHeight = measuredHeight; // When an item dimension is not fixed, we need to arrange based on the measured size, // otherwise the arrange will be passed a dimension that is too large and the first // few items will not be visible if (double.IsNaN(ItemWidth)) { slotSize.Width = ViewHelper.PhysicalToLogicalPixels(_implicitItemWidth.Value); } if (double.IsNaN(ItemHeight)) { slotSize.Height = ViewHelper.PhysicalToLogicalPixels(_implicitItemHeight.Value); } availableWidth = ResolveAvailableWidth(availableBreadth); availableHeight = ResolveAvailableHeight(availableBreadth); itemsInLine = ResolveMaximumItemsInLine(availableBreadth); } LayoutChild(view, GeneratorDirection.Forward, //We always lay out view 'top down' so that it is aligned correctly if its height is less than the line height direction == GeneratorDirection.Forward ? extentOffset : extentOffset - ResolveItemExtent().Value, breadthOffset + usedBreadth, slotSize ); usedBreadth += ResolveItemBreadth().Value; lastItemInLine = currentItem.Value; currentItem = GetNextUnmaterializedItem(GeneratorDirection.Forward, currentItem); if (currentItem == null || currentItem.Value.Section != firstItemInLine.Section) { itemsInLine = i + 1; break; } } return(new Line { NumberOfViews = itemsInLine, Extent = ResolveItemExtent().Value, Breadth = usedBreadth, FirstItem = firstItemInLine, LastItem = lastItemInLine }); }
public override void OnLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { ComputeSpanCount(Width); base.OnLayoutChildren(recycler, state); }
private void recycleAnchoredViewsFromStart(RecyclerView.Recycler recycler) { recycleViewsFromStart(recycler, false); }
private void recycleAnchorsFromStart(RecyclerView.Recycler recycler) { recycleViewsFromStart(recycler, true); }
private void removeAndRecycleAnchor(int position, RecyclerView.Recycler recycler) { RemoveAndRecycleView(anchors[position], recycler); anchors[position] = null; }
//public void Logi(string msg) //{ // Log.Info(TAG, msg); //} //public void Loge(string err) //{ // Log.Error(TAG, err); //} //--- 处理布局 ---------------------------------------------------------------------------------- /** * 布局子View * * @param recycler Recycler * @param state State */ public override void OnLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { Log.Info(TAG, "Item onLayoutChildren"); Log.Info(TAG, "Item onLayoutChildren isPreLayout = " + state.IsPreLayout); Log.Info(TAG, "Item onLayoutChildren isMeasuring = " + state.IsMeasuring); Log.Info(TAG, "Item onLayoutChildren state = " + state); // 如果是 preLayout 则不重新布局 if (state.IsPreLayout || !state.DidStructureChange()) { return; } if (ItemCount == 0) { RemoveAndRecycleAllViews(recycler); // 页面变化回调 SetPageCount(0); SetPageIndex(0, false); return; } else { SetPageCount(GetTotalPageCount()); SetPageIndex(GetPageIndexByOffset(), false); } // 计算页面数量 int mPageCount = ItemCount / mOnePageSize; if (ItemCount % mOnePageSize != 0) { mPageCount++; } // 计算可以滚动的最大数值,并对滚动距离进行修正 if (CanScrollHorizontally()) { mMaxScrollX = (mPageCount - 1) * GetUsableWidth(); mMaxScrollY = 0; if (mOffsetX > mMaxScrollX) { mOffsetX = mMaxScrollX; } } else { mMaxScrollX = 0; mMaxScrollY = (mPageCount - 1) * GetUsableHeight(); if (mOffsetY > mMaxScrollY) { mOffsetY = mMaxScrollY; } } // 接口回调 // setPageCount(mPageCount); // setPageIndex(mCurrentPageIndex, false); Logi("count = " + ItemCount); if (mItemWidth <= 0) { mItemWidth = GetUsableWidth() / mColumns; } if (mItemHeight <= 0) { mItemHeight = GetUsableHeight() / mRows; } mWidthUsed = GetUsableWidth() - mItemWidth; mHeightUsed = GetUsableHeight() - mItemHeight; // 预存储两页的View显示区域 for (int i = 0; i < mOnePageSize * 2; i++) { GetItemFrameByPosition(i); } if (mOffsetX == 0 && mOffsetY == 0) { // 预存储View for (int i = 0; i < mOnePageSize; i++) { if (i >= ItemCount) { break; // 防止数据过少时导致数组越界异常 } View view = recycler.GetViewForPosition(i); AddView(view); MeasureChildWithMargins(view, mWidthUsed, mHeightUsed); } } // 回收和填充布局 RecycleAndFillItems(recycler, state, true); }