2 * Copyright(c) 2019 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 using System.ComponentModel;
20 namespace Tizen.NUI.Components
23 /// Layout collection of views horizontally/vertically.
25 /// <since_tizen> 6 </since_tizen>
26 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
27 [EditorBrowsable(EditorBrowsableState.Never)]
28 public class LinearLayoutManager : FlexibleView.LayoutManager
31 /// Constant value: 0.
33 /// <since_tizen> 6 </since_tizen>
34 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
35 [EditorBrowsable(EditorBrowsableState.Never)]
36 public static readonly int HORIZONTAL = OrientationHelper.HORIZONTAL;
38 /// Constant value: 1.
40 /// <since_tizen> 6 </since_tizen>
41 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
42 [EditorBrowsable(EditorBrowsableState.Never)]
43 public static readonly int VERTICAL = OrientationHelper.VERTICAL;
45 /// Constant value: -1.
47 /// <since_tizen> 6 </since_tizen>
48 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
49 [EditorBrowsable(EditorBrowsableState.Never)]
50 public static readonly int NO_POSITION = FlexibleView.NO_POSITION;
52 /// Constant value: -2^31.
54 /// <since_tizen> 6 </since_tizen>
55 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
56 [EditorBrowsable(EditorBrowsableState.Never)]
57 public static readonly int INVALID_OFFSET = -2147483648;
59 private static readonly float MAX_SCROLL_FACTOR = 1 / 3f;
62 /// Current orientation.
64 /// <since_tizen> 6 </since_tizen>
65 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
66 [EditorBrowsable(EditorBrowsableState.Never)]
67 protected int mOrientation;
69 internal OrientationHelper mOrientationHelper;
71 private LayoutState mLayoutState;
72 private AnchorInfo mAnchorInfo = new AnchorInfo();
74 // Stashed to avoid allocation, currently only used in #fill()
75 private LayoutChunkResult mLayoutChunkResult = new LayoutChunkResult();
77 private bool mShouldReverseLayout = false;
79 // When LayoutManager needs to scroll to a position, it sets this variable and requests a
80 // layout which will check this variable and re-layout accordingly.
81 private int mPendingScrollPosition = NO_POSITION;
83 // Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is
85 private int mPendingScrollPositionOffset = INVALID_OFFSET;
88 /// Creates a LinearLayoutManager with orientation.
90 /// <param name="orientation">Layout orientation.Should be HORIZONTAL or VERTICAL</param>
91 /// <since_tizen> 6 </since_tizen>
92 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
93 [EditorBrowsable(EditorBrowsableState.Never)]
94 public LinearLayoutManager(int orientation)
96 mOrientation = orientation;
97 mOrientationHelper = OrientationHelper.CreateOrientationHelper(this, mOrientation);
99 mLayoutState = new LayoutState();
100 mLayoutState.Offset = mOrientationHelper.GetStartAfterPadding();
104 /// Retrieves the first visible item position.
106 /// <since_tizen> 6 </since_tizen>
107 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
108 [EditorBrowsable(EditorBrowsableState.Never)]
109 public int FirstVisibleItemPosition
113 FlexibleView.ViewHolder child = FindFirstVisibleItemView();
114 return child == null ? NO_POSITION : child.LayoutPosition;
119 /// Retrieves the first complete visible item position.
121 /// <since_tizen> 6 </since_tizen>
122 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
123 [EditorBrowsable(EditorBrowsableState.Never)]
124 public int FirstCompleteVisibleItemPosition
128 FlexibleView.ViewHolder child = FindFirstCompleteVisibleItemView();
129 return child == null ? NO_POSITION : child.LayoutPosition;
134 /// Retrieves the last visible item position.
136 /// <since_tizen> 6 </since_tizen>
137 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
138 [EditorBrowsable(EditorBrowsableState.Never)]
139 public int LastVisibleItemPosition
143 FlexibleView.ViewHolder child = FindLastVisibleItemView();
144 return child == null ? NO_POSITION : child.LayoutPosition;
149 /// Retrieves the last complete visible item position.
151 /// <since_tizen> 6 </since_tizen>
152 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
153 [EditorBrowsable(EditorBrowsableState.Never)]
154 public int LastCompleteVisibleItemPosition
158 FlexibleView.ViewHolder child = FindLastCompleteVisibleItemView();
159 return child == null ? NO_POSITION : child.LayoutPosition;
164 /// Query if horizontal scrolling is currently supported. The default implementation returns false.
166 /// <since_tizen> 6 </since_tizen>
167 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
168 [EditorBrowsable(EditorBrowsableState.Never)]
169 public override bool CanScrollHorizontally()
171 return mOrientation == HORIZONTAL;
175 /// Query if vertical scrolling is currently supported. The default implementation returns false.
177 /// <since_tizen> 6 </since_tizen>
178 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
179 [EditorBrowsable(EditorBrowsableState.Never)]
180 public override bool CanScrollVertically()
182 return mOrientation == VERTICAL;
186 /// Lay out all relevant child views from the given adapter.
188 /// <param name="recycler">Recycler to use for fetching potentially cached views for a position</param>
189 /// <since_tizen> 6 </since_tizen>
190 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
191 [EditorBrowsable(EditorBrowsableState.Never)]
192 public override void OnLayoutChildren(FlexibleView.Recycler recycler)
194 mLayoutState.Recycle = false;
195 if (!mAnchorInfo.Valid || mPendingScrollPosition != NO_POSITION)
198 mAnchorInfo.LayoutFromEnd = mShouldReverseLayout;
199 // calculate anchor position and coordinate
200 UpdateAnchorInfoForLayout(recycler, mAnchorInfo);
201 mAnchorInfo.Valid = true;
204 int firstLayoutDirection;
205 if (mAnchorInfo.LayoutFromEnd)
207 firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
208 : LayoutState.ITEM_DIRECTION_HEAD;
212 firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
213 : LayoutState.ITEM_DIRECTION_TAIL;
215 EnsureAnchorReady(recycler, mAnchorInfo, firstLayoutDirection);
216 ScrapAttachedViews(recycler);
218 if (mAnchorInfo.LayoutFromEnd == true)
220 UpdateLayoutStateToFillStart(mAnchorInfo.Position, mAnchorInfo.Coordinate);
221 Fill(recycler, mLayoutState, false, true);
222 Cache(recycler, mLayoutState, true);
224 UpdateLayoutStateToFillEnd(mAnchorInfo.Position, mAnchorInfo.Coordinate);
225 mLayoutState.CurrentPosition += mLayoutState.ItemDirection;
226 Fill(recycler, mLayoutState, false, true);
227 Cache(recycler, mLayoutState, true);
231 UpdateLayoutStateToFillEnd(mAnchorInfo.Position, mAnchorInfo.Coordinate);
232 Fill(recycler, mLayoutState, false, true);
233 Cache(recycler, mLayoutState, true);
235 UpdateLayoutStateToFillStart(mAnchorInfo.Position, mAnchorInfo.Coordinate);
236 mLayoutState.CurrentPosition += mLayoutState.ItemDirection;
237 Fill(recycler, mLayoutState, false, true);
238 Cache(recycler, mLayoutState, true);
245 /// Scroll horizontally by dy pixels in screen coordinates.
247 /// <param name="dx">distance to scroll in pixels. Y increases as scroll position approaches the top.</param>
248 /// <param name="recycler">Recycler to use for fetching potentially cached views for a position</param>
249 /// <param name="immediate">Specify if the scroll need animation</param>
250 /// <since_tizen> 6 </since_tizen>
251 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
252 [EditorBrowsable(EditorBrowsableState.Never)]
253 public override float ScrollHorizontallyBy(float dx, FlexibleView.Recycler recycler, bool immediate)
255 if (mOrientation == VERTICAL)
259 return ScrollBy(dx, recycler, immediate);
263 /// Scroll vertically by dy pixels in screen coordinates.
265 /// <param name="dy">distance to scroll in pixels. Y increases as scroll position approaches the top.</param>
266 /// <param name="recycler">Recycler to use for fetching potentially cached views for a position</param>
267 /// <param name="immediate">Specify if the scroll need animation</param>
268 /// <since_tizen> 6 </since_tizen>
269 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
270 [EditorBrowsable(EditorBrowsableState.Never)]
271 public override float ScrollVerticallyBy(float dy, FlexibleView.Recycler recycler, bool immediate)
273 if (mOrientation == HORIZONTAL)
277 return ScrollBy(dy, recycler, immediate);
281 /// Compute the offset of the scrollbar's thumb within the range.
283 /// <since_tizen> 6 </since_tizen>
284 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
285 [EditorBrowsable(EditorBrowsableState.Never)]
286 public override float ComputeScrollOffset()
288 FlexibleView.ViewHolder startChild = FindFirstVisibleItemView();
289 FlexibleView.ViewHolder endChild = FindLastVisibleItemView();
290 if (ChildCount == 0 || startChild == null || endChild == null)
294 int minPosition = Math.Min(startChild.LayoutPosition, endChild.LayoutPosition);
295 int maxPosition = Math.Max(startChild.LayoutPosition, endChild.LayoutPosition);
296 int itemsBefore = mShouldReverseLayout
297 ? Math.Max(0, ItemCount - maxPosition - 1)
298 : Math.Max(0, minPosition);
300 float laidOutArea = Math.Abs(mOrientationHelper.GetViewHolderEnd(endChild)
301 - mOrientationHelper.GetViewHolderStart(startChild));
302 int itemRange = Math.Abs(startChild.LayoutPosition - endChild.LayoutPosition) + 1;
303 float avgSizePerRow = laidOutArea / itemRange;
305 return (float)Math.Round(itemsBefore * avgSizePerRow + (mOrientationHelper.GetStartAfterPadding()
306 - mOrientationHelper.GetViewHolderStart(startChild)));
310 /// Compute the extent of the scrollbar's thumb within the range.
312 /// <since_tizen> 6 </since_tizen>
313 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
314 [EditorBrowsable(EditorBrowsableState.Never)]
315 public override float ComputeScrollExtent()
317 FlexibleView.ViewHolder startChild = FindFirstVisibleItemView();
318 FlexibleView.ViewHolder endChild = FindLastVisibleItemView();
319 if (ChildCount == 0 || startChild == null || endChild == null)
323 float extend = mOrientationHelper.GetViewHolderEnd(endChild)
324 - mOrientationHelper.GetViewHolderStart(startChild);
325 return Math.Min(mOrientationHelper.GetTotalSpace(), extend);
329 /// Compute the range that the scrollbar represents.
331 /// <since_tizen> 6 </since_tizen>
332 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
333 [EditorBrowsable(EditorBrowsableState.Never)]
334 public override float ComputeScrollRange()
336 FlexibleView.ViewHolder startChild = FindFirstVisibleItemView();
337 FlexibleView.ViewHolder endChild = FindLastVisibleItemView();
338 if (ChildCount == 0 || startChild == null || endChild == null)
342 float laidOutArea = mOrientationHelper.GetViewHolderEnd(endChild)
343 - mOrientationHelper.GetViewHolderStart(startChild);
344 int laidOutRange = Math.Abs(startChild.LayoutPosition - endChild.LayoutPosition) + 1;
345 // estimate a size for full list.
346 return laidOutArea / laidOutRange * ItemCount;
350 /// Scroll the FlexibleView to make the position visible.
352 /// <param name="position">Scroll to this adapter position</param>
353 /// <since_tizen> 6 </since_tizen>
354 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
355 [EditorBrowsable(EditorBrowsableState.Never)]
356 public override void ScrollToPosition(int position)
358 mPendingScrollPosition = position;
359 mPendingScrollPositionOffset = INVALID_OFFSET;
365 /// Scroll to the specified adapter position with the given offset from resolved layout start.
367 /// <param name="position">Scroll to this adapter position</param>
368 /// <param name="offset">The distance (in pixels) between the start edge of the item view and start edge of the FlexibleView.</param>
369 /// <since_tizen> 6 </since_tizen>
370 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
371 [EditorBrowsable(EditorBrowsableState.Never)]
372 public override void ScrollToPositionWithOffset(int position, int offset)
374 mPendingScrollPosition = position;
375 mPendingScrollPositionOffset = offset;
381 /// Called after a full layout calculation is finished.
383 /// <since_tizen> 6 </since_tizen>
384 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
385 [EditorBrowsable(EditorBrowsableState.Never)]
386 public override void OnLayoutCompleted()
388 if (mPendingScrollPosition != NO_POSITION)
390 ChangeFocus(mPendingScrollPosition);
392 mPendingScrollPosition = NO_POSITION;
393 mPendingScrollPositionOffset = INVALID_OFFSET;
398 internal virtual void EnsureAnchorReady(FlexibleView.Recycler recycler, AnchorInfo anchorInfo, int itemDirection)
405 /// Retrieves a position that neighbor to current position by direction.
407 /// <param name="position">The anchor adapter position</param>
408 /// <param name="direction">The direction.</param>
409 /// <since_tizen> 6 </since_tizen>
410 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
411 [EditorBrowsable(EditorBrowsableState.Never)]
412 protected override int GetNextPosition(int position, FlexibleView.LayoutManager.Direction direction)
414 if (mOrientation == HORIZONTAL)
418 case FlexibleView.LayoutManager.Direction.Left:
424 case FlexibleView.LayoutManager.Direction.Right:
425 if (position < ItemCount - 1)
436 case FlexibleView.LayoutManager.Direction.Up:
442 case FlexibleView.LayoutManager.Direction.Down:
443 if (position < ItemCount - 1)
455 /// Retrieves the first visible item view.
457 /// <since_tizen> 6 </since_tizen>
458 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
459 [EditorBrowsable(EditorBrowsableState.Never)]
460 protected override FlexibleView.ViewHolder FindFirstVisibleItemView()
462 int childCount = ChildCount;
463 if (mShouldReverseLayout == false)
465 for (int i = 0; i < childCount; i++)
467 FlexibleView.ViewHolder child = GetChildAt(i);
468 int end = (int)mOrientationHelper.GetViewHolderEnd(child);
469 if (end >= 0 && end < (int)mOrientationHelper.GetEnd())
477 for (int i = childCount - 1; i >= 0; i--)
479 FlexibleView.ViewHolder child = GetChildAt(i);
480 int end = (int)mOrientationHelper.GetViewHolderEnd(child);
481 if (end >= 0 && end < (int)mOrientationHelper.GetEnd())
491 /// Retrieves the last visible item view.
493 /// <since_tizen> 6 </since_tizen>
494 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
495 [EditorBrowsable(EditorBrowsableState.Never)]
496 protected override FlexibleView.ViewHolder FindLastVisibleItemView()
498 int childCount = ChildCount;
499 if (mShouldReverseLayout == false)
501 for (int i = childCount - 1; i >= 0; i--)
503 FlexibleView.ViewHolder child = GetChildAt(i);
504 int start = (int)mOrientationHelper.GetViewHolderStart(child);
505 if (start > 0 && start < (int)mOrientationHelper.GetEnd())
513 for (int i = 0; i < childCount; i++)
515 FlexibleView.ViewHolder child = GetChildAt(i);
516 int start = (int)mOrientationHelper.GetViewHolderStart(child);
517 if (start > 0 && start < (int)mOrientationHelper.GetEnd())
526 internal virtual void LayoutChunk(FlexibleView.Recycler recycler,
527 LayoutState layoutState, LayoutChunkResult result)
529 FlexibleView.ViewHolder holder = layoutState.Next(recycler);
532 // if we are laying out views in scrap, this may return null which means there is
533 // no more items to layout.
534 result.Finished = true;
538 if (mShouldReverseLayout == (layoutState.LayoutDirection == LayoutState.LAYOUT_START))
543 result.Consumed = mOrientationHelper.GetViewHolderMeasurement(holder);
545 float left, top, width, height;
546 if (mOrientation == VERTICAL)
548 width = Width - PaddingLeft - PaddingRight;
549 height = result.Consumed;
551 if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
553 top = layoutState.Offset;
557 top = layoutState.Offset - height;
559 LayoutChild(holder, left, top, width, height);
563 width = result.Consumed;
564 height = Height - PaddingTop - PaddingBottom;
566 if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
568 left = layoutState.Offset;
572 left = layoutState.Offset - width;
574 LayoutChild(holder, left, top, width, height);
577 result.Focusable = true;
580 internal override FlexibleView.ViewHolder OnFocusSearchFailed(FlexibleView.ViewHolder focused, FlexibleView.LayoutManager.Direction direction, FlexibleView.Recycler recycler)
586 int layoutDir = ConvertFocusDirectionToLayoutDirection(direction);
587 if (layoutDir == LayoutState.INVALID_LAYOUT)
591 int maxScroll = (int)(MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace());
592 UpdateLayoutState(layoutDir, maxScroll, false);
593 mLayoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
594 mLayoutState.Recycle = false;
595 Fill(recycler, mLayoutState, true, true);
597 FlexibleView.ViewHolder nextFocus;
598 if (layoutDir == LayoutState.LAYOUT_START)
600 nextFocus = GetChildAt(0);
604 nextFocus = GetChildAt(ChildCount - 1);
610 private void UpdateAnchorInfoForLayout(FlexibleView.Recycler recycler, AnchorInfo anchorInfo)
612 if (UpdateAnchorFromPendingData(anchorInfo))
617 if (UpdateAnchorFromChildren(recycler, anchorInfo))
622 anchorInfo.Position = FocusPosition != NO_POSITION ? FocusPosition : 0;
623 anchorInfo.Coordinate = anchorInfo.LayoutFromEnd ? mOrientationHelper.GetEndAfterPadding() : mOrientationHelper.GetStartAfterPadding();
627 // If there is a pending scroll position or saved states, updates the anchor info from that
628 // data and returns true
629 private bool UpdateAnchorFromPendingData(AnchorInfo anchorInfo)
631 if (mPendingScrollPosition == NO_POSITION)
635 // validate scroll position
636 if (mPendingScrollPosition < 0 || mPendingScrollPosition >= ItemCount)
638 mPendingScrollPosition = NO_POSITION;
639 mPendingScrollPositionOffset = INVALID_OFFSET;
643 anchorInfo.Position = mPendingScrollPosition;
645 if (mPendingScrollPositionOffset == INVALID_OFFSET)
647 anchorInfo.Coordinate = anchorInfo.LayoutFromEnd ? mOrientationHelper.GetEndAfterPadding() : mOrientationHelper.GetStartAfterPadding();
651 if (mShouldReverseLayout)
653 anchorInfo.Coordinate = mOrientationHelper.GetEndAfterPadding()
654 - mPendingScrollPositionOffset;
658 anchorInfo.Coordinate = mOrientationHelper.GetStartAfterPadding()
659 + mPendingScrollPositionOffset;
666 // Finds an anchor child from existing Views. Most of the time, this is the view closest to
667 // start or end that has a valid position (e.g. not removed).
668 // If a child has focus, it is given priority.
669 private bool UpdateAnchorFromChildren(FlexibleView.Recycler recycler, AnchorInfo anchorInfo)
676 FlexibleView.ViewHolder anchorChild = FindFirstVisibleItemView();
677 if (anchorChild == null)
679 Log.Error("flexibleview", $"exception occurs when updating anchor information!");
680 anchorChild = GetChildAt(0);
682 anchorInfo.Position = anchorChild.LayoutPosition;
683 anchorInfo.Coordinate = mOrientationHelper.GetViewHolderStart(anchorChild);
688 // Converts a focusDirection to orientation.
690 // @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
691 // {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
692 // {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
693 // or 0 for not applicable
694 // @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction
695 // is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise.
696 private int ConvertFocusDirectionToLayoutDirection(FlexibleView.LayoutManager.Direction focusDirection)
698 switch (focusDirection)
700 case FlexibleView.LayoutManager.Direction.Up:
701 return mOrientation == VERTICAL ? LayoutState.LAYOUT_START
702 : LayoutState.INVALID_LAYOUT;
703 case FlexibleView.LayoutManager.Direction.Down:
704 return mOrientation == VERTICAL ? LayoutState.LAYOUT_END
705 : LayoutState.INVALID_LAYOUT;
706 case FlexibleView.LayoutManager.Direction.Left:
707 return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_START
708 : LayoutState.INVALID_LAYOUT;
709 case FlexibleView.LayoutManager.Direction.Right:
710 return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_END
711 : LayoutState.INVALID_LAYOUT;
713 return LayoutState.INVALID_LAYOUT;
719 private float Fill(FlexibleView.Recycler recycler, LayoutState layoutState, bool stopOnFocusable, bool immediate)
721 float start = layoutState.Available;
723 if (layoutState.ScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN)
725 // TODO ugly bug fix. should not happen
726 if (layoutState.Available < 0)
728 layoutState.ScrollingOffset += layoutState.Available;
730 if (immediate == true)
732 RecycleByLayoutState(recycler, layoutState, true);
735 float remainingSpace = layoutState.Available + layoutState.Extra;
736 LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
737 while ((remainingSpace > 0) && layoutState.HasMore(ItemCount))
739 layoutChunkResult.ResetInternal();
740 LayoutChunk(recycler, layoutState, layoutChunkResult);
741 if (layoutChunkResult.Finished)
745 layoutState.Offset += layoutChunkResult.Consumed * layoutState.LayoutDirection;
747 // Consume the available space if:
748 // layoutChunk did not request to be ignored
749 // OR we are laying out scrap children
750 // OR we are not doing pre-layout
751 if (!layoutChunkResult.IgnoreConsumed)
753 layoutState.Available -= layoutChunkResult.Consumed;
754 // we keep a separate remaining space because mAvailable is important for recycling
755 remainingSpace -= layoutChunkResult.Consumed;
758 if (layoutState.ScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN)
760 layoutState.ScrollingOffset += layoutChunkResult.Consumed;
761 if (layoutState.Available < 0)
763 layoutState.ScrollingOffset += layoutState.Available;
765 if (immediate == true)
767 RecycleByLayoutState(recycler, layoutState, true);
770 if (stopOnFocusable && layoutChunkResult.Focusable)
775 if (immediate == false)
777 RecycleByLayoutState(recycler, layoutState, false);
780 return start - layoutState.Available;
783 private void Cache(FlexibleView.Recycler recycler, LayoutState layoutState, bool immediate, float scrolled = 0)
785 if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
787 // get the first child in the direction we are going
788 FlexibleView.ViewHolder child = GetChildClosestToEnd();
791 if (child.ItemView.Focusable == false || mOrientationHelper.GetViewHolderEnd(child) + scrolled < mOrientationHelper.GetEnd())
793 layoutState.Available = MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace();
794 layoutState.Extra = 0;
795 layoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
796 layoutState.Recycle = false;
797 Fill(recycler, layoutState, true, immediate);
803 FlexibleView.ViewHolder child = GetChildClosestToStart();
806 if (child.ItemView.Focusable == false || mOrientationHelper.GetViewHolderStart(child) + scrolled > 0)
808 layoutState.Available = MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace();
809 layoutState.Extra = 0;
810 layoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
811 layoutState.Recycle = false;
812 Fill(recycler, layoutState, true, immediate);
818 private void RecycleByLayoutState(FlexibleView.Recycler recycler, LayoutState layoutState, bool immediate)
820 if (!layoutState.Recycle)
824 if (layoutState.LayoutDirection == LayoutState.LAYOUT_START)
826 RecycleViewsFromEnd(recycler, layoutState.ScrollingOffset, immediate);
830 RecycleViewsFromStart(recycler, layoutState.ScrollingOffset, immediate);
834 private void RecycleViewsFromStart(FlexibleView.Recycler recycler, float dt, bool immediate)
840 // ignore padding, ViewGroup may not clip children.
842 int childCount = ChildCount;
843 if (mShouldReverseLayout)
845 for (int i = childCount - 1; i >= 0; i--)
847 FlexibleView.ViewHolder child = GetChildAt(i);
848 if (mOrientationHelper.GetViewHolderEnd(child) > limit)
851 RecycleChildren(recycler, childCount - 1, i, immediate);
858 for (int i = 0; i < childCount; i++)
860 FlexibleView.ViewHolder child = GetChildAt(i);
861 if (mOrientationHelper.GetViewHolderEnd(child) > limit)
864 RecycleChildren(recycler, 0, i, immediate);
871 private void RecycleViewsFromEnd(FlexibleView.Recycler recycler, float dt, bool immediate)
877 int childCount = ChildCount;
878 float limit = mOrientationHelper.GetEnd() - dt;
879 if (mShouldReverseLayout)
881 for (int i = 0; i < childCount; i++)
883 FlexibleView.ViewHolder child = GetChildAt(i);
884 if (mOrientationHelper.GetViewHolderStart(child) < limit)
887 RecycleChildren(recycler, 0, i, immediate);
894 for (int i = childCount - 1; i >= 0; i--)
896 FlexibleView.ViewHolder child = GetChildAt(i);
897 if (mOrientationHelper.GetViewHolderStart(child) < limit)
900 RecycleChildren(recycler, childCount - 1, i, immediate);
907 private float ScrollBy(float dy, FlexibleView.Recycler recycler, bool immediate)
909 if (ChildCount == 0 || dy == 0)
913 mLayoutState.Recycle = true;
914 int layoutDirection = dy < 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
915 float absDy = Math.Abs(dy);
917 UpdateLayoutState(layoutDirection, absDy, true);
919 float consumed = mLayoutState.ScrollingOffset
920 + Fill(recycler, mLayoutState, false, immediate);
927 float scrolled = absDy > consumed ? -layoutDirection * consumed : dy;
928 Cache(recycler, mLayoutState, immediate, scrolled);
930 mOrientationHelper.OffsetChildren(scrolled, immediate);
935 private void UpdateLayoutState(int layoutDirection, float requiredSpace, bool canUseExistingSpace)
937 mLayoutState.Extra = 0;
938 mLayoutState.LayoutDirection = layoutDirection;
939 float scrollingOffset = 0.0f;
940 if (layoutDirection == LayoutState.LAYOUT_END)
942 mLayoutState.Extra += mOrientationHelper.GetEndPadding();
943 // get the first child in the direction we are going
944 FlexibleView.ViewHolder child = GetChildClosestToEnd();
947 // the direction in which we are traversing children
948 mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
949 : LayoutState.ITEM_DIRECTION_TAIL;
950 mLayoutState.CurrentPosition = child.LayoutPosition + mLayoutState.ItemDirection;
951 mLayoutState.Offset = mOrientationHelper.GetViewHolderEnd(child);
952 // calculate how much we can scroll without adding new children (independent of layout)
953 scrollingOffset = mOrientationHelper.GetViewHolderEnd(child)
954 - mOrientationHelper.GetEndAfterPadding();
960 mLayoutState.Extra += mOrientationHelper.GetStartAfterPadding();
961 FlexibleView.ViewHolder child = GetChildClosestToStart();
964 mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
965 : LayoutState.ITEM_DIRECTION_HEAD;
966 mLayoutState.CurrentPosition = child.LayoutPosition + mLayoutState.ItemDirection;
967 mLayoutState.Offset = mOrientationHelper.GetViewHolderStart(child);
968 scrollingOffset = -mOrientationHelper.GetViewHolderStart(child)
969 + mOrientationHelper.GetStartAfterPadding();
972 mLayoutState.Available = requiredSpace;
973 if (canUseExistingSpace)
975 mLayoutState.Available -= scrollingOffset;
977 mLayoutState.ScrollingOffset = scrollingOffset;
980 // Convenience method to find the child closes to start. Caller should check it has enough
983 // @return The child closes to start of the layout from user's perspective.
984 private FlexibleView.ViewHolder GetChildClosestToStart()
986 return GetChildAt(mShouldReverseLayout ? ChildCount - 1 : 0);
989 // Convenience method to find the child closes to end. Caller should check it has enough
992 // @return The child closes to end of the layout from user's perspective.
993 private FlexibleView.ViewHolder GetChildClosestToEnd()
995 return GetChildAt(mShouldReverseLayout ? 0 : ChildCount - 1);
998 private void UpdateLayoutStateToFillEnd(int itemPosition, float offset)
1000 mLayoutState.Available = mOrientationHelper.GetEndAfterPadding() - offset;
1001 mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
1002 LayoutState.ITEM_DIRECTION_TAIL;
1003 mLayoutState.CurrentPosition = itemPosition;
1004 mLayoutState.LayoutDirection = LayoutState.LAYOUT_END;
1005 mLayoutState.Offset = offset;
1006 mLayoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
1007 mLayoutState.Extra = mOrientationHelper.GetEndPadding();
1010 private void UpdateLayoutStateToFillStart(int itemPosition, float offset)
1012 mLayoutState.Available = offset - mOrientationHelper.GetStartAfterPadding();
1013 mLayoutState.CurrentPosition = itemPosition;
1014 mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL :
1015 LayoutState.ITEM_DIRECTION_HEAD;
1016 mLayoutState.LayoutDirection = LayoutState.LAYOUT_START;
1017 mLayoutState.Offset = offset;
1018 mLayoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
1019 mLayoutState.Extra = mOrientationHelper.GetStartAfterPadding();
1022 private FlexibleView.ViewHolder FindFirstCompleteVisibleItemView()
1024 int childCount = ChildCount;
1025 if (mShouldReverseLayout == false)
1027 for (int i = 0; i < childCount; i++)
1029 FlexibleView.ViewHolder child = GetChildAt(i);
1030 int start = (int)mOrientationHelper.GetViewHolderStart(child);
1031 if (start > 0 && start < (int)mOrientationHelper.GetEnd())
1039 for (int i = childCount - 1; i >= 0; i--)
1041 FlexibleView.ViewHolder child = GetChildAt(i);
1042 int start = (int)mOrientationHelper.GetViewHolderStart(child);
1043 if (start > 0 && start < (int)mOrientationHelper.GetEnd())
1052 private FlexibleView.ViewHolder FindLastCompleteVisibleItemView()
1054 int childCount = ChildCount;
1055 if (mShouldReverseLayout == false)
1057 for (int i = childCount - 1; i >= 0; i--)
1059 FlexibleView.ViewHolder child = GetChildAt(i);
1060 if ((int)mOrientationHelper.GetViewHolderEnd(child) < (int)mOrientationHelper.GetEnd())
1068 for (int i = 0; i < childCount; i++)
1070 FlexibleView.ViewHolder child = GetChildAt(i);
1071 if ((int)mOrientationHelper.GetViewHolderEnd(child) < (int)mOrientationHelper.GetEnd())
1080 // Helper class that keeps temporary state while {LayoutManager} is filling out the empty space.
1081 internal class LayoutState
1083 public static readonly int LAYOUT_START = -1;
1085 public static readonly int LAYOUT_END = 1;
1087 public static readonly int INVALID_LAYOUT = -1000;
1089 public static readonly int ITEM_DIRECTION_HEAD = -1;
1091 public static readonly int ITEM_DIRECTION_TAIL = 1;
1093 public static readonly int SCROLLING_OFFSET_NaN = -10000;
1095 // We may not want to recycle children in some cases (e.g. layout)
1096 public bool Recycle = true;
1098 // Pixel offset where layout should start
1099 public float Offset;
1101 // Number of pixels that we should fill, in the layout direction.
1102 public float Available;
1104 // Current position on the adapter to get the next item.
1105 public int CurrentPosition;
1107 // Defines the direction in which the data adapter is traversed.
1108 // Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL}
1109 public int ItemDirection;
1111 // Defines the direction in which the layout is filled.
1112 // Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
1113 public int LayoutDirection;
1115 // Used when LayoutState is constructed in a scrolling state.
1116 // It should be set the amount of scrolling we can make without creating a new view.
1117 // Settings this is required for efficient view recycling.
1118 public float ScrollingOffset;
1120 // Used if you want to pre-layout items that are not yet visible.
1121 // The difference with {@link #mAvailable} is that, when recycling, distance laid out for
1122 // {@link #mExtra} is not considered to avoid recycling visible children.
1123 public float Extra = 0;
1126 // @return true if there are more items in the data adapter
1127 public bool HasMore(int itemCount)
1129 return CurrentPosition >= 0 && CurrentPosition < itemCount;
1132 // Gets the view for the next element that we should layout.
1133 // Also updates current item index to the next item, based on {@link #mItemDirection}
1135 // @return The next element that we should layout.
1136 public FlexibleView.ViewHolder Next(FlexibleView.Recycler recycler)
1138 FlexibleView.ViewHolder itemView = recycler.GetViewForPosition(CurrentPosition);
1139 CurrentPosition += ItemDirection;
1144 internal class LayoutChunkResult
1146 public float Consumed;
1147 public bool Finished;
1148 public bool IgnoreConsumed;
1149 public bool Focusable;
1151 public void ResetInternal()
1155 IgnoreConsumed = false;
1160 internal class AnchorInfo
1162 public int Position;
1163 public float Coordinate;
1164 public bool LayoutFromEnd;
1169 Position = NO_POSITION;
1170 Coordinate = INVALID_OFFSET;
1171 LayoutFromEnd = false;