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="dy">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)
454 internal virtual void LayoutChunk(FlexibleView.Recycler recycler,
455 LayoutState layoutState, LayoutChunkResult result)
457 FlexibleView.ViewHolder holder = layoutState.Next(recycler);
460 // if we are laying out views in scrap, this may return null which means there is
461 // no more items to layout.
462 result.Finished = true;
466 if (mShouldReverseLayout == (layoutState.LayoutDirection == LayoutState.LAYOUT_START))
471 result.Consumed = mOrientationHelper.GetViewHolderMeasurement(holder);
473 float left, top, width, height;
474 if (mOrientation == VERTICAL)
476 width = Width - PaddingLeft - PaddingRight;
477 height = result.Consumed;
479 if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
481 top = layoutState.Offset;
485 top = layoutState.Offset - height;
487 LayoutChild(holder, left, top, width, height);
491 width = result.Consumed;
492 height = Height - PaddingTop - PaddingBottom;
494 if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
496 left = layoutState.Offset;
500 left = layoutState.Offset - width;
502 LayoutChild(holder, left, top, width, height);
505 result.Focusable = true;
508 internal override FlexibleView.ViewHolder OnFocusSearchFailed(FlexibleView.ViewHolder focused, FlexibleView.LayoutManager.Direction direction, FlexibleView.Recycler recycler)
514 int layoutDir = ConvertFocusDirectionToLayoutDirection(direction);
515 if (layoutDir == LayoutState.INVALID_LAYOUT)
519 int maxScroll = (int)(MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace());
520 UpdateLayoutState(layoutDir, maxScroll, false);
521 mLayoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
522 mLayoutState.Recycle = false;
523 Fill(recycler, mLayoutState, true, true);
525 FlexibleView.ViewHolder nextFocus;
526 if (layoutDir == LayoutState.LAYOUT_START)
528 nextFocus = GetChildAt(0);
532 nextFocus = GetChildAt(ChildCount - 1);
538 private void UpdateAnchorInfoForLayout(FlexibleView.Recycler recycler, AnchorInfo anchorInfo)
540 if (UpdateAnchorFromPendingData(anchorInfo))
545 if (UpdateAnchorFromChildren(recycler, anchorInfo))
550 anchorInfo.Position = FocusPosition != NO_POSITION ? FocusPosition : 0;
551 anchorInfo.Coordinate = anchorInfo.LayoutFromEnd ? mOrientationHelper.GetEndAfterPadding() : mOrientationHelper.GetStartAfterPadding();
555 // If there is a pending scroll position or saved states, updates the anchor info from that
556 // data and returns true
557 private bool UpdateAnchorFromPendingData(AnchorInfo anchorInfo)
559 if (mPendingScrollPosition == NO_POSITION)
563 // validate scroll position
564 if (mPendingScrollPosition < 0 || mPendingScrollPosition >= ItemCount)
566 mPendingScrollPosition = NO_POSITION;
567 mPendingScrollPositionOffset = INVALID_OFFSET;
571 anchorInfo.Position = mPendingScrollPosition;
573 if (mPendingScrollPositionOffset == INVALID_OFFSET)
575 anchorInfo.Coordinate = anchorInfo.LayoutFromEnd ? mOrientationHelper.GetEndAfterPadding() : mOrientationHelper.GetStartAfterPadding();
579 if (mShouldReverseLayout)
581 anchorInfo.Coordinate = mOrientationHelper.GetEndAfterPadding()
582 - mPendingScrollPositionOffset;
586 anchorInfo.Coordinate = mOrientationHelper.GetStartAfterPadding()
587 + mPendingScrollPositionOffset;
594 // Finds an anchor child from existing Views. Most of the time, this is the view closest to
595 // start or end that has a valid position (e.g. not removed).
596 // If a child has focus, it is given priority.
597 private bool UpdateAnchorFromChildren(FlexibleView.Recycler recycler, AnchorInfo anchorInfo)
604 FlexibleView.ViewHolder anchorChild = FindFirstCompleteVisibleItemView();
605 if (anchorChild != null)
609 anchorInfo.Position = anchorChild.LayoutPosition;
610 anchorInfo.Coordinate = mOrientationHelper.GetViewHolderStart(anchorChild);
615 // Converts a focusDirection to orientation.
617 // @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
618 // {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
619 // {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
620 // or 0 for not applicable
621 // @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction
622 // is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise.
623 private int ConvertFocusDirectionToLayoutDirection(FlexibleView.LayoutManager.Direction focusDirection)
625 switch (focusDirection)
627 case FlexibleView.LayoutManager.Direction.Up:
628 return mOrientation == VERTICAL ? LayoutState.LAYOUT_START
629 : LayoutState.INVALID_LAYOUT;
630 case FlexibleView.LayoutManager.Direction.Down:
631 return mOrientation == VERTICAL ? LayoutState.LAYOUT_END
632 : LayoutState.INVALID_LAYOUT;
633 case FlexibleView.LayoutManager.Direction.Left:
634 return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_START
635 : LayoutState.INVALID_LAYOUT;
636 case FlexibleView.LayoutManager.Direction.Right:
637 return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_END
638 : LayoutState.INVALID_LAYOUT;
640 return LayoutState.INVALID_LAYOUT;
646 private float Fill(FlexibleView.Recycler recycler, LayoutState layoutState, bool stopOnFocusable, bool immediate)
648 float start = layoutState.Available;
650 if (layoutState.ScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN)
652 // TODO ugly bug fix. should not happen
653 if (layoutState.Available < 0)
655 layoutState.ScrollingOffset += layoutState.Available;
657 if (immediate == true)
659 RecycleByLayoutState(recycler, layoutState, true);
662 float remainingSpace = layoutState.Available + layoutState.Extra;
663 LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
664 while ((remainingSpace > 0) && layoutState.HasMore(ItemCount))
666 layoutChunkResult.ResetInternal();
667 LayoutChunk(recycler, layoutState, layoutChunkResult);
668 if (layoutChunkResult.Finished)
672 layoutState.Offset += layoutChunkResult.Consumed * layoutState.LayoutDirection;
674 // Consume the available space if:
675 // layoutChunk did not request to be ignored
676 // OR we are laying out scrap children
677 // OR we are not doing pre-layout
678 if (!layoutChunkResult.IgnoreConsumed)
680 layoutState.Available -= layoutChunkResult.Consumed;
681 // we keep a separate remaining space because mAvailable is important for recycling
682 remainingSpace -= layoutChunkResult.Consumed;
685 if (layoutState.ScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN)
687 layoutState.ScrollingOffset += layoutChunkResult.Consumed;
688 if (layoutState.Available < 0)
690 layoutState.ScrollingOffset += layoutState.Available;
692 if (immediate == true)
694 RecycleByLayoutState(recycler, layoutState, true);
697 if (stopOnFocusable && layoutChunkResult.Focusable)
702 if (immediate == false)
704 RecycleByLayoutState(recycler, layoutState, false);
707 return start - layoutState.Available;
710 private void Cache(FlexibleView.Recycler recycler, LayoutState layoutState, bool immediate, float scrolled = 0)
712 if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
714 // get the first child in the direction we are going
715 FlexibleView.ViewHolder child = GetChildClosestToEnd();
716 //Log.Fatal("TV.FLUX.Component", $"==========> child:{child.LayoutGroupIndex}-{child.LayoutItemIndex} childEnd:{orientationHelper.GetItemEnd(child)} # {orientationHelper.GetEnd()}");
720 if (child.ItemView.Focusable == false || mOrientationHelper.GetViewHolderEnd(child) + scrolled < mOrientationHelper.GetEnd())
722 layoutState.Available = MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace();
723 layoutState.Extra = 0;
724 layoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
725 layoutState.Recycle = false;
726 Fill(recycler, layoutState, true, immediate);
732 FlexibleView.ViewHolder child = GetChildClosestToStart();
736 if (child.ItemView.Focusable == false || mOrientationHelper.GetViewHolderStart(child) + scrolled > 0)
738 layoutState.Available = MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace();
739 layoutState.Extra = 0;
740 layoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
741 layoutState.Recycle = false;
742 Fill(recycler, layoutState, true, immediate);
748 private void RecycleByLayoutState(FlexibleView.Recycler recycler, LayoutState layoutState, bool immediate)
750 if (!layoutState.Recycle)
754 if (layoutState.LayoutDirection == LayoutState.LAYOUT_START)
756 RecycleViewsFromEnd(recycler, layoutState.ScrollingOffset, immediate);
760 RecycleViewsFromStart(recycler, layoutState.ScrollingOffset, immediate);
764 private void RecycleViewsFromStart(FlexibleView.Recycler recycler, float dt, bool immediate)
770 // ignore padding, ViewGroup may not clip children.
772 int childCount = ChildCount;
773 if (mShouldReverseLayout)
775 for (int i = childCount - 1; i >= 0; i--)
777 FlexibleView.ViewHolder child = GetChildAt(i);
778 if (mOrientationHelper.GetViewHolderEnd(child) > limit)
781 RecycleChildren(recycler, childCount - 1, i, immediate);
788 for (int i = 0; i < childCount; i++)
790 FlexibleView.ViewHolder child = GetChildAt(i);
791 if (mOrientationHelper.GetViewHolderEnd(child) > limit)
794 RecycleChildren(recycler, 0, i, immediate);
801 private void RecycleViewsFromEnd(FlexibleView.Recycler recycler, float dt, bool immediate)
803 int childCount = ChildCount;
808 float limit = mOrientationHelper.GetEnd() - dt;
809 if (mShouldReverseLayout)
811 for (int i = 0; i < childCount; i++)
813 FlexibleView.ViewHolder child = GetChildAt(i);
814 if (mOrientationHelper.GetViewHolderStart(child) < limit)
817 RecycleChildren(recycler, 0, i, immediate);
824 for (int i = childCount - 1; i >= 0; i--)
826 FlexibleView.ViewHolder child = GetChildAt(i);
827 if (mOrientationHelper.GetViewHolderStart(child) < limit)
830 RecycleChildren(recycler, childCount - 1, i, immediate);
837 private float ScrollBy(float dy, FlexibleView.Recycler recycler, bool immediate)
839 if (ChildCount == 0 || dy == 0)
843 mLayoutState.Recycle = true;
844 int layoutDirection = dy < 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
845 float absDy = Math.Abs(dy);
846 UpdateLayoutState(layoutDirection, absDy, true);
847 float consumed = mLayoutState.ScrollingOffset
848 + Fill(recycler, mLayoutState, false, immediate);
855 float scrolled = absDy > consumed ? -layoutDirection * consumed : dy;
857 Cache(recycler, mLayoutState, immediate, scrolled);
859 mOrientationHelper.OffsetChildren(scrolled, immediate);
864 private void UpdateLayoutState(int layoutDirection, float requiredSpace, bool canUseExistingSpace)
866 mLayoutState.Extra = 0;
867 mLayoutState.LayoutDirection = layoutDirection;
868 float scrollingOffset = 0.0f;
869 if (layoutDirection == LayoutState.LAYOUT_END)
871 mLayoutState.Extra += mOrientationHelper.GetEndPadding();
872 // get the first child in the direction we are going
873 FlexibleView.ViewHolder child = GetChildClosestToEnd();
876 // the direction in which we are traversing children
877 mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
878 : LayoutState.ITEM_DIRECTION_TAIL;
879 mLayoutState.CurrentPosition = child.LayoutPosition + mLayoutState.ItemDirection;
880 mLayoutState.Offset = mOrientationHelper.GetViewHolderEnd(child);
881 // calculate how much we can scroll without adding new children (independent of layout)
882 scrollingOffset = mOrientationHelper.GetViewHolderEnd(child)
883 - mOrientationHelper.GetEndAfterPadding();
889 mLayoutState.Extra += mOrientationHelper.GetStartAfterPadding();
890 FlexibleView.ViewHolder child = GetChildClosestToStart();
893 mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
894 : LayoutState.ITEM_DIRECTION_HEAD;
895 mLayoutState.CurrentPosition = child.LayoutPosition + mLayoutState.ItemDirection;
896 mLayoutState.Offset = mOrientationHelper.GetViewHolderStart(child);
897 scrollingOffset = -mOrientationHelper.GetViewHolderStart(child)
898 + mOrientationHelper.GetStartAfterPadding();
901 mLayoutState.Available = requiredSpace;
902 if (canUseExistingSpace)
904 mLayoutState.Available -= scrollingOffset;
906 mLayoutState.ScrollingOffset = scrollingOffset;
910 // Convenience method to find the child closes to start. Caller should check it has enough
913 // @return The child closes to start of the layout from user's perspective.
914 private FlexibleView.ViewHolder GetChildClosestToStart()
916 return GetChildAt(mShouldReverseLayout ? ChildCount - 1 : 0);
919 // Convenience method to find the child closes to end. Caller should check it has enough
922 // @return The child closes to end of the layout from user's perspective.
923 private FlexibleView.ViewHolder GetChildClosestToEnd()
925 return GetChildAt(mShouldReverseLayout ? 0 : ChildCount - 1);
928 private void UpdateLayoutStateToFillEnd(int itemPosition, float offset)
930 mLayoutState.Available = mOrientationHelper.GetEndAfterPadding() - offset;
931 mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
932 LayoutState.ITEM_DIRECTION_TAIL;
933 mLayoutState.CurrentPosition = itemPosition;
934 mLayoutState.LayoutDirection = LayoutState.LAYOUT_END;
935 mLayoutState.Offset = offset;
936 mLayoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
937 mLayoutState.Extra = mOrientationHelper.GetEndPadding();
940 private void UpdateLayoutStateToFillStart(int itemPosition, float offset)
942 mLayoutState.Available = offset - mOrientationHelper.GetStartAfterPadding();
943 mLayoutState.CurrentPosition = itemPosition;
944 mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL :
945 LayoutState.ITEM_DIRECTION_HEAD;
946 mLayoutState.LayoutDirection = LayoutState.LAYOUT_START;
947 mLayoutState.Offset = offset;
948 mLayoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
949 mLayoutState.Extra = mOrientationHelper.GetStartAfterPadding();
952 private FlexibleView.ViewHolder FindFirstVisibleItemView()
954 int childCount = ChildCount;
955 if (mShouldReverseLayout == false)
957 for (int i = 0; i < childCount; i++)
959 FlexibleView.ViewHolder child = GetChildAt(i);
960 if ((int)mOrientationHelper.GetViewHolderEnd(child) > 0)
968 for (int i = childCount - 1; i >= 0; i--)
970 FlexibleView.ViewHolder child = GetChildAt(i);
971 if ((int)mOrientationHelper.GetViewHolderEnd(child) > 0)
980 private FlexibleView.ViewHolder FindFirstCompleteVisibleItemView()
982 int childCount = ChildCount;
983 if (mShouldReverseLayout == false)
985 for (int i = 0; i < childCount; i++)
987 FlexibleView.ViewHolder child = GetChildAt(i);
988 if ((int)mOrientationHelper.GetViewHolderStart(child) > 0)
996 for (int i = childCount - 1; i >= 0; i--)
998 FlexibleView.ViewHolder child = GetChildAt(i);
999 if ((int)mOrientationHelper.GetViewHolderStart(child) > 0)
1008 private FlexibleView.ViewHolder FindLastVisibleItemView()
1010 int childCount = ChildCount;
1011 if (mShouldReverseLayout == false)
1013 for (int i = childCount - 1; i >= 0; i--)
1015 FlexibleView.ViewHolder child = GetChildAt(i);
1016 if ((int)mOrientationHelper.GetViewHolderStart(child) < (int)mOrientationHelper.GetEnd())
1024 for (int i = 0; i < childCount; i++)
1026 FlexibleView.ViewHolder child = GetChildAt(i);
1027 if ((int)mOrientationHelper.GetViewHolderStart(child) < (int)mOrientationHelper.GetEnd())
1036 private FlexibleView.ViewHolder FindLastCompleteVisibleItemView()
1038 int childCount = ChildCount;
1039 if (mShouldReverseLayout == false)
1041 for (int i = childCount - 1; i >= 0; i--)
1043 FlexibleView.ViewHolder child = GetChildAt(i);
1044 if ((int)mOrientationHelper.GetViewHolderEnd(child) < (int)mOrientationHelper.GetEnd())
1052 for (int i = 0; i < childCount; i++)
1054 FlexibleView.ViewHolder child = GetChildAt(i);
1055 if ((int)mOrientationHelper.GetViewHolderEnd(child) < (int)mOrientationHelper.GetEnd())
1064 // Helper class that keeps temporary state while {LayoutManager} is filling out the empty space.
1065 internal class LayoutState
1067 public static readonly int LAYOUT_START = -1;
1069 public static readonly int LAYOUT_END = 1;
1071 public static readonly int INVALID_LAYOUT = -1000;
1073 public static readonly int ITEM_DIRECTION_HEAD = -1;
1075 public static readonly int ITEM_DIRECTION_TAIL = 1;
1077 public static readonly int SCROLLING_OFFSET_NaN = -10000;
1079 // We may not want to recycle children in some cases (e.g. layout)
1080 public bool Recycle = true;
1082 // Pixel offset where layout should start
1083 public float Offset;
1085 // Number of pixels that we should fill, in the layout direction.
1086 public float Available;
1088 // Current position on the adapter to get the next item.
1089 public int CurrentPosition;
1091 // Defines the direction in which the data adapter is traversed.
1092 // Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL}
1093 public int ItemDirection;
1095 // Defines the direction in which the layout is filled.
1096 // Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
1097 public int LayoutDirection;
1099 // Used when LayoutState is constructed in a scrolling state.
1100 // It should be set the amount of scrolling we can make without creating a new view.
1101 // Settings this is required for efficient view recycling.
1102 public float ScrollingOffset;
1104 // Used if you want to pre-layout items that are not yet visible.
1105 // The difference with {@link #mAvailable} is that, when recycling, distance laid out for
1106 // {@link #mExtra} is not considered to avoid recycling visible children.
1107 public float Extra = 0;
1110 // @return true if there are more items in the data adapter
1111 public bool HasMore(int itemCount)
1113 return CurrentPosition >= 0 && CurrentPosition < itemCount;
1116 // Gets the view for the next element that we should layout.
1117 // Also updates current item index to the next item, based on {@link #mItemDirection}
1119 // @return The next element that we should layout.
1120 public FlexibleView.ViewHolder Next(FlexibleView.Recycler recycler)
1122 FlexibleView.ViewHolder itemView = recycler.GetViewForPosition(CurrentPosition);
1123 CurrentPosition += ItemDirection;
1128 internal class LayoutChunkResult
1130 public float Consumed;
1131 public bool Finished;
1132 public bool IgnoreConsumed;
1133 public bool Focusable;
1135 public void ResetInternal()
1139 IgnoreConsumed = false;
1144 internal class AnchorInfo
1146 public int Position;
1147 public float Coordinate;
1148 public bool LayoutFromEnd;
1153 Position = NO_POSITION;
1154 Coordinate = INVALID_OFFSET;
1155 LayoutFromEnd = false;