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 item = GetFlatItemIndex(nextVisibleItem); var view = recycler.GetViewForPosition(item, state); if (!(view is SelectorItem)) { throw new InvalidOperationException($"Expected {nameof(SelectorItem)} but received {view?.GetType().ToString() ?? "<null>"}"); } var size = AddViewAtOffset(view, direction, extentOffset, breadthOffset, availableBreadth); var physicalSize = size.LogicalToPhysicalPixels(); var breadth = (int)(ScrollOrientation == Orientation.Vertical ? physicalSize.Width : physicalSize.Height); return(new Line { NumberOfViews = 1, Extent = (int)(ScrollOrientation == Orientation.Vertical ? physicalSize.Height : physicalSize.Width), FirstItem = nextVisibleItem, LastItem = nextVisibleItem, Breadth = breadth }); }
/// <summary> /// Prefetch a view for given <paramref name="displayPosition"/>. /// </summary> private ElementViewRecord PrefetchView(RecyclerView.Recycler recycler, int displayPosition) { if (displayPosition < 0) { throw new ArgumentException($"{nameof(displayPosition)} must be greater than 0."); } var view = GetViewFromIntermediateCache(recycler, displayPosition); if (view == null) { view = recycler.GetViewForPosition(displayPosition); // Add->Detach allows view to be efficiently re-displayed Layout.AddView(view); Layout.TryDetachView(view); } var viewHolder = _owner.GetChildViewHolder(view) as UnoViewHolder; if (!(view is SelectorItem)) { throw new InvalidOperationException($"{nameof(PrefetchView)} received {view?.GetType()} in place of {nameof(SelectorItem)}."); } return(new ElementViewRecord(displayPosition, viewHolder)); }
protected override Line CreateLine(FillDirection direction, int extentOffset, int breadthOffset, int availableBreadth, RecyclerView.Recycler recycler, RecyclerView.State state, IndexPath nextVisibleItem, bool isNewGroup ) { var item = GetFlatItemIndex(nextVisibleItem); var view = recycler.GetViewForPosition(item, state); Debug.Assert(view is SelectorItem, "view is SelectorItem (we should never be given a group header)"); var size = AddViewAtOffset(view, direction, extentOffset, breadthOffset, availableBreadth); var physicalSize = size.LogicalToPhysicalPixels(); var breadth = (int)(ScrollOrientation == Orientation.Vertical ? physicalSize.Width : physicalSize.Height); return(new Line { NumberOfViews = 1, Extent = (int)(ScrollOrientation == Orientation.Vertical ? physicalSize.Height : physicalSize.Width), FirstItem = nextVisibleItem, LastItem = nextVisibleItem, Breadth = breadth }); }
/// <summary> /// Wrapper of <see cref="RecyclerView.Recycler.GetViewForPosition(int)"/> that duplicates bounds checking in managed code, for easier error handling. /// </summary> public static View GetViewForPosition(this RecyclerView.Recycler recycler, int position, RecyclerView.State state) { if (position < 0 || position >= state.ItemCount) { throw new IndexOutOfRangeException($"Invalid item position ({position}). Item count:{state.ItemCount}"); } return(recycler.GetViewForPosition(position)); }
public View Next(RecyclerView.Recycler recycler) { if (ScrapList != null) { //todo: check in the scrapList when laying out for predictive animations } var view = recycler.GetViewForPosition(CurrentAnchorPosition); CurrentAnchorPosition += ItemDirection; return(view); }
private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec, int heightSpec, int[] measuredDimension) { View view = recycler.GetViewForPosition(position); if (view != null) { RecyclerView.LayoutParams p = (RecyclerView.LayoutParams)view.LayoutParameters; int childWidthSpec = ViewGroup.GetChildMeasureSpec(widthSpec, PaddingLeft + PaddingRight, p.Width); int childHeightSpec = ViewGroup.GetChildMeasureSpec(heightSpec, PaddingTop + PaddingBottom, p.Height); view.Measure(childWidthSpec, childHeightSpec); measuredDimension[0] = view.MeasuredWidth + p.LeftMargin + p.RightMargin; measuredDimension[1] = view.MeasuredHeight + p.BottomMargin + p.TopMargin; recycler.RecycleView(view); } }
/** * 添加或者移除条目 * * @param recycler RecyclerView * @param displayRect 显示区域 * @param i 条目下标 */ private void AddOrRemove(RecyclerView.Recycler recycler, Rect displayRect, int i) { View child = recycler.GetViewForPosition(i); Rect rect = GetItemFrameByPosition(i); if (!Rect.Intersects(displayRect, rect)) { RemoveAndRecycleView(child, recycler); // 回收入暂存区 } else { AddView(child); MeasureChildWithMargins(child, mWidthUsed, mHeightUsed); RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)child.LayoutParameters;// getLayoutParams(); LayoutDecorated(child, rect.Left - mOffsetX + lp.LeftMargin + PaddingLeft, rect.Top - mOffsetY + lp.TopMargin + PaddingTop, rect.Right - mOffsetX - lp.RightMargin + PaddingLeft, rect.Bottom - mOffsetY - lp.BottomMargin + PaddingTop); } }
protected override Line CreateLine(GeneratorDirection 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 == 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")); } } 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); 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 }); }
private void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state) { var view = layoutState.Next(recycler); if (view == null || !(view.LayoutParameters is RecyclerView.LayoutParams layoutParams)) { layoutChunkResult.IsFinished = true; return; } if (!(view.Tag is Anchor anchor)) { return; } //todo: check for scrap list if we do predictive animations if (layoutState.LayoutDirection == TowardsTheEnd) { addAnchor(view); } else { addAnchor(view, 0); } MeasureChildWithMargins(view, 0, anchor.Height); layoutChunkResult.Consumed = anchor.Height; var anchorLeft = PaddingLeft; var anchorRight = PaddingLeft; int anchorTop; int anchorBottom; if (layoutState.LayoutDirection == TowardsTheStart) { anchorBottom = layoutState.Offset; anchorTop = layoutState.Offset - layoutChunkResult.Consumed; } else { anchorTop = layoutState.Offset; anchorBottom = layoutState.Offset + layoutChunkResult.Consumed; } LayoutDecoratedWithMargins(view, anchorLeft, anchorTop, anchorRight, anchorBottom); if (layoutParams.IsItemRemoved || layoutParams.IsItemChanged) { layoutChunkResult.IgnoreConsumed = true; } foreach (var anchorData in anchor.AnchoredData) { if (anchoredViewsPositions.Get(anchorData.AdapterPosition) != null) { continue; } var anchoredView = recycler.GetViewForPosition(anchorData.AdapterPosition); var anchoredViewLeft = anchorLeft + anchorData.LeftOffset; var anchoredViewTop = anchorTop + anchorData.TopOffset; if (layoutState.LayoutDirection == TowardsTheEnd) { addAnchoredView(anchoredView, anchorData.AdapterPosition); } else { addAnchoredView(anchoredView, anchorData.AdapterPosition, 0); } MeasureChildWithMargins(anchoredView, anchorData.Width, anchorData.Height); LayoutDecoratedWithMargins(anchoredView, anchoredViewLeft, anchoredViewTop, anchoredViewLeft + anchorData.Width, anchoredViewTop + anchorData.Height); } }
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 }); }
//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); }