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.CommonUI
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();
75 * Stashed to avoid allocation, currently only used in #fill()
77 private LayoutChunkResult mLayoutChunkResult = new LayoutChunkResult();
79 private bool mShouldReverseLayout = false;
82 * When LayoutManager needs to scroll to a position, it sets this variable and requests a
83 * layout which will check this variable and re-layout accordingly.
85 private int mPendingScrollPosition = NO_POSITION;
88 * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is
91 private int mPendingScrollPositionOffset = INVALID_OFFSET;
94 /// Creates a LinearLayoutManager with orientation.
96 /// <param name="orientation">Layout orientation.Should be HORIZONTAL or VERTICAL</param>
97 /// <since_tizen> 6 </since_tizen>
98 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
99 [EditorBrowsable(EditorBrowsableState.Never)]
100 public LinearLayoutManager(int orientation)
102 mOrientation = orientation;
103 mOrientationHelper = OrientationHelper.CreateOrientationHelper(this, mOrientation);
105 mLayoutState = new LayoutState();
106 mLayoutState.Offset = mOrientationHelper.GetStartAfterPadding();
110 /// Retrieves the first visible item position.
112 /// <since_tizen> 6 </since_tizen>
113 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
114 [EditorBrowsable(EditorBrowsableState.Never)]
115 public int FirstVisibleItemPosition
119 FlexibleView.ViewHolder child = FindFirstVisibleItemView();
120 return child == null ? NO_POSITION : child.LayoutPosition;
125 /// Retrieves the first complete visible item position.
127 /// <since_tizen> 6 </since_tizen>
128 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
129 [EditorBrowsable(EditorBrowsableState.Never)]
130 public int FirstCompleteVisibleItemPosition
134 FlexibleView.ViewHolder child = FindFirstCompleteVisibleItemView();
135 return child == null ? NO_POSITION : child.LayoutPosition;
140 /// Retrieves the last visible item position.
142 /// <since_tizen> 6 </since_tizen>
143 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
144 [EditorBrowsable(EditorBrowsableState.Never)]
145 public int LastVisibleItemPosition
149 FlexibleView.ViewHolder child = FindLastVisibleItemView();
150 return child == null ? NO_POSITION : child.LayoutPosition;
155 /// Retrieves the last complete visible item position.
157 /// <since_tizen> 6 </since_tizen>
158 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
159 [EditorBrowsable(EditorBrowsableState.Never)]
160 public int LastCompleteVisibleItemPosition
164 FlexibleView.ViewHolder child = FindLastCompleteVisibleItemView();
165 return child == null ? NO_POSITION : child.LayoutPosition;
170 /// Query if horizontal scrolling is currently supported. The default implementation returns false.
172 /// <since_tizen> 6 </since_tizen>
173 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
174 [EditorBrowsable(EditorBrowsableState.Never)]
175 public override bool CanScrollHorizontally()
177 return mOrientation == HORIZONTAL;
181 /// Query if vertical scrolling is currently supported. The default implementation returns false.
183 /// <since_tizen> 6 </since_tizen>
184 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
185 [EditorBrowsable(EditorBrowsableState.Never)]
186 public override bool CanScrollVertically()
188 return mOrientation == VERTICAL;
192 /// Lay out all relevant child views from the given adapter.
194 /// <param name="recycler">Recycler to use for fetching potentially cached views for a position</param>
195 /// <since_tizen> 6 </since_tizen>
196 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
197 [EditorBrowsable(EditorBrowsableState.Never)]
198 public override void OnLayoutChildren(FlexibleView.Recycler recycler)
200 mLayoutState.Recycle = false;
201 if (!mAnchorInfo.Valid || mPendingScrollPosition != NO_POSITION)
204 mAnchorInfo.LayoutFromEnd = mShouldReverseLayout;
205 // calculate anchor position and coordinate
206 UpdateAnchorInfoForLayout(recycler, mAnchorInfo);
207 mAnchorInfo.Valid = true;
210 int firstLayoutDirection;
211 if (mAnchorInfo.LayoutFromEnd)
213 firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
214 : LayoutState.ITEM_DIRECTION_HEAD;
218 firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
219 : LayoutState.ITEM_DIRECTION_TAIL;
221 EnsureAnchorReady(recycler, mAnchorInfo, firstLayoutDirection);
222 ScrapAttachedViews(recycler);
224 if (mAnchorInfo.LayoutFromEnd == true)
226 UpdateLayoutStateToFillStart(mAnchorInfo.Position, mAnchorInfo.Coordinate);
227 Fill(recycler, mLayoutState, false, true);
228 Cache(recycler, mLayoutState, true);
230 UpdateLayoutStateToFillEnd(mAnchorInfo.Position, mAnchorInfo.Coordinate);
231 mLayoutState.CurrentPosition += mLayoutState.ItemDirection;
232 Fill(recycler, mLayoutState, false, true);
233 Cache(recycler, mLayoutState, true);
237 UpdateLayoutStateToFillEnd(mAnchorInfo.Position, mAnchorInfo.Coordinate);
238 Fill(recycler, mLayoutState, false, true);
239 Cache(recycler, mLayoutState, true);
241 UpdateLayoutStateToFillStart(mAnchorInfo.Position, mAnchorInfo.Coordinate);
242 mLayoutState.CurrentPosition += mLayoutState.ItemDirection;
243 Fill(recycler, mLayoutState, false, true);
244 Cache(recycler, mLayoutState, true);
251 /// Scroll horizontally by dy pixels in screen coordinates.
253 /// <param name="dy">distance to scroll in pixels. Y increases as scroll position approaches the top.</param>
254 /// <param name="recycler">Recycler to use for fetching potentially cached views for a position</param>
255 /// <param name="immediate">Specify if the scroll need animation</param>
256 /// <since_tizen> 6 </since_tizen>
257 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
258 [EditorBrowsable(EditorBrowsableState.Never)]
259 public override float ScrollHorizontallyBy(float dx, FlexibleView.Recycler recycler, bool immediate)
261 if (mOrientation == VERTICAL)
265 return ScrollBy(dx, recycler, immediate);
269 /// Scroll vertically by dy pixels in screen coordinates.
271 /// <param name="dy">distance to scroll in pixels. Y increases as scroll position approaches the top.</param>
272 /// <param name="recycler">Recycler to use for fetching potentially cached views for a position</param>
273 /// <param name="immediate">Specify if the scroll need animation</param>
274 /// <since_tizen> 6 </since_tizen>
275 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
276 [EditorBrowsable(EditorBrowsableState.Never)]
277 public override float ScrollVerticallyBy(float dy, FlexibleView.Recycler recycler, bool immediate)
279 if (mOrientation == HORIZONTAL)
283 return ScrollBy(dy, recycler, immediate); ;
287 /// Compute the offset of the scrollbar's thumb within the range.
289 /// <since_tizen> 6 </since_tizen>
290 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
291 [EditorBrowsable(EditorBrowsableState.Never)]
292 public override float ComputeScrollOffset()
294 FlexibleView.ViewHolder startChild = FindFirstVisibleItemView();
295 FlexibleView.ViewHolder endChild = FindLastVisibleItemView();
296 if (GetChildCount() == 0 || startChild == null || endChild == null)
300 int minPosition = Math.Min(startChild.LayoutPosition, endChild.LayoutPosition);
301 int maxPosition = Math.Max(startChild.LayoutPosition, endChild.LayoutPosition);
302 int itemsBefore = mShouldReverseLayout
303 ? Math.Max(0, ItemCount - maxPosition - 1)
304 : Math.Max(0, minPosition);
306 float laidOutArea = Math.Abs(mOrientationHelper.GetViewHolderEnd(endChild)
307 - mOrientationHelper.GetViewHolderStart(startChild));
308 int itemRange = Math.Abs(startChild.LayoutPosition - endChild.LayoutPosition) + 1;
309 float avgSizePerRow = laidOutArea / itemRange;
311 return (float)Math.Round(itemsBefore * avgSizePerRow + (mOrientationHelper.GetStartAfterPadding()
312 - mOrientationHelper.GetViewHolderStart(startChild)));
316 /// Compute the extent of the scrollbar's thumb within the range.
318 /// <since_tizen> 6 </since_tizen>
319 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
320 [EditorBrowsable(EditorBrowsableState.Never)]
321 public override float ComputeScrollExtent()
323 FlexibleView.ViewHolder startChild = FindFirstVisibleItemView();
324 FlexibleView.ViewHolder endChild = FindLastVisibleItemView();
325 if (GetChildCount() == 0 || startChild == null || endChild == null)
329 float extend = mOrientationHelper.GetViewHolderEnd(endChild)
330 - mOrientationHelper.GetViewHolderStart(startChild);
331 return Math.Min(mOrientationHelper.GetTotalSpace(), extend);
335 /// Compute the range that the scrollbar represents.
337 /// <since_tizen> 6 </since_tizen>
338 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
339 [EditorBrowsable(EditorBrowsableState.Never)]
340 public override float ComputeScrollRange()
342 FlexibleView.ViewHolder startChild = FindFirstVisibleItemView();
343 FlexibleView.ViewHolder endChild = FindLastVisibleItemView();
344 if (GetChildCount() == 0 || startChild == null || endChild == null)
348 float laidOutArea = mOrientationHelper.GetViewHolderEnd(endChild)
349 - mOrientationHelper.GetViewHolderStart(startChild);
350 int laidOutRange = Math.Abs(startChild.LayoutPosition - endChild.LayoutPosition) + 1;
351 // estimate a size for full list.
352 return laidOutArea / laidOutRange * ItemCount;
356 /// Scroll the FlexibleView to make the position visible.
358 /// <param name="position">Scroll to this adapter position</param>
359 /// <since_tizen> 6 </since_tizen>
360 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
361 [EditorBrowsable(EditorBrowsableState.Never)]
362 public override void ScrollToPosition(int position)
364 mPendingScrollPosition = position;
365 mPendingScrollPositionOffset = INVALID_OFFSET;
371 /// Scroll to the specified adapter position with the given offset from resolved layout start.
373 /// <param name="position">Scroll to this adapter position</param>
374 /// <param name="offset">The distance (in pixels) between the start edge of the item view and start edge of the FlexibleView.</param>
375 /// <since_tizen> 6 </since_tizen>
376 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
377 [EditorBrowsable(EditorBrowsableState.Never)]
378 public override void ScrollToPositionWithOffset(int position, int offset)
380 mPendingScrollPosition = position;
381 mPendingScrollPositionOffset = offset;
387 /// Called after a full layout calculation is finished.
389 /// <since_tizen> 6 </since_tizen>
390 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
391 [EditorBrowsable(EditorBrowsableState.Never)]
392 public override void OnLayoutCompleted()
394 if (mPendingScrollPosition != NO_POSITION)
396 ChangeFocus(mPendingScrollPosition);
398 mPendingScrollPosition = NO_POSITION;
399 mPendingScrollPositionOffset = INVALID_OFFSET;
404 internal virtual void EnsureAnchorReady(FlexibleView.Recycler recycler, AnchorInfo anchorInfo, int itemDirection)
411 /// Retrieves a position that neighbor to current position by direction.
413 /// <param name="position">The anchor adapter position</param>
414 /// <param name="direction">The direction.</param>
415 /// <since_tizen> 6 </since_tizen>
416 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
417 [EditorBrowsable(EditorBrowsableState.Never)]
418 protected override int GetNextPosition(int position, FlexibleView.LayoutManager.Direction direction)
420 if (mOrientation == HORIZONTAL)
424 case FlexibleView.LayoutManager.Direction.Left:
430 case FlexibleView.LayoutManager.Direction.Right:
431 if (position < ItemCount - 1)
442 case FlexibleView.LayoutManager.Direction.Up:
448 case FlexibleView.LayoutManager.Direction.Down:
449 if (position < ItemCount - 1)
460 internal virtual void LayoutChunk(FlexibleView.Recycler recycler,
461 LayoutState layoutState, LayoutChunkResult result)
463 FlexibleView.ViewHolder holder = layoutState.Next(recycler);
466 // if we are laying out views in scrap, this may return null which means there is
467 // no more items to layout.
468 result.Finished = true;
472 if (mShouldReverseLayout == (layoutState.LayoutDirection == LayoutState.LAYOUT_START))
477 result.Consumed = mOrientationHelper.GetViewHolderMeasurement(holder);
479 float left, top, width, height;
480 if (mOrientation == VERTICAL)
482 width = GetWidth() - GetPaddingLeft() - GetPaddingRight();
483 height = result.Consumed;
484 left = GetPaddingLeft();
485 if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
487 top = layoutState.Offset;
491 top = layoutState.Offset - height;
493 LayoutChild(holder, left, top, width, height);
497 width = result.Consumed;
498 height = GetHeight() - GetPaddingTop() - GetPaddingBottom();
499 top = GetPaddingTop();
500 if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
502 left = layoutState.Offset;
506 left = layoutState.Offset - width;
508 LayoutChild(holder, left, top, width, height);
511 result.Focusable = true;
514 internal override FlexibleView.ViewHolder OnFocusSearchFailed(FlexibleView.ViewHolder focused, FlexibleView.LayoutManager.Direction direction, FlexibleView.Recycler recycler)
516 if (GetChildCount() == 0)
520 int layoutDir = ConvertFocusDirectionToLayoutDirection(direction);
521 if (layoutDir == LayoutState.INVALID_LAYOUT)
525 int maxScroll = (int)(MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace());
526 UpdateLayoutState(layoutDir, maxScroll, false);
527 mLayoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
528 mLayoutState.Recycle = false;
529 Fill(recycler, mLayoutState, true, true);
531 FlexibleView.ViewHolder nextFocus;
532 if (layoutDir == LayoutState.LAYOUT_START)
534 nextFocus = GetChildAt(0);
538 nextFocus = GetChildAt(GetChildCount() - 1);
544 private void UpdateAnchorInfoForLayout(FlexibleView.Recycler recycler, AnchorInfo anchorInfo)
546 if (UpdateAnchorFromPendingData(anchorInfo))
551 if (UpdateAnchorFromChildren(recycler, anchorInfo))
556 anchorInfo.Position = FocusPosition != NO_POSITION ? FocusPosition : 0;
557 anchorInfo.Coordinate = anchorInfo.LayoutFromEnd ? mOrientationHelper.GetEndAfterPadding() : mOrientationHelper.GetStartAfterPadding();
561 * If there is a pending scroll position or saved states, updates the anchor info from that
562 * data and returns true
564 private bool UpdateAnchorFromPendingData(AnchorInfo anchorInfo)
566 if (mPendingScrollPosition == NO_POSITION)
570 // validate scroll position
571 if (mPendingScrollPosition < 0 || mPendingScrollPosition >= ItemCount)
573 mPendingScrollPosition = NO_POSITION;
574 mPendingScrollPositionOffset = INVALID_OFFSET;
578 anchorInfo.Position = mPendingScrollPosition;
580 if (mPendingScrollPositionOffset == INVALID_OFFSET)
582 anchorInfo.Coordinate = anchorInfo.LayoutFromEnd ? mOrientationHelper.GetEndAfterPadding() : mOrientationHelper.GetStartAfterPadding();
586 if (mShouldReverseLayout)
588 anchorInfo.Coordinate = mOrientationHelper.GetEndAfterPadding()
589 - mPendingScrollPositionOffset;
593 anchorInfo.Coordinate = mOrientationHelper.GetStartAfterPadding()
594 + mPendingScrollPositionOffset;
601 * Finds an anchor child from existing Views. Most of the time, this is the view closest to
602 * start or end that has a valid position (e.g. not removed).
603 * If a child has focus, it is given priority.
605 private bool UpdateAnchorFromChildren(FlexibleView.Recycler recycler, AnchorInfo anchorInfo)
607 if (GetChildCount() == 0)
612 FlexibleView.ViewHolder anchorChild = FindFirstCompleteVisibleItemView();
613 if (anchorChild != null)
617 anchorInfo.Position = anchorChild.LayoutPosition;
618 anchorInfo.Coordinate = mOrientationHelper.GetViewHolderStart(anchorChild);
624 * Converts a focusDirection to orientation.
626 * @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
627 * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
628 * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
629 * or 0 for not applicable
630 * @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction
631 * is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise.
633 private int ConvertFocusDirectionToLayoutDirection(FlexibleView.LayoutManager.Direction focusDirection)
635 switch (focusDirection)
637 case FlexibleView.LayoutManager.Direction.Up:
638 return mOrientation == VERTICAL ? LayoutState.LAYOUT_START
639 : LayoutState.INVALID_LAYOUT;
640 case FlexibleView.LayoutManager.Direction.Down:
641 return mOrientation == VERTICAL ? LayoutState.LAYOUT_END
642 : LayoutState.INVALID_LAYOUT;
643 case FlexibleView.LayoutManager.Direction.Left:
644 return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_START
645 : LayoutState.INVALID_LAYOUT;
646 case FlexibleView.LayoutManager.Direction.Right:
647 return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_END
648 : LayoutState.INVALID_LAYOUT;
650 return LayoutState.INVALID_LAYOUT;
656 private float Fill(FlexibleView.Recycler recycler, LayoutState layoutState, bool stopOnFocusable, bool immediate)
658 float start = layoutState.Available;
659 if (layoutState.ScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN)
661 // TODO ugly bug fix. should not happen
662 if (layoutState.Available < 0)
664 layoutState.ScrollingOffset += layoutState.Available;
666 if (immediate == true)
668 RecycleByLayoutState(recycler, layoutState, true);
671 float remainingSpace = layoutState.Available + layoutState.Extra;
672 LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
673 while ((remainingSpace > 0) && layoutState.HasMore(ItemCount))
675 layoutChunkResult.ResetInternal();
676 LayoutChunk(recycler, layoutState, layoutChunkResult);
677 if (layoutChunkResult.Finished)
681 layoutState.Offset += layoutChunkResult.Consumed * layoutState.LayoutDirection;
683 * Consume the available space if:
684 * layoutChunk did not request to be ignored
685 * OR we are laying out scrap children
686 * OR we are not doing pre-layout
688 if (!layoutChunkResult.IgnoreConsumed)
690 layoutState.Available -= layoutChunkResult.Consumed;
691 // we keep a separate remaining space because mAvailable is important for recycling
692 remainingSpace -= layoutChunkResult.Consumed;
695 if (layoutState.ScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN)
697 layoutState.ScrollingOffset += layoutChunkResult.Consumed;
698 if (layoutState.Available < 0)
700 layoutState.ScrollingOffset += layoutState.Available;
702 if (immediate == true)
704 RecycleByLayoutState(recycler, layoutState, true);
707 if (stopOnFocusable && layoutChunkResult.Focusable)
712 if (immediate == false)
714 RecycleByLayoutState(recycler, layoutState, false);
717 return start - layoutState.Available;
720 private void Cache(FlexibleView.Recycler recycler, LayoutState layoutState, bool immediate, float scrolled = 0)
722 if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
724 // get the first child in the direction we are going
725 FlexibleView.ViewHolder child = GetChildClosestToEnd();
726 //Log.Fatal("TV.FLUX.Component", $"==========> child:{child.LayoutGroupIndex}-{child.LayoutItemIndex} childEnd:{orientationHelper.GetItemEnd(child)} # {orientationHelper.GetEnd()}");
730 if (child.ItemView.Focusable == false || mOrientationHelper.GetViewHolderEnd(child) + scrolled < mOrientationHelper.GetEnd())
732 layoutState.Available = MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace();
733 layoutState.Extra = 0;
734 layoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
735 layoutState.Recycle = false;
736 Fill(recycler, layoutState, true, immediate);
742 FlexibleView.ViewHolder child = GetChildClosestToStart();
746 if (child.ItemView.Focusable == false || mOrientationHelper.GetViewHolderStart(child) + scrolled > 0)
748 layoutState.Available = MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace();
749 layoutState.Extra = 0;
750 layoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
751 layoutState.Recycle = false;
752 Fill(recycler, layoutState, true, immediate);
758 private void RecycleByLayoutState(FlexibleView.Recycler recycler, LayoutState layoutState, bool immediate)
760 if (!layoutState.Recycle)
764 if (layoutState.LayoutDirection == LayoutState.LAYOUT_START)
766 RecycleViewsFromEnd(recycler, layoutState.ScrollingOffset, immediate);
770 RecycleViewsFromStart(recycler, layoutState.ScrollingOffset, immediate);
774 private void RecycleViewsFromStart(FlexibleView.Recycler recycler, float dt, bool immediate)
780 // ignore padding, ViewGroup may not clip children.
782 int childCount = GetChildCount();
783 if (mShouldReverseLayout)
785 for (int i = childCount - 1; i >= 0; i--)
787 FlexibleView.ViewHolder child = GetChildAt(i);
788 if (mOrientationHelper.GetViewHolderEnd(child) > limit)
791 RecycleChildren(recycler, childCount - 1, i, immediate);
798 for (int i = 0; i < childCount; i++)
800 FlexibleView.ViewHolder child = GetChildAt(i);
801 if (mOrientationHelper.GetViewHolderEnd(child) > limit)
804 RecycleChildren(recycler, 0, i, immediate);
811 private void RecycleViewsFromEnd(FlexibleView.Recycler recycler, float dt, bool immediate)
813 int childCount = GetChildCount();
818 float limit = mOrientationHelper.GetEnd() - dt;
819 if (mShouldReverseLayout)
821 for (int i = 0; i < childCount; i++)
823 FlexibleView.ViewHolder child = GetChildAt(i);
824 if (mOrientationHelper.GetViewHolderStart(child) < limit)
827 RecycleChildren(recycler, 0, i, immediate);
834 for (int i = childCount - 1; i >= 0; i--)
836 FlexibleView.ViewHolder child = GetChildAt(i);
837 if (mOrientationHelper.GetViewHolderStart(child) < limit)
840 RecycleChildren(recycler, childCount - 1, i, immediate);
847 private float ScrollBy(float dy, FlexibleView.Recycler recycler, bool immediate)
849 if (GetChildCount() == 0 || dy == 0)
853 mLayoutState.Recycle = true;
854 int layoutDirection = dy < 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
855 float absDy = Math.Abs(dy);
856 UpdateLayoutState(layoutDirection, absDy, true);
857 float consumed = mLayoutState.ScrollingOffset
858 + Fill(recycler, mLayoutState, false, immediate);
865 float scrolled = absDy > consumed ? -layoutDirection * consumed : dy;
867 Cache(recycler, mLayoutState, immediate, scrolled);
869 mOrientationHelper.OffsetChildren(scrolled, immediate);
875 private void UpdateLayoutState(int layoutDirection, float requiredSpace, bool canUseExistingSpace)
877 mLayoutState.Extra = 0;
878 mLayoutState.LayoutDirection = layoutDirection;
879 float scrollingOffset = 0.0f;
880 if (layoutDirection == LayoutState.LAYOUT_END)
882 mLayoutState.Extra += mOrientationHelper.GetEndPadding();
883 // get the first child in the direction we are going
884 FlexibleView.ViewHolder child = GetChildClosestToEnd();
887 // the direction in which we are traversing children
888 mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
889 : LayoutState.ITEM_DIRECTION_TAIL;
890 mLayoutState.CurrentPosition = child.LayoutPosition + mLayoutState.ItemDirection;
891 mLayoutState.Offset = mOrientationHelper.GetViewHolderEnd(child);
892 // calculate how much we can scroll without adding new children (independent of layout)
893 scrollingOffset = mOrientationHelper.GetViewHolderEnd(child)
894 - mOrientationHelper.GetEndAfterPadding();
900 mLayoutState.Extra += mOrientationHelper.GetStartAfterPadding();
901 FlexibleView.ViewHolder child = GetChildClosestToStart();
904 mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
905 : LayoutState.ITEM_DIRECTION_HEAD;
906 mLayoutState.CurrentPosition = child.LayoutPosition + mLayoutState.ItemDirection;
907 mLayoutState.Offset = mOrientationHelper.GetViewHolderStart(child);
908 scrollingOffset = -mOrientationHelper.GetViewHolderStart(child)
909 + mOrientationHelper.GetStartAfterPadding();
912 mLayoutState.Available = requiredSpace;
913 if (canUseExistingSpace)
915 mLayoutState.Available -= scrollingOffset;
917 mLayoutState.ScrollingOffset = scrollingOffset;
921 * Convenience method to find the child closes to start. Caller should check it has enough
924 * @return The child closes to start of the layout from user's perspective.
926 private FlexibleView.ViewHolder GetChildClosestToStart()
928 return GetChildAt(mShouldReverseLayout ? GetChildCount() - 1 : 0);
932 * Convenience method to find the child closes to end. Caller should check it has enough
935 * @return The child closes to end of the layout from user's perspective.
937 private FlexibleView.ViewHolder GetChildClosestToEnd()
939 return GetChildAt(mShouldReverseLayout ? 0 : GetChildCount() - 1);
942 private void UpdateLayoutStateToFillEnd(int itemPosition, float offset)
944 mLayoutState.Available = mOrientationHelper.GetEndAfterPadding() - offset;
945 mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
946 LayoutState.ITEM_DIRECTION_TAIL;
947 mLayoutState.CurrentPosition = itemPosition;
948 mLayoutState.LayoutDirection = LayoutState.LAYOUT_END;
949 mLayoutState.Offset = offset;
950 mLayoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
951 mLayoutState.Extra = mOrientationHelper.GetEndPadding();
954 private void UpdateLayoutStateToFillStart(int itemPosition, float offset)
956 mLayoutState.Available = offset - mOrientationHelper.GetStartAfterPadding();
957 mLayoutState.CurrentPosition = itemPosition;
958 mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL :
959 LayoutState.ITEM_DIRECTION_HEAD;
960 mLayoutState.LayoutDirection = LayoutState.LAYOUT_START;
961 mLayoutState.Offset = offset;
962 mLayoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
963 mLayoutState.Extra = mOrientationHelper.GetStartAfterPadding();
966 private FlexibleView.ViewHolder FindFirstVisibleItemView()
968 int childCount = GetChildCount();
969 if (mShouldReverseLayout == false)
971 for (int i = 0; i < childCount; i++)
973 FlexibleView.ViewHolder child = GetChildAt(i);
974 if ((int)mOrientationHelper.GetViewHolderEnd(child) > 0)
982 for (int i = childCount - 1; i >= 0; i--)
984 FlexibleView.ViewHolder child = GetChildAt(i);
985 if ((int)mOrientationHelper.GetViewHolderEnd(child) > 0)
994 private FlexibleView.ViewHolder FindFirstCompleteVisibleItemView()
996 int childCount = GetChildCount();
997 if (mShouldReverseLayout == false)
999 for (int i = 0; i < childCount; i++)
1001 FlexibleView.ViewHolder child = GetChildAt(i);
1002 if ((int)mOrientationHelper.GetViewHolderStart(child) > 0)
1010 for (int i = childCount - 1; i >= 0; i--)
1012 FlexibleView.ViewHolder child = GetChildAt(i);
1013 if ((int)mOrientationHelper.GetViewHolderStart(child) > 0)
1022 private FlexibleView.ViewHolder FindLastVisibleItemView()
1024 int childCount = GetChildCount();
1025 if (mShouldReverseLayout == false)
1027 for (int i = childCount - 1; i >= 0; i--)
1029 FlexibleView.ViewHolder child = GetChildAt(i);
1030 if ((int)mOrientationHelper.GetViewHolderStart(child) < (int)mOrientationHelper.GetEnd())
1038 for (int i = 0; i < childCount; i++)
1040 FlexibleView.ViewHolder child = GetChildAt(i);
1041 if ((int)mOrientationHelper.GetViewHolderStart(child) < (int)mOrientationHelper.GetEnd())
1050 private FlexibleView.ViewHolder FindLastCompleteVisibleItemView()
1052 int childCount = GetChildCount();
1053 if (mShouldReverseLayout == false)
1055 for (int i = childCount - 1; i >= 0; i--)
1057 FlexibleView.ViewHolder child = GetChildAt(i);
1058 if ((int)mOrientationHelper.GetViewHolderEnd(child) < (int)mOrientationHelper.GetEnd())
1066 for (int i = 0; i < childCount; i++)
1068 FlexibleView.ViewHolder child = GetChildAt(i);
1069 if ((int)mOrientationHelper.GetViewHolderEnd(child) < (int)mOrientationHelper.GetEnd())
1080 * Helper class that keeps temporary state while {LayoutManager} is filling out the empty space.
1082 internal class LayoutState
1084 public static readonly int LAYOUT_START = -1;
1086 public static readonly int LAYOUT_END = 1;
1088 public static readonly int INVALID_LAYOUT = -1000;
1090 public static readonly int ITEM_DIRECTION_HEAD = -1;
1092 public static readonly int ITEM_DIRECTION_TAIL = 1;
1094 public static readonly int SCROLLING_OFFSET_NaN = -10000;
1097 * We may not want to recycle children in some cases (e.g. layout)
1099 public bool Recycle = true;
1102 * Pixel offset where layout should start
1104 public float Offset;
1107 * Number of pixels that we should fill, in the layout direction.
1109 public float Available;
1112 * Current position on the adapter to get the next item.
1114 public int CurrentPosition;
1117 * Defines the direction in which the data adapter is traversed.
1118 * Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL}
1120 public int ItemDirection;
1123 * Defines the direction in which the layout is filled.
1124 * Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
1126 public int LayoutDirection;
1129 * Used when LayoutState is constructed in a scrolling state.
1130 * It should be set the amount of scrolling we can make without creating a new view.
1131 * Settings this is required for efficient view recycling.
1133 public float ScrollingOffset;
1136 * Used if you want to pre-layout items that are not yet visible.
1137 * The difference with {@link #mAvailable} is that, when recycling, distance laid out for
1138 * {@link #mExtra} is not considered to avoid recycling visible children.
1140 public float Extra = 0;
1144 * @return true if there are more items in the data adapter
1146 public bool HasMore(int itemCount)
1148 return CurrentPosition >= 0 && CurrentPosition < itemCount;
1152 * Gets the view for the next element that we should layout.
1153 * Also updates current item index to the next item, based on {@link #mItemDirection}
1155 * @return The next element that we should layout.
1157 public FlexibleView.ViewHolder Next(FlexibleView.Recycler recycler)
1159 FlexibleView.ViewHolder itemView = recycler.GetViewForPosition(CurrentPosition);
1160 CurrentPosition += ItemDirection;
1165 internal class LayoutChunkResult
1167 public float Consumed;
1168 public bool Finished;
1169 public bool IgnoreConsumed;
1170 public bool Focusable;
1172 public void ResetInternal()
1176 IgnoreConsumed = false;
1181 internal class AnchorInfo
1183 public int Position;
1184 public float Coordinate;
1185 public bool LayoutFromEnd;
1190 Position = NO_POSITION;
1191 Coordinate = INVALID_OFFSET;
1192 LayoutFromEnd = false;