[NUI] Refactor NUI Components (#1159)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / FlexibleView / LinearLayoutManager.cs
1 /*
2  * Copyright(c) 2019 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17 using System;
18 using System.ComponentModel;
19
20 namespace Tizen.NUI.Components
21 {
22     /// <summary>
23     /// Layout collection of views horizontally/vertically.
24     /// </summary>
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
29     {
30         /// <summary>
31         /// Constant value: 0.
32         /// </summary>
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;
37         /// <summary>
38         /// Constant value: 1.
39         /// </summary>
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;
44         /// <summary>
45         /// Constant value: -1.
46         /// </summary>
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;
51         /// <summary>
52         /// Constant value: -2^31.
53         /// </summary>
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;
58
59         private static readonly float MAX_SCROLL_FACTOR = 1 / 3f;
60
61         /// <summary>
62         /// Current orientation.
63         /// </summary>
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;
68
69         internal OrientationHelper mOrientationHelper;
70
71         private LayoutState mLayoutState;
72         private AnchorInfo mAnchorInfo = new AnchorInfo();
73
74         // Stashed to avoid allocation, currently only used in #fill()
75         private LayoutChunkResult mLayoutChunkResult = new LayoutChunkResult();
76
77         private bool mShouldReverseLayout = false;
78
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;
82
83         // Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is
84         // called.
85         private int mPendingScrollPositionOffset = INVALID_OFFSET;
86
87         /// <summary>
88         /// Creates a LinearLayoutManager with orientation.
89         /// </summary>
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)
95         {
96             mOrientation = orientation;
97             mOrientationHelper = OrientationHelper.CreateOrientationHelper(this, mOrientation);
98
99             mLayoutState = new LayoutState();
100             mLayoutState.Offset = mOrientationHelper.GetStartAfterPadding();
101         }
102
103         /// <summary>
104         /// Retrieves the first visible item position.
105         /// </summary>
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
110         {
111             get
112             {
113                 FlexibleView.ViewHolder child = FindFirstVisibleItemView();
114                 return child == null ? NO_POSITION : child.LayoutPosition;
115             }
116         }
117
118         /// <summary>
119         /// Retrieves the first complete visible item position.
120         /// </summary>
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
125         {
126             get
127             {
128                 FlexibleView.ViewHolder child = FindFirstCompleteVisibleItemView();
129                 return child == null ? NO_POSITION : child.LayoutPosition;
130             }
131         }
132
133         /// <summary>
134         /// Retrieves the last visible item position.
135         /// </summary>
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
140         {
141             get
142             {
143                 FlexibleView.ViewHolder child = FindLastVisibleItemView();
144                 return child == null ? NO_POSITION : child.LayoutPosition;
145             }
146         }
147
148         /// <summary>
149         /// Retrieves the last complete visible item position.
150         /// </summary>
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
155         {
156             get
157             {
158                 FlexibleView.ViewHolder child = FindLastCompleteVisibleItemView();
159                 return child == null ? NO_POSITION : child.LayoutPosition;
160             }
161         }
162
163         /// <summary>
164         /// Query if horizontal scrolling is currently supported. The default implementation returns false.
165         /// </summary>
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()
170         {
171             return mOrientation == HORIZONTAL;
172         }
173
174         /// <summary>
175         /// Query if vertical scrolling is currently supported. The default implementation returns false.
176         /// </summary>
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()
181         {
182             return mOrientation == VERTICAL;
183         }
184
185         /// <summary>
186         /// Lay out all relevant child views from the given adapter.
187         /// </summary>
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)
193         {
194             mLayoutState.Recycle = false;
195             if (!mAnchorInfo.Valid || mPendingScrollPosition != NO_POSITION)
196             {
197                 mAnchorInfo.Reset();
198                 mAnchorInfo.LayoutFromEnd = mShouldReverseLayout;
199                 // calculate anchor position and coordinate
200                 UpdateAnchorInfoForLayout(recycler, mAnchorInfo);
201                 mAnchorInfo.Valid = true;
202             }
203
204             int firstLayoutDirection;
205             if (mAnchorInfo.LayoutFromEnd)
206             {
207                 firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
208                         : LayoutState.ITEM_DIRECTION_HEAD;
209             }
210             else
211             {
212                 firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
213                         : LayoutState.ITEM_DIRECTION_TAIL;
214             }
215             EnsureAnchorReady(recycler, mAnchorInfo, firstLayoutDirection);
216             ScrapAttachedViews(recycler);
217
218             if (mAnchorInfo.LayoutFromEnd == true)
219             {
220                 UpdateLayoutStateToFillStart(mAnchorInfo.Position, mAnchorInfo.Coordinate);
221                 Fill(recycler, mLayoutState, false, true);
222                 Cache(recycler, mLayoutState, true);
223
224                 UpdateLayoutStateToFillEnd(mAnchorInfo.Position, mAnchorInfo.Coordinate);
225                 mLayoutState.CurrentPosition += mLayoutState.ItemDirection;
226                 Fill(recycler, mLayoutState, false, true);
227                 Cache(recycler, mLayoutState, true);
228             }
229             else
230             {
231                 UpdateLayoutStateToFillEnd(mAnchorInfo.Position, mAnchorInfo.Coordinate);
232                 Fill(recycler, mLayoutState, false, true);
233                 Cache(recycler, mLayoutState, true);
234
235                 UpdateLayoutStateToFillStart(mAnchorInfo.Position, mAnchorInfo.Coordinate);
236                 mLayoutState.CurrentPosition += mLayoutState.ItemDirection;
237                 Fill(recycler, mLayoutState, false, true);
238                 Cache(recycler, mLayoutState, true);
239             }
240
241             OnLayoutCompleted();
242         }
243
244         /// <summary>
245         /// Scroll horizontally by dy pixels in screen coordinates.
246         /// </summary>
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)
254         {
255             if (mOrientation == VERTICAL)
256             {
257                 return 0;
258             }
259             return ScrollBy(dx, recycler, immediate);
260         }
261
262         /// <summary>
263         /// Scroll vertically by dy pixels in screen coordinates.
264         /// </summary>
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)
272         {
273             if (mOrientation == HORIZONTAL)
274             {
275                 return 0;
276             }
277             return ScrollBy(dy, recycler, immediate);
278         }
279
280         /// <summary>
281         /// Compute the offset of the scrollbar's thumb within the range.
282         /// </summary>
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()
287         {
288             FlexibleView.ViewHolder startChild = FindFirstVisibleItemView();
289             FlexibleView.ViewHolder endChild = FindLastVisibleItemView();
290             if (ChildCount == 0 || startChild == null || endChild == null)
291             {
292                 return 0;
293             }
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);
299
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;
304
305             return (float)Math.Round(itemsBefore * avgSizePerRow + (mOrientationHelper.GetStartAfterPadding()
306                     - mOrientationHelper.GetViewHolderStart(startChild)));
307         }
308
309         /// <summary>
310         /// Compute the extent of the scrollbar's thumb within the range.
311         /// </summary>
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()
316         {
317             FlexibleView.ViewHolder startChild = FindFirstVisibleItemView();
318             FlexibleView.ViewHolder endChild = FindLastVisibleItemView();
319             if (ChildCount == 0 || startChild == null || endChild == null)
320             {
321                 return 0;
322             }
323             float extend = mOrientationHelper.GetViewHolderEnd(endChild)
324                 - mOrientationHelper.GetViewHolderStart(startChild);
325             return Math.Min(mOrientationHelper.GetTotalSpace(), extend);
326         }
327
328         /// <summary>
329         /// Compute the range that the scrollbar represents.
330         /// </summary>
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()
335         {
336             FlexibleView.ViewHolder startChild = FindFirstVisibleItemView();
337             FlexibleView.ViewHolder endChild = FindLastVisibleItemView();
338             if (ChildCount == 0 || startChild == null || endChild == null)
339             {
340                 return 0;
341             }
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;
347         }
348
349         /// <summary>
350         /// Scroll the FlexibleView to make the position visible.
351         /// </summary>
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)
357         {
358             mPendingScrollPosition = position;
359             mPendingScrollPositionOffset = INVALID_OFFSET;
360
361             RelayoutRequest();
362         }
363
364         /// <summary>
365         /// Scroll to the specified adapter position with the given offset from resolved layout start.
366         /// </summary>
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)
373         {
374             mPendingScrollPosition = position;
375             mPendingScrollPositionOffset = offset;
376
377             RelayoutRequest();
378         }
379
380         /// <summary>
381         /// Called after a full layout calculation is finished.
382         /// </summary>
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()
387         {
388             if (mPendingScrollPosition != NO_POSITION)
389             {
390                 ChangeFocus(mPendingScrollPosition);
391             }
392             mPendingScrollPosition = NO_POSITION;
393             mPendingScrollPositionOffset = INVALID_OFFSET;
394
395             mAnchorInfo.Reset();
396         }
397
398         internal virtual void EnsureAnchorReady(FlexibleView.Recycler recycler, AnchorInfo anchorInfo, int itemDirection)
399         {
400
401         }
402
403
404         /// <summary>
405         /// Retrieves a position that neighbor to current position by direction.
406         /// </summary>
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)
413         {
414             if (mOrientation == HORIZONTAL)
415             {
416                 switch (direction)
417                 {
418                     case FlexibleView.LayoutManager.Direction.Left:
419                         if (position > 0)
420                         {
421                             return position - 1;
422                         }
423                         break;
424                     case FlexibleView.LayoutManager.Direction.Right:
425                         if (position < ItemCount - 1)
426                         {
427                             return position + 1;
428                         }
429                         break;
430                 }
431             }
432             else
433             {
434                 switch (direction)
435                 {
436                     case FlexibleView.LayoutManager.Direction.Up:
437                         if (position > 0)
438                         {
439                             return position - 1;
440                         }
441                         break;
442                     case FlexibleView.LayoutManager.Direction.Down:
443                         if (position < ItemCount - 1)
444                         {
445                             return position + 1;
446                         }
447                         break;
448                 }
449             }
450
451             return NO_POSITION;
452         }
453
454         /// <summary>
455         /// Retrieves the first visible item view.
456         /// </summary>
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()
461         {
462             int childCount = ChildCount;
463             if (mShouldReverseLayout == false)
464             {
465                 for (int i = 0; i < childCount; i++)
466                 {
467                     FlexibleView.ViewHolder child = GetChildAt(i);
468                     int end = (int)mOrientationHelper.GetViewHolderEnd(child);
469                     if (end >= 0 && end < (int)mOrientationHelper.GetEnd())
470                     {
471                         return child;
472                     }
473                 }
474             }
475             else
476             {
477                 for (int i = childCount - 1; i >= 0; i--)
478                 {
479                     FlexibleView.ViewHolder child = GetChildAt(i);
480                     int end = (int)mOrientationHelper.GetViewHolderEnd(child);
481                     if (end >= 0 && end < (int)mOrientationHelper.GetEnd())
482                     {
483                         return child;
484                     }
485                 }
486             }
487             return null;
488         }
489
490         /// <summary>
491         /// Retrieves the last visible item view.
492         /// </summary>
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()
497         {
498             int childCount = ChildCount;
499             if (mShouldReverseLayout == false)
500             {
501                 for (int i = childCount - 1; i >= 0; i--)
502                 {
503                     FlexibleView.ViewHolder child = GetChildAt(i);
504                     int start = (int)mOrientationHelper.GetViewHolderStart(child);
505                     if (start > 0 && start < (int)mOrientationHelper.GetEnd())
506                     {
507                         return child;
508                     }
509                 }
510             }
511             else
512             {
513                 for (int i = 0; i < childCount; i++)
514                 {
515                     FlexibleView.ViewHolder child = GetChildAt(i);
516                     int start = (int)mOrientationHelper.GetViewHolderStart(child);
517                     if (start > 0 && start < (int)mOrientationHelper.GetEnd())
518                     {
519                         return child;
520                     }
521                 }
522             }
523             return null;
524         }
525
526         internal virtual void LayoutChunk(FlexibleView.Recycler recycler,
527             LayoutState layoutState, LayoutChunkResult result)
528         {
529             FlexibleView.ViewHolder holder = layoutState.Next(recycler);
530             if (holder == null)
531             {
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;
535                 return;
536             }
537
538             if (mShouldReverseLayout == (layoutState.LayoutDirection == LayoutState.LAYOUT_START))
539                 AddView(holder);
540             else
541                 AddView(holder, 0);
542
543             result.Consumed = mOrientationHelper.GetViewHolderMeasurement(holder);
544
545             float left, top, width, height;
546             if (mOrientation == VERTICAL)
547             {
548                 width = Width - PaddingLeft - PaddingRight;
549                 height = result.Consumed;
550                 left = PaddingLeft;
551                 if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
552                 {
553                     top = layoutState.Offset;
554                 }
555                 else
556                 {
557                     top = layoutState.Offset - height;
558                 }
559                 LayoutChild(holder, left, top, width, height);
560             }
561             else
562             {
563                 width = result.Consumed;
564                 height = Height - PaddingTop - PaddingBottom;
565                 top = PaddingTop;
566                 if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
567                 {
568                     left = layoutState.Offset;
569                 }
570                 else
571                 {
572                     left = layoutState.Offset - width;
573                 }
574                 LayoutChild(holder, left, top, width, height);
575             }
576
577             result.Focusable = true;
578         }
579
580         internal override FlexibleView.ViewHolder OnFocusSearchFailed(FlexibleView.ViewHolder focused, FlexibleView.LayoutManager.Direction direction, FlexibleView.Recycler recycler)
581         {
582             if (ChildCount == 0)
583             {
584                 return null;
585             }
586             int layoutDir = ConvertFocusDirectionToLayoutDirection(direction);
587             if (layoutDir == LayoutState.INVALID_LAYOUT)
588             {
589                 return null;
590             }
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);
596
597             FlexibleView.ViewHolder nextFocus;
598             if (layoutDir == LayoutState.LAYOUT_START)
599             {
600                 nextFocus = GetChildAt(0);
601             }
602             else
603             {
604                 nextFocus = GetChildAt(ChildCount - 1);
605             }
606             return nextFocus;
607         }
608
609
610         private void UpdateAnchorInfoForLayout(FlexibleView.Recycler recycler, AnchorInfo anchorInfo)
611         {
612             if (UpdateAnchorFromPendingData(anchorInfo))
613             {
614                 return;
615             }
616
617             if (UpdateAnchorFromChildren(recycler, anchorInfo))
618             {
619                 return;
620             }
621
622             anchorInfo.Position = FocusPosition != NO_POSITION ? FocusPosition : 0;
623             anchorInfo.Coordinate = anchorInfo.LayoutFromEnd ? mOrientationHelper.GetEndAfterPadding() : mOrientationHelper.GetStartAfterPadding();
624         }
625
626         
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)
630         {
631             if (mPendingScrollPosition == NO_POSITION)
632             {
633                 return false;
634             }
635             // validate scroll position
636             if (mPendingScrollPosition < 0 || mPendingScrollPosition >= ItemCount)
637             {
638                 mPendingScrollPosition = NO_POSITION;
639                 mPendingScrollPositionOffset = INVALID_OFFSET;
640                 return false;
641             }
642
643             anchorInfo.Position = mPendingScrollPosition;
644
645             if (mPendingScrollPositionOffset == INVALID_OFFSET)
646             {
647                 anchorInfo.Coordinate = anchorInfo.LayoutFromEnd ? mOrientationHelper.GetEndAfterPadding() : mOrientationHelper.GetStartAfterPadding();
648             }
649             else
650             {
651                 if (mShouldReverseLayout)
652                 {
653                     anchorInfo.Coordinate = mOrientationHelper.GetEndAfterPadding()
654                             - mPendingScrollPositionOffset;
655                 }
656                 else
657                 {
658                     anchorInfo.Coordinate = mOrientationHelper.GetStartAfterPadding()
659                             + mPendingScrollPositionOffset;
660                 }
661             }
662
663             return true;
664         }
665
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)
670         {
671             if (ChildCount == 0)
672             {
673                 return false;
674             }
675
676             FlexibleView.ViewHolder anchorChild = FindFirstVisibleItemView();
677             if (anchorChild == null)
678             {
679                 Log.Error("flexibleview", $"exception occurs when updating anchor information!");
680                 anchorChild = GetChildAt(0);
681             }
682             anchorInfo.Position = anchorChild.LayoutPosition;
683             anchorInfo.Coordinate = mOrientationHelper.GetViewHolderStart(anchorChild);
684
685             return true;
686         }
687
688         // Converts a focusDirection to orientation.
689         //
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)
697         {
698             switch (focusDirection)
699             {
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;
712                 default:
713                     return LayoutState.INVALID_LAYOUT;
714             }
715
716         }
717
718
719         private float Fill(FlexibleView.Recycler recycler, LayoutState layoutState, bool stopOnFocusable, bool immediate)
720         {
721             float start = layoutState.Available;
722
723             if (layoutState.ScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN)
724             {
725                 // TODO ugly bug fix. should not happen
726                 if (layoutState.Available < 0)
727                 {
728                     layoutState.ScrollingOffset += layoutState.Available;
729                 }
730                 if (immediate == true)
731                 {
732                     RecycleByLayoutState(recycler, layoutState, true);
733                 }
734             }
735             float remainingSpace = layoutState.Available + layoutState.Extra;
736             LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
737             while ((remainingSpace > 0) && layoutState.HasMore(ItemCount))
738             {
739                 layoutChunkResult.ResetInternal();
740                 LayoutChunk(recycler, layoutState, layoutChunkResult);
741                 if (layoutChunkResult.Finished)
742                 {
743                     break;
744                 }
745                 layoutState.Offset += layoutChunkResult.Consumed * layoutState.LayoutDirection;
746                 
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)
752                 {
753                     layoutState.Available -= layoutChunkResult.Consumed;
754                     // we keep a separate remaining space because mAvailable is important for recycling
755                     remainingSpace -= layoutChunkResult.Consumed;
756                 }
757
758                 if (layoutState.ScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN)
759                 {
760                     layoutState.ScrollingOffset += layoutChunkResult.Consumed;
761                     if (layoutState.Available < 0)
762                     {
763                         layoutState.ScrollingOffset += layoutState.Available;
764                     }
765                     if (immediate == true)
766                     {
767                         RecycleByLayoutState(recycler, layoutState, true);
768                     }
769                 }
770                 if (stopOnFocusable && layoutChunkResult.Focusable)
771                 {
772                     break;
773                 }
774             }
775             if (immediate == false)
776             {
777                 RecycleByLayoutState(recycler, layoutState, false);
778             }
779
780             return start - layoutState.Available;
781         }
782
783         private void Cache(FlexibleView.Recycler recycler, LayoutState layoutState, bool immediate, float scrolled = 0)
784         {
785             if (layoutState.LayoutDirection == LayoutState.LAYOUT_END)
786             {
787                 // get the first child in the direction we are going
788                 FlexibleView.ViewHolder child = GetChildClosestToEnd();
789                 if (child != null)
790                 {
791                     if (child.ItemView.Focusable == false || mOrientationHelper.GetViewHolderEnd(child) + scrolled < mOrientationHelper.GetEnd())
792                     {
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);
798                     }
799                 }
800             }
801             else
802             {
803                 FlexibleView.ViewHolder child = GetChildClosestToStart();
804                 if (child != null)
805                 {
806                     if (child.ItemView.Focusable == false || mOrientationHelper.GetViewHolderStart(child) + scrolled > 0)
807                     {
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);
813                     }
814                 }
815             }
816         }
817
818         private void RecycleByLayoutState(FlexibleView.Recycler recycler, LayoutState layoutState, bool immediate)
819         {
820             if (!layoutState.Recycle)
821             {
822                 return;
823             }
824             if (layoutState.LayoutDirection == LayoutState.LAYOUT_START)
825             {
826                 RecycleViewsFromEnd(recycler, layoutState.ScrollingOffset, immediate);
827             }
828             else
829             {
830                 RecycleViewsFromStart(recycler, layoutState.ScrollingOffset, immediate);
831             }
832         }
833
834         private void RecycleViewsFromStart(FlexibleView.Recycler recycler, float dt, bool immediate)
835         {
836             if (dt < 0)
837             {
838                 return;
839             }
840             // ignore padding, ViewGroup may not clip children.
841             float limit = dt;
842             int childCount = ChildCount;
843             if (mShouldReverseLayout)
844             {
845                 for (int i = childCount - 1; i >= 0; i--)
846                 {
847                     FlexibleView.ViewHolder child = GetChildAt(i);
848                     if (mOrientationHelper.GetViewHolderEnd(child) > limit)
849                     {
850                         // stop here
851                         RecycleChildren(recycler, childCount - 1, i, immediate);
852                         return;
853                     }
854                 }
855             }
856             else
857             {
858                 for (int i = 0; i < childCount; i++)
859                 {
860                     FlexibleView.ViewHolder child = GetChildAt(i);
861                     if (mOrientationHelper.GetViewHolderEnd(child) > limit)
862                     {
863                         // stop here
864                         RecycleChildren(recycler, 0, i, immediate);
865                         return;
866                     }
867                 }
868             }
869         }
870
871         private void RecycleViewsFromEnd(FlexibleView.Recycler recycler, float dt, bool immediate)
872         {
873             if (dt < 0)
874             {
875                 return;
876             }
877             int childCount = ChildCount;
878             float limit = mOrientationHelper.GetEnd() - dt;
879             if (mShouldReverseLayout)
880             {
881                 for (int i = 0; i < childCount; i++)
882                 {
883                     FlexibleView.ViewHolder child = GetChildAt(i);
884                     if (mOrientationHelper.GetViewHolderStart(child) < limit)
885                     {
886                         // stop here
887                         RecycleChildren(recycler, 0, i, immediate);
888                         return;
889                     }
890                 }
891             }
892             else
893             {
894                 for (int i = childCount - 1; i >= 0; i--)
895                 {
896                     FlexibleView.ViewHolder child = GetChildAt(i);
897                     if (mOrientationHelper.GetViewHolderStart(child) < limit)
898                     {
899                         // stop here
900                         RecycleChildren(recycler, childCount - 1, i, immediate);
901                         return;
902                     }
903                 }
904             }
905         }
906
907         private float ScrollBy(float dy, FlexibleView.Recycler recycler, bool immediate)
908         {
909             if (ChildCount == 0 || dy == 0)
910             {
911                 return 0;
912             }
913             mLayoutState.Recycle = true;
914             int layoutDirection = dy < 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
915             float absDy = Math.Abs(dy);
916
917             UpdateLayoutState(layoutDirection, absDy, true);
918
919             float consumed = mLayoutState.ScrollingOffset
920                 + Fill(recycler, mLayoutState, false, immediate);
921
922             if (consumed < 0)
923             {
924                 return 0;
925             }
926
927             float scrolled = absDy > consumed ? -layoutDirection * consumed : dy;
928             Cache(recycler, mLayoutState, immediate, scrolled);
929
930             mOrientationHelper.OffsetChildren(scrolled, immediate);
931
932             return scrolled;
933         }
934
935         private void UpdateLayoutState(int layoutDirection, float requiredSpace, bool canUseExistingSpace)
936         {
937             mLayoutState.Extra = 0;
938             mLayoutState.LayoutDirection = layoutDirection;
939             float scrollingOffset = 0.0f;
940             if (layoutDirection == LayoutState.LAYOUT_END)
941             {
942                 mLayoutState.Extra += mOrientationHelper.GetEndPadding();
943                 // get the first child in the direction we are going
944                 FlexibleView.ViewHolder child = GetChildClosestToEnd();
945                 if (child != null)
946                 {
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();
955                 }
956
957             }
958             else
959             {
960                 mLayoutState.Extra += mOrientationHelper.GetStartAfterPadding();
961                 FlexibleView.ViewHolder child = GetChildClosestToStart();
962                 if (child != null)
963                 {
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();
970                 }
971             }
972             mLayoutState.Available = requiredSpace;
973             if (canUseExistingSpace)
974             {
975                 mLayoutState.Available -= scrollingOffset;
976             }
977             mLayoutState.ScrollingOffset = scrollingOffset;
978         }
979
980         // Convenience method to find the child closes to start. Caller should check it has enough
981         // children.
982         //
983         // @return The child closes to start of the layout from user's perspective.
984         private FlexibleView.ViewHolder GetChildClosestToStart()
985         {
986             return GetChildAt(mShouldReverseLayout ? ChildCount - 1 : 0);
987         }
988
989         // Convenience method to find the child closes to end. Caller should check it has enough
990         // children.
991         //
992         // @return The child closes to end of the layout from user's perspective.
993         private FlexibleView.ViewHolder GetChildClosestToEnd()
994         {
995             return GetChildAt(mShouldReverseLayout ? 0 : ChildCount - 1);
996         }
997
998         private void UpdateLayoutStateToFillEnd(int itemPosition, float offset)
999         {
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();
1008         }
1009
1010         private void UpdateLayoutStateToFillStart(int itemPosition, float offset)
1011         {
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();
1020         }
1021
1022         private FlexibleView.ViewHolder FindFirstCompleteVisibleItemView()
1023         {
1024             int childCount = ChildCount;
1025             if (mShouldReverseLayout == false)
1026             {
1027                 for (int i = 0; i < childCount; i++)
1028                 {
1029                     FlexibleView.ViewHolder child = GetChildAt(i);
1030                     int start = (int)mOrientationHelper.GetViewHolderStart(child);
1031                     if (start > 0 && start < (int)mOrientationHelper.GetEnd())
1032                     {
1033                         return child;
1034                     }
1035                 }
1036             }
1037             else
1038             {
1039                 for (int i = childCount - 1; i >= 0; i--)
1040                 {
1041                     FlexibleView.ViewHolder child = GetChildAt(i);
1042                     int start = (int)mOrientationHelper.GetViewHolderStart(child);
1043                     if (start > 0 && start < (int)mOrientationHelper.GetEnd())
1044                     {
1045                         return child;
1046                     }
1047                 }
1048             }
1049             return null;
1050         }
1051
1052         private FlexibleView.ViewHolder FindLastCompleteVisibleItemView()
1053         {
1054             int childCount = ChildCount;
1055             if (mShouldReverseLayout == false)
1056             {
1057                 for (int i = childCount - 1; i >= 0; i--)
1058                 {
1059                     FlexibleView.ViewHolder child = GetChildAt(i);
1060                     if ((int)mOrientationHelper.GetViewHolderEnd(child) < (int)mOrientationHelper.GetEnd())
1061                     {
1062                         return child;
1063                     }
1064                 }
1065             }
1066             else
1067             {
1068                 for (int i = 0; i < childCount; i++)
1069                 {
1070                     FlexibleView.ViewHolder child = GetChildAt(i);
1071                     if ((int)mOrientationHelper.GetViewHolderEnd(child) < (int)mOrientationHelper.GetEnd())
1072                     {
1073                         return child;
1074                     }
1075                 }
1076             }
1077             return null;
1078         }
1079
1080         // Helper class that keeps temporary state while {LayoutManager} is filling out the empty space.
1081         internal class LayoutState
1082         {
1083             public static readonly int LAYOUT_START = -1;
1084
1085             public static readonly int LAYOUT_END = 1;
1086
1087             public static readonly int INVALID_LAYOUT = -1000;
1088
1089             public static readonly int ITEM_DIRECTION_HEAD = -1;
1090
1091             public static readonly int ITEM_DIRECTION_TAIL = 1;
1092
1093             public static readonly int SCROLLING_OFFSET_NaN = -10000;
1094
1095             // We may not want to recycle children in some cases (e.g. layout)
1096             public bool Recycle = true;
1097
1098             // Pixel offset where layout should start
1099             public float Offset;
1100
1101             // Number of pixels that we should fill, in the layout direction.
1102             public float Available;
1103
1104             // Current position on the adapter to get the next item.
1105             public int CurrentPosition;
1106
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;
1110
1111             // Defines the direction in which the layout is filled.
1112             // Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
1113             public int LayoutDirection;
1114
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;
1119
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;
1124
1125
1126             // @return true if there are more items in the data adapter
1127             public bool HasMore(int itemCount)
1128             {
1129                 return CurrentPosition >= 0 && CurrentPosition < itemCount;
1130             }
1131
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}
1134             //
1135             // @return The next element that we should layout.
1136             public FlexibleView.ViewHolder Next(FlexibleView.Recycler recycler)
1137             {
1138                 FlexibleView.ViewHolder itemView = recycler.GetViewForPosition(CurrentPosition);
1139                 CurrentPosition += ItemDirection;
1140                 return itemView;
1141             }
1142         }
1143
1144         internal class LayoutChunkResult
1145         {
1146             public float Consumed;
1147             public bool Finished;
1148             public bool IgnoreConsumed;
1149             public bool Focusable;
1150
1151             public void ResetInternal()
1152             {
1153                 Consumed = 0;
1154                 Finished = false;
1155                 IgnoreConsumed = false;
1156                 Focusable = false;
1157             }
1158         }
1159
1160         internal class AnchorInfo
1161         {
1162             public int Position;
1163             public float Coordinate;
1164             public bool LayoutFromEnd;
1165             public bool Valid;
1166
1167             public void Reset()
1168             {
1169                 Position = NO_POSITION;
1170                 Coordinate = INVALID_OFFSET;
1171                 LayoutFromEnd = false;
1172                 Valid = false;
1173             }
1174
1175         }
1176     }
1177 }