2 using System.Collections.Generic;
5 namespace Tizen.NUI.Components
7 public partial class LinearLayoutManager
9 internal virtual void LayoutChunk(FlexibleViewRecycler recycler,
10 LayoutState layoutState, LayoutChunkResult result)
12 FlexibleViewViewHolder holder = layoutState.Next(recycler);
15 // if we are laying out views in scrap, this may return null which means there is
16 // no more items to layout.
17 result.Finished = true;
21 if (mShouldReverseLayout == (layoutState.LayoutDirection == LayoutState.LAYOUT_START))
26 result.Consumed = mOrientationHelper.GetViewHolderMeasurement(holder);
28 float left, top, width, height;
29 if (mOrientation == VERTICAL)
31 width = Width - PaddingLeft - PaddingRight;
32 height = result.Consumed;
34 if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
36 top = layoutState.Offset;
40 top = layoutState.Offset - height;
42 LayoutChild(holder, left, top, width, height);
46 width = result.Consumed;
47 height = Height - PaddingTop - PaddingBottom;
49 if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
51 left = layoutState.Offset;
55 left = layoutState.Offset - width;
57 LayoutChild(holder, left, top, width, height);
60 result.Focusable = true;
63 internal override FlexibleViewViewHolder OnFocusSearchFailed(FlexibleViewViewHolder focused, FlexibleViewLayoutManager.Direction direction, FlexibleViewRecycler recycler)
69 int layoutDir = ConvertFocusDirectionToLayoutDirection(direction);
70 if (layoutDir == LayoutState.INVALID_LAYOUT)
74 int maxScroll = (int)(MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace());
75 UpdateLayoutState(layoutDir, maxScroll, false);
76 mLayoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
77 mLayoutState.Recycle = false;
78 Fill(recycler, mLayoutState, true, true);
80 FlexibleViewViewHolder nextFocus;
81 if (layoutDir == LayoutState.LAYOUT_START)
83 nextFocus = GetChildAt(0);
87 nextFocus = GetChildAt(ChildCount - 1);
92 private void UpdateAnchorInfoForLayout(FlexibleViewRecycler recycler, AnchorInfo anchorInfo)
94 if (UpdateAnchorFromPendingData(anchorInfo))
99 if (UpdateAnchorFromChildren(recycler, anchorInfo))
104 anchorInfo.Position = FocusPosition != NO_POSITION ? FocusPosition : 0;
105 anchorInfo.Coordinate = anchorInfo.LayoutFromEnd ? mOrientationHelper.GetEndAfterPadding() : mOrientationHelper.GetStartAfterPadding();
109 // If there is a pending scroll position or saved states, updates the anchor info from that
110 // data and returns true
111 private bool UpdateAnchorFromPendingData(AnchorInfo anchorInfo)
113 if (mPendingScrollPosition == NO_POSITION)
117 // validate scroll position
118 if (mPendingScrollPosition < 0 || mPendingScrollPosition >= ItemCount)
120 mPendingScrollPosition = NO_POSITION;
121 mPendingScrollPositionOffset = INVALID_OFFSET;
125 anchorInfo.Position = mPendingScrollPosition;
127 if (mPendingScrollPositionOffset == INVALID_OFFSET)
129 anchorInfo.Coordinate = anchorInfo.LayoutFromEnd ? mOrientationHelper.GetEndAfterPadding() : mOrientationHelper.GetStartAfterPadding();
133 if (mShouldReverseLayout)
135 anchorInfo.Coordinate = mOrientationHelper.GetEndAfterPadding()
136 - mPendingScrollPositionOffset;
140 anchorInfo.Coordinate = mOrientationHelper.GetStartAfterPadding()
141 + mPendingScrollPositionOffset;
148 // Finds an anchor child from existing Views. Most of the time, this is the view closest to
149 // start or end that has a valid position (e.g. not removed).
150 // If a child has focus, it is given priority.
151 private bool UpdateAnchorFromChildren(FlexibleViewRecycler recycler, AnchorInfo anchorInfo)
158 FlexibleViewViewHolder anchorChild = FindFirstVisibleItemView();
159 if (anchorChild == null)
161 Log.Error("flexibleview", $"exception occurs when updating anchor information!");
162 anchorChild = GetChildAt(0);
164 anchorInfo.Position = anchorChild.LayoutPosition;
165 anchorInfo.Coordinate = mOrientationHelper.GetViewHolderStart(anchorChild);
170 // Converts a focusDirection to orientation.
172 // @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
173 // {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
174 // {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
175 // or 0 for not applicable
176 // @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction
177 // is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise.
178 private int ConvertFocusDirectionToLayoutDirection(FlexibleViewLayoutManager.Direction focusDirection)
180 switch (focusDirection)
182 case FlexibleViewLayoutManager.Direction.Up:
183 return mOrientation == VERTICAL ? LayoutState.LAYOUT_START
184 : LayoutState.INVALID_LAYOUT;
185 case FlexibleViewLayoutManager.Direction.Down:
186 return mOrientation == VERTICAL ? LayoutState.LAYOUT_END
187 : LayoutState.INVALID_LAYOUT;
188 case FlexibleViewLayoutManager.Direction.Left:
189 return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_START
190 : LayoutState.INVALID_LAYOUT;
191 case FlexibleViewLayoutManager.Direction.Right:
192 return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_END
193 : LayoutState.INVALID_LAYOUT;
195 return LayoutState.INVALID_LAYOUT;
201 private float Fill(FlexibleViewRecycler recycler, LayoutState layoutState, bool stopOnFocusable, bool immediate)
203 float start = layoutState.Available;
205 if (layoutState.ScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN)
207 // TODO ugly bug fix. should not happen
208 if (layoutState.Available < 0)
210 layoutState.ScrollingOffset += layoutState.Available;
212 if (immediate == true)
214 RecycleByLayoutState(recycler, layoutState, true);
217 float remainingSpace = layoutState.Available + layoutState.Extra;
218 LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
219 while ((remainingSpace > 0) && layoutState.HasMore(ItemCount))
221 layoutChunkResult.ResetInternal();
222 LayoutChunk(recycler, layoutState, layoutChunkResult);
223 if (layoutChunkResult.Finished)
227 layoutState.Offset += layoutChunkResult.Consumed * layoutState.LayoutDirection;
229 // Consume the available space if:
230 // layoutChunk did not request to be ignored
231 // OR we are laying out scrap children
232 // OR we are not doing pre-layout
233 if (!layoutChunkResult.IgnoreConsumed)
235 layoutState.Available -= layoutChunkResult.Consumed;
236 // we keep a separate remaining space because mAvailable is important for recycling
237 remainingSpace -= layoutChunkResult.Consumed;
240 if (layoutState.ScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN)
242 layoutState.ScrollingOffset += layoutChunkResult.Consumed;
243 if (layoutState.Available < 0)
245 layoutState.ScrollingOffset += layoutState.Available;
247 if (immediate == true)
249 RecycleByLayoutState(recycler, layoutState, true);
252 if (stopOnFocusable && layoutChunkResult.Focusable)
257 if (immediate == false)
259 RecycleByLayoutState(recycler, layoutState, false);
262 return start - layoutState.Available;
265 private void Cache(FlexibleViewRecycler recycler, LayoutState layoutState, bool immediate, float scrolled = 0)
267 if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
269 // get the first child in the direction we are going
270 FlexibleViewViewHolder child = GetChildClosestToEnd();
273 if (child.ItemView.Focusable == false || mOrientationHelper.GetViewHolderEnd(child) + scrolled < mOrientationHelper.GetEnd())
275 layoutState.Available = MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace();
276 layoutState.Extra = 0;
277 layoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
278 layoutState.Recycle = false;
279 Fill(recycler, layoutState, true, immediate);
285 FlexibleViewViewHolder child = GetChildClosestToStart();
288 if (child.ItemView.Focusable == false || mOrientationHelper.GetViewHolderStart(child) + scrolled > 0)
290 layoutState.Available = MAX_SCROLL_FACTOR * mOrientationHelper.GetTotalSpace();
291 layoutState.Extra = 0;
292 layoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
293 layoutState.Recycle = false;
294 Fill(recycler, layoutState, true, immediate);
300 private void RecycleByLayoutState(FlexibleViewRecycler recycler, LayoutState layoutState, bool immediate)
302 if (!layoutState.Recycle)
306 if (layoutState.LayoutDirection == LayoutState.LAYOUT_START)
308 RecycleViewsFromEnd(recycler, layoutState.ScrollingOffset, immediate);
312 RecycleViewsFromStart(recycler, layoutState.ScrollingOffset, immediate);
316 private void RecycleViewsFromStart(FlexibleViewRecycler recycler, float dt, bool immediate)
322 // ignore padding, ViewGroup may not clip children.
324 int childCount = ChildCount;
325 if (mShouldReverseLayout)
327 for (int i = childCount - 1; i >= 0; i--)
329 FlexibleViewViewHolder child = GetChildAt(i);
330 if (mOrientationHelper.GetViewHolderEnd(child) > limit)
333 RecycleChildren(recycler, childCount - 1, i, immediate);
340 for (int i = 0; i < childCount; i++)
342 FlexibleViewViewHolder child = GetChildAt(i);
343 if (mOrientationHelper.GetViewHolderEnd(child) > limit)
346 RecycleChildren(recycler, 0, i, immediate);
353 private void RecycleViewsFromEnd(FlexibleViewRecycler recycler, float dt, bool immediate)
359 int childCount = ChildCount;
360 float limit = mOrientationHelper.GetEnd() - dt;
361 if (mShouldReverseLayout)
363 for (int i = 0; i < childCount; i++)
365 FlexibleViewViewHolder child = GetChildAt(i);
366 if (mOrientationHelper.GetViewHolderStart(child) < limit)
369 RecycleChildren(recycler, 0, i, immediate);
376 for (int i = childCount - 1; i >= 0; i--)
378 FlexibleViewViewHolder child = GetChildAt(i);
379 if (mOrientationHelper.GetViewHolderStart(child) < limit)
382 RecycleChildren(recycler, childCount - 1, i, immediate);
389 private float ScrollBy(float dy, FlexibleViewRecycler recycler, bool immediate)
391 if (ChildCount == 0 || dy == 0)
395 mLayoutState.Recycle = true;
396 int layoutDirection = dy < 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
397 float absDy = Math.Abs(dy);
399 UpdateLayoutState(layoutDirection, absDy, true);
401 float consumed = mLayoutState.ScrollingOffset
402 + Fill(recycler, mLayoutState, false, immediate);
409 float scrolled = absDy > consumed ? -layoutDirection * consumed : dy;
410 Cache(recycler, mLayoutState, immediate, scrolled);
412 mOrientationHelper.OffsetChildren(scrolled, immediate);
417 private void UpdateLayoutState(int layoutDirection, float requiredSpace, bool canUseExistingSpace)
419 mLayoutState.Extra = 0;
420 mLayoutState.LayoutDirection = layoutDirection;
421 float scrollingOffset = 0.0f;
422 if (layoutDirection == LayoutState.LAYOUT_END)
424 mLayoutState.Extra += mOrientationHelper.GetEndPadding();
425 // get the first child in the direction we are going
426 FlexibleViewViewHolder child = GetChildClosestToEnd();
429 // the direction in which we are traversing children
430 mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
431 : LayoutState.ITEM_DIRECTION_TAIL;
432 mLayoutState.CurrentPosition = child.LayoutPosition + mLayoutState.ItemDirection;
433 mLayoutState.Offset = mOrientationHelper.GetViewHolderEnd(child);
434 // calculate how much we can scroll without adding new children (independent of layout)
435 scrollingOffset = mOrientationHelper.GetViewHolderEnd(child)
436 - mOrientationHelper.GetEndAfterPadding();
442 mLayoutState.Extra += mOrientationHelper.GetStartAfterPadding();
443 FlexibleViewViewHolder child = GetChildClosestToStart();
446 mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
447 : LayoutState.ITEM_DIRECTION_HEAD;
448 mLayoutState.CurrentPosition = child.LayoutPosition + mLayoutState.ItemDirection;
449 mLayoutState.Offset = mOrientationHelper.GetViewHolderStart(child);
450 scrollingOffset = -mOrientationHelper.GetViewHolderStart(child)
451 + mOrientationHelper.GetStartAfterPadding();
454 mLayoutState.Available = requiredSpace;
455 if (canUseExistingSpace)
457 mLayoutState.Available -= scrollingOffset;
459 mLayoutState.ScrollingOffset = scrollingOffset;
462 // Convenience method to find the child closes to start. Caller should check it has enough
465 // @return The child closes to start of the layout from user's perspective.
466 private FlexibleViewViewHolder GetChildClosestToStart()
468 return GetChildAt(mShouldReverseLayout ? ChildCount - 1 : 0);
471 // Convenience method to find the child closes to end. Caller should check it has enough
474 // @return The child closes to end of the layout from user's perspective.
475 private FlexibleViewViewHolder GetChildClosestToEnd()
477 return GetChildAt(mShouldReverseLayout ? 0 : ChildCount - 1);
480 private void UpdateLayoutStateToFillEnd(int itemPosition, float offset)
482 mLayoutState.Available = mOrientationHelper.GetEndAfterPadding() - offset;
483 mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
484 LayoutState.ITEM_DIRECTION_TAIL;
485 mLayoutState.CurrentPosition = itemPosition;
486 mLayoutState.LayoutDirection = LayoutState.LAYOUT_END;
487 mLayoutState.Offset = offset;
488 mLayoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
489 mLayoutState.Extra = mOrientationHelper.GetEndPadding();
493 private void UpdateLayoutStateToFillStart(int itemPosition, float offset)
495 mLayoutState.Available = offset - mOrientationHelper.GetStartAfterPadding();
496 mLayoutState.CurrentPosition = itemPosition;
497 mLayoutState.ItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL :
498 LayoutState.ITEM_DIRECTION_HEAD;
499 mLayoutState.LayoutDirection = LayoutState.LAYOUT_START;
500 mLayoutState.Offset = offset;
501 mLayoutState.ScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
502 mLayoutState.Extra = mOrientationHelper.GetStartAfterPadding();
505 private FlexibleViewViewHolder FindFirstCompleteVisibleItemView()
507 int childCount = ChildCount;
508 if (mShouldReverseLayout == false)
510 for (int i = 0; i < childCount; i++)
512 FlexibleViewViewHolder child = GetChildAt(i);
513 int start = (int)mOrientationHelper.GetViewHolderStart(child);
514 if (start > 0 && start < (int)mOrientationHelper.GetEnd())
522 for (int i = childCount - 1; i >= 0; i--)
524 FlexibleViewViewHolder child = GetChildAt(i);
525 int start = (int)mOrientationHelper.GetViewHolderStart(child);
526 if (start > 0 && start < (int)mOrientationHelper.GetEnd())
535 private FlexibleViewViewHolder FindLastCompleteVisibleItemView()
537 int childCount = ChildCount;
538 if (mShouldReverseLayout == false)
540 for (int i = childCount - 1; i >= 0; i--)
542 FlexibleViewViewHolder child = GetChildAt(i);
543 if ((int)mOrientationHelper.GetViewHolderEnd(child) < (int)mOrientationHelper.GetEnd())
551 for (int i = 0; i < childCount; i++)
553 FlexibleViewViewHolder child = GetChildAt(i);
554 if ((int)mOrientationHelper.GetViewHolderEnd(child) < (int)mOrientationHelper.GetEnd())
563 // Helper class that keeps temporary state while {LayoutManager} is filling out the empty space.
564 internal class LayoutState
566 public const int LAYOUT_START = -1;
568 public const int LAYOUT_END = 1;
570 public const int INVALID_LAYOUT = -1000;
572 public const int ITEM_DIRECTION_HEAD = -1;
574 public const int ITEM_DIRECTION_TAIL = 1;
576 public const int SCROLLING_OFFSET_NaN = -10000;
578 // We may not want to recycle children in some cases (e.g. layout)
579 public bool Recycle = true;
581 // Pixel offset where layout should start
584 // Number of pixels that we should fill, in the layout direction.
585 public float Available;
587 // Current position on the adapter to get the next item.
588 public int CurrentPosition;
590 // Defines the direction in which the data adapter is traversed.
591 // Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL}
592 public int ItemDirection;
594 // Defines the direction in which the layout is filled.
595 // Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
596 public int LayoutDirection;
598 // Used when LayoutState is constructed in a scrolling state.
599 // It should be set the amount of scrolling we can make without creating a new view.
600 // Settings this is required for efficient view recycling.
601 public float ScrollingOffset;
603 // Used if you want to pre-layout items that are not yet visible.
604 // The difference with {@link #mAvailable} is that, when recycling, distance laid out for
605 // {@link #mExtra} is not considered to avoid recycling visible children.
606 public float Extra = 0;
608 // @return true if there are more items in the data adapter
609 public bool HasMore(int itemCount)
611 return CurrentPosition >= 0 && CurrentPosition < itemCount;
614 // Gets the view for the next element that we should layout.
615 // Also updates current item index to the next item, based on {@link #mItemDirection}
617 // @return The next element that we should layout.
618 public FlexibleViewViewHolder Next(FlexibleViewRecycler recycler)
620 FlexibleViewViewHolder itemView = recycler.GetViewForPosition(CurrentPosition);
621 CurrentPosition += ItemDirection;
627 internal class LayoutChunkResult
629 public float Consumed;
630 public bool Finished;
631 public bool IgnoreConsumed;
632 public bool Focusable;
634 public void ResetInternal()
638 IgnoreConsumed = false;
643 internal class AnchorInfo
646 public float Coordinate;
647 public bool LayoutFromEnd;
652 Position = NO_POSITION;
653 Coordinate = INVALID_OFFSET;
654 LayoutFromEnd = false;