[NUI] Support Fade Scroll Bar feature.
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / ScrollableBase.cs
1 /* Copyright (c) 2021 Samsung Electronics Co., Ltd.
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  *
15  */
16 using System;
17 using Tizen.NUI.BaseComponents;
18 using System.Collections.Generic;
19 using System.ComponentModel;
20 using System.Diagnostics;
21 using System.Runtime.InteropServices;
22 using Tizen.NUI.Accessibility;
23
24 namespace Tizen.NUI.Components
25 {
26     /// <summary>
27     /// ScrollEventArgs is a class to record scroll event arguments which will sent to user.
28     /// </summary>
29     /// <since_tizen> 8 </since_tizen>
30     [global::System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1001: Types that own disposable fields should be disposable.", Justification = "Scroll event is temporarily used for notifying scroll position update, so position will not be disposed during the event processing.")]
31     public class ScrollEventArgs : EventArgs
32     {
33         // Position class is derived class of Disposable class and they will be implicitly disposed by DisposeQueue,
34         // so that there will be no memory leak.
35         private Position position;
36         private Position scrollPosition;
37
38         /// <summary>
39         /// Default constructor.
40         /// </summary>
41         /// <param name="position">Current container position</param>
42         /// <since_tizen> 8 </since_tizen>
43         public ScrollEventArgs(Position position)
44         {
45             this.position = position;
46             this.scrollPosition = new Position(-position);
47         }
48
49         /// <summary>
50         /// Current position of ContentContainer.
51         /// </summary>
52         /// <since_tizen> 8 </since_tizen>
53         public Position Position
54         {
55             get
56             {
57                 return position;
58             }
59         }
60         /// <summary>
61         /// Current scroll position of scrollableBase pan.
62         /// This is the position in the opposite direction to the current position of ContentContainer.
63         /// </summary>
64         [EditorBrowsable(EditorBrowsableState.Never)]
65         public Position ScrollPosition
66         {
67             get
68             {
69                 return scrollPosition;
70             }
71         }
72     }
73
74     /// <summary>
75     /// ScrollOutofBoundEventArgs is to record scroll out-of-bound event arguments which will be sent to user.
76     /// </summary>
77     [EditorBrowsable(EditorBrowsableState.Never)]
78     public class ScrollOutOfBoundEventArgs : EventArgs
79     {
80         /// <summary>
81         /// The direction to be touched.
82         /// </summary>
83         [EditorBrowsable(EditorBrowsableState.Never)]
84         public enum Direction
85         {
86             /// <summary>
87             /// Upwards.
88             /// </summary>
89             [EditorBrowsable(EditorBrowsableState.Never)]
90             Up,
91
92             /// <summary>
93             /// Downwards.
94             /// </summary>
95             [EditorBrowsable(EditorBrowsableState.Never)]
96             Down,
97
98             /// <summary>
99             /// Left bound.
100             /// </summary>
101             [EditorBrowsable(EditorBrowsableState.Never)]
102             Left,
103
104             /// <summary>
105             /// Right bound.
106             /// </summary>
107             [EditorBrowsable(EditorBrowsableState.Never)]
108             Right,
109         }
110
111         /// <summary>
112         /// Constructor.
113         /// </summary>
114         /// <param name="direction">Current pan direction</param>
115         /// <param name="displacement">Current total displacement</param>
116         [EditorBrowsable(EditorBrowsableState.Never)]
117         public ScrollOutOfBoundEventArgs(Direction direction, float displacement)
118         {
119             PanDirection = direction;
120             Displacement = displacement;
121         }
122
123         /// <summary>
124         /// Current pan direction of ContentContainer.
125         /// </summary>
126         [EditorBrowsable(EditorBrowsableState.Never)]
127         public Direction PanDirection
128         {
129             get;
130         }
131
132         /// <summary>
133         /// Current total displacement of ContentContainer.
134         /// if its value is greater than 0, it is at the top/left;
135         /// if less than 0, it is at the bottom/right.
136         /// </summary>
137         [EditorBrowsable(EditorBrowsableState.Never)]
138         public float Displacement
139         {
140             get;
141         }
142     }
143
144     /// <summary>
145     /// This class provides a View that can scroll a single View with a layout. This View can be a nest of Views.
146     /// </summary>
147     /// <since_tizen> 8 </since_tizen>
148     public partial class ScrollableBase : Control
149     {
150         static bool LayoutDebugScrollableBase = false; // Debug flag
151         private Direction mScrollingDirection = Direction.Vertical;
152         private bool mScrollEnabled = true;
153         private int mScrollDuration = 125;
154         private int mPageWidth = 0;
155         private float mPageFlickThreshold = 0.4f;
156         private float mScrollingEventThreshold = 0.001f;
157         private bool fadeScrollbar = true;
158
159         private class ScrollableBaseCustomLayout : AbsoluteLayout
160         {
161             protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
162             {
163                 MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK;
164                 MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK;
165
166                 Direction scrollingDirection = Direction.Vertical;
167                 ScrollableBase scrollableBase = this.Owner as ScrollableBase;
168                 if (scrollableBase != null)
169                 {
170                     scrollingDirection = scrollableBase.ScrollingDirection;
171                 }
172
173                 float totalWidth = 0.0f;
174                 float totalHeight = 0.0f;
175
176                 // measure child, should be a single scrolling child
177                 foreach (LayoutItem childLayout in LayoutChildren)
178                 {
179                     if (childLayout != null && childLayout.Owner.Name == "ContentContainer")
180                     {
181                         // Get size of child
182                         // Use an Unspecified MeasureSpecification mode so scrolling child is not restricted to it's parents size in Height (for vertical scrolling)
183                         // or Width for horizontal scrolling
184                         if (scrollingDirection == Direction.Vertical)
185                         {
186                             MeasureSpecification unrestrictedMeasureSpec = new MeasureSpecification(heightMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
187                             MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), unrestrictedMeasureSpec, new LayoutLength(0)); // Height unrestricted by parent
188                         }
189                         else
190                         {
191                             MeasureSpecification unrestrictedMeasureSpec = new MeasureSpecification(widthMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
192                             MeasureChildWithMargins(childLayout, unrestrictedMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0)); // Width unrestricted by parent
193                         }
194
195                         totalWidth = (childLayout.MeasuredWidth.Size + (childLayout.Padding.Start + childLayout.Padding.End)).AsDecimal();
196                         totalHeight = (childLayout.MeasuredHeight.Size + (childLayout.Padding.Top + childLayout.Padding.Bottom)).AsDecimal();
197
198                         if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
199                         {
200                             childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
201                         }
202                         if (childLayout.MeasuredHeight.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
203                         {
204                             childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
205                         }
206                     }
207                 }
208
209                 MeasuredSize widthSizeAndState = ResolveSizeAndState(new LayoutLength(totalWidth), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
210                 MeasuredSize heightSizeAndState = ResolveSizeAndState(new LayoutLength(totalHeight), heightMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
211                 totalWidth = widthSizeAndState.Size.AsDecimal();
212                 totalHeight = heightSizeAndState.Size.AsDecimal();
213
214                 // Ensure layout respects it's given minimum size
215                 totalWidth = Math.Max(totalWidth, SuggestedMinimumWidth.AsDecimal());
216                 totalHeight = Math.Max(totalHeight, SuggestedMinimumHeight.AsDecimal());
217
218                 widthSizeAndState.State = childWidthState;
219                 heightSizeAndState.State = childHeightState;
220
221                 SetMeasuredDimensions(ResolveSizeAndState(new LayoutLength(totalWidth), widthMeasureSpec, childWidthState),
222                     ResolveSizeAndState(new LayoutLength(totalHeight), heightMeasureSpec, childHeightState));
223
224                 // Size of ScrollableBase is changed. Change Page width too.
225                 if (scrollableBase != null)
226                 {
227                     scrollableBase.mPageWidth = (int)MeasuredWidth.Size.AsRoundedValue();
228                     scrollableBase.OnScrollingChildRelayout(null, null);
229                 }
230             }
231         } //  ScrollableBaseCustomLayout
232
233         /// <summary>
234         /// The direction axis to scroll.
235         /// </summary>
236         /// <since_tizen> 8 </since_tizen>
237         public enum Direction
238         {
239             /// <summary>
240             /// Horizontal axis.
241             /// </summary>
242             /// <since_tizen> 8 </since_tizen>
243             Horizontal,
244
245             /// <summary>
246             /// Vertical axis.
247             /// </summary>
248             /// <since_tizen> 8 </since_tizen>
249             Vertical
250         }
251
252         /// <summary>
253         /// Scrolling direction mode.
254         /// Default is Vertical scrolling.
255         /// </summary>
256         /// <since_tizen> 8 </since_tizen>
257         public Direction ScrollingDirection
258         {
259             get
260             {
261                 return (Direction)GetValue(ScrollingDirectionProperty);
262             }
263             set
264             {
265                 SetValue(ScrollingDirectionProperty, value);
266                 NotifyPropertyChanged();
267             }
268         }
269         private Direction InternalScrollingDirection
270         {
271             get
272             {
273                 return mScrollingDirection;
274             }
275             set
276             {
277                 if (value != mScrollingDirection)
278                 {
279                     //Reset scroll position and stop scroll animation
280                     ScrollTo(0, false);
281
282                     mScrollingDirection = value;
283                     mPanGestureDetector.ClearAngles();
284                     mPanGestureDetector.AddDirection(value == Direction.Horizontal ?
285                         PanGestureDetector.DirectionHorizontal : PanGestureDetector.DirectionVertical);
286
287                     ContentContainer.WidthSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent;
288                     ContentContainer.HeightSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent;
289                     SetScrollbar();
290                 }
291             }
292         }
293
294         /// <summary>
295         /// Enable or disable scrolling.
296         /// </summary>
297         /// <since_tizen> 8 </since_tizen>
298         public bool ScrollEnabled
299         {
300             get
301             {
302                 return (bool)GetValue(ScrollEnabledProperty);
303             }
304             set
305             {
306                 SetValue(ScrollEnabledProperty, value);
307                 NotifyPropertyChanged();
308             }
309         }
310         private bool InternalScrollEnabled
311         {
312             get
313             {
314                 return mScrollEnabled;
315             }
316             set
317             {
318                 if (value != mScrollEnabled)
319                 {
320                     mScrollEnabled = value;
321                     if (mScrollEnabled)
322                     {
323                         mPanGestureDetector.Detected += OnPanGestureDetected;
324                     }
325                     else
326                     {
327                         mPanGestureDetector.Detected -= OnPanGestureDetected;
328                     }
329                 }
330             }
331         }
332
333         /// <summary>
334         /// Gets scrollable status.
335         /// </summary>
336         [EditorBrowsable(EditorBrowsableState.Never)]
337         protected override bool AccessibilityIsScrollable()
338         {
339             return true;
340         }
341
342         /// <summary>
343         /// Pages mode, enables moving to the next or return to current page depending on pan displacement.
344         /// Default is false.
345         /// </summary>
346         /// <since_tizen> 8 </since_tizen>
347         public bool SnapToPage
348         {
349             get
350             {
351                 return (bool)GetValue(SnapToPageProperty);
352             }
353             set
354             {
355                 SetValue(SnapToPageProperty, value);
356                 NotifyPropertyChanged();
357             }
358         }
359         private bool InternalSnapToPage { set; get; } = false;
360
361         /// <summary>
362         /// Get current page.
363         /// Working property with SnapToPage property.
364         /// </summary>
365         /// <since_tizen> 8 </since_tizen>
366         public int CurrentPage { get; private set; } = 0;
367
368         /// <summary>
369         /// Duration of scroll animation.
370         /// Default value is 125ms.
371         /// </summary>
372         /// <since_tizen> 8 </since_tizen>
373         public int ScrollDuration
374         {
375             get
376             {
377                 return (int)GetValue(ScrollDurationProperty);
378             }
379             set
380             {
381                 SetValue(ScrollDurationProperty, value);
382                 NotifyPropertyChanged();
383             }
384         }
385         private int InternalScrollDuration
386         {
387             set
388             {
389                 mScrollDuration = value >= 0 ? value : mScrollDuration;
390             }
391             get
392             {
393                 return mScrollDuration;
394             }
395         }
396
397         /// <summary>
398         /// Scroll Available area.
399         /// </summary>
400         /// <since_tizen> 8 </since_tizen>
401         public Vector2 ScrollAvailableArea
402         {
403             get
404             {
405                 return GetValue(ScrollAvailableAreaProperty) as Vector2;
406             }
407             set
408             {
409                 SetValue(ScrollAvailableAreaProperty, value);
410                 NotifyPropertyChanged();
411             }
412         }
413         private Vector2 InternalScrollAvailableArea { set; get; }
414
415         /// <summary>
416         /// An event emitted when user starts dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
417         /// </summary>
418         /// <since_tizen> 8 </since_tizen>
419         public event EventHandler<ScrollEventArgs> ScrollDragStarted;
420
421         /// <summary>
422         /// An event emitted when user stops dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
423         /// </summary>
424         /// <since_tizen> 8 </since_tizen>
425         public event EventHandler<ScrollEventArgs> ScrollDragEnded;
426
427         /// <summary>
428         /// An event emitted when the scrolling slide animation starts, user can subscribe or unsubscribe to this event handler.<br />
429         /// </summary>
430         /// <since_tizen> 8 </since_tizen>
431         public event EventHandler<ScrollEventArgs> ScrollAnimationStarted;
432
433         /// <summary>
434         /// An event emitted when the scrolling slide animation ends, user can subscribe or unsubscribe to this event handler.<br />
435         /// </summary>
436         /// <since_tizen> 8 </since_tizen>
437         public event EventHandler<ScrollEventArgs> ScrollAnimationEnded;
438
439         /// <summary>
440         /// An event emitted when scrolling, user can subscribe or unsubscribe to this event handler.<br />
441         /// </summary>
442         /// <since_tizen> 8 </since_tizen>
443         public event EventHandler<ScrollEventArgs> Scrolling;
444
445         /// <summary>
446         /// An event emitted when scrolling out of bound, user can subscribe or unsubscribe to this event handler.<br />
447         /// </summary>
448         [EditorBrowsable(EditorBrowsableState.Never)]
449         public event EventHandler<ScrollOutOfBoundEventArgs> ScrollOutOfBound;
450
451         /// <summary>
452         /// Scrollbar for ScrollableBase.
453         /// </summary>
454         /// <since_tizen> 8 </since_tizen>
455         public ScrollbarBase Scrollbar
456         {
457             get
458             {
459                 return GetValue(ScrollbarProperty) as ScrollbarBase;
460             }
461             set
462             {
463                 SetValue(ScrollbarProperty, value);
464                 NotifyPropertyChanged();
465             }
466         }
467         private ScrollbarBase InternalScrollbar
468         {
469             get
470             {
471                 return scrollBar;
472             }
473             set
474             {
475                 if (scrollBar)
476                 {
477                     base.Remove(scrollBar);
478                 }
479                 scrollBar = value;
480
481                 if (scrollBar != null)
482                 {
483                     scrollBar.Name = "ScrollBar";
484                     base.Add(scrollBar);
485
486                     if (hideScrollbar)
487                     {
488                         scrollBar.Hide();
489                     }
490                     else
491                     {
492                         scrollBar.Show();
493                     }
494
495                     SetScrollbar();
496                 }
497             }
498         }
499
500         /// <summary>
501         /// Always hide Scrollbar.
502         /// </summary>
503         /// <since_tizen> 8 </since_tizen>
504         public bool HideScrollbar
505         {
506             get
507             {
508                 return (bool)GetValue(HideScrollbarProperty);
509             }
510             set
511             {
512                 SetValue(HideScrollbarProperty, value);
513                 NotifyPropertyChanged();
514             }
515         }
516         private bool InternalHideScrollbar
517         {
518             get
519             {
520                 return hideScrollbar;
521             }
522             set
523             {
524                 hideScrollbar = value;
525
526                 if (scrollBar)
527                 {
528                     if (value)
529                     {
530                         scrollBar.Hide();
531                     }
532                     else
533                     {
534                         scrollBar.Show();
535                         if (fadeScrollbar)
536                         {
537                             scrollBar.Opacity = 1.0f;
538                             scrollBar.FadeOut();
539                         }
540                     }
541                 }
542             }
543         }
544
545         /// <summary>
546         /// The boolean flag for automatic fading Scrollbar.
547         /// Scrollbar will be faded out when scroll stay in certain position longer than the threshold.
548         /// Scrollbar will be faded in scroll position changes.
549         /// </summary>
550         [EditorBrowsable(EditorBrowsableState.Never)]
551         public bool FadeScrollbar
552         {
553             get => (bool)GetValue(FadeScrollbarProperty);
554             set => SetValue(FadeScrollbarProperty, value);
555         }
556
557         private bool InternalFadeScrollbar
558         {
559             get
560             {
561                 return fadeScrollbar;
562             }
563             set
564             {
565                 fadeScrollbar = value;
566
567                 if (scrollBar != null && !hideScrollbar)
568                 {
569                     if (value)
570                     {
571                         scrollBar.FadeOut();
572                     }
573                     else
574                     {
575                         scrollBar.Opacity = 1.0f;
576                         // Removing fadeout timer and animation.
577                         scrollBar.FadeIn();
578                     }
579                 }
580             }
581         }
582
583         /// <summary>
584         /// Container which has content of ScrollableBase.
585         /// </summary>
586         /// <since_tizen> 8 </since_tizen>
587         public View ContentContainer { get; private set; }
588
589         /// <summary>
590         /// Set the layout on this View. Replaces any existing Layout.
591         /// </summary>
592         /// <since_tizen> 8 </since_tizen>
593         public new LayoutItem Layout
594         {
595             get
596             {
597                 return GetValue(LayoutProperty) as LayoutItem;
598             }
599             set
600             {
601                 SetValue(LayoutProperty, value);
602                 NotifyPropertyChanged();
603             }
604         }
605         private LayoutItem InternalLayout
606         {
607             get
608             {
609                 return ContentContainer.Layout;
610             }
611             set
612             {
613                 ContentContainer.Layout = value;
614             }
615         }
616
617         /// <summary>
618         /// List of children of Container.
619         /// </summary>
620         /// <since_tizen> 8 </since_tizen>
621         public new List<View> Children
622         {
623             get
624             {
625                 return ContentContainer.Children;
626             }
627         }
628
629         /// <summary>
630         /// Deceleration rate of scrolling by finger.
631         /// Rate should be bigger than 0 and smaller than 1.
632         /// Default value is 0.998f;
633         /// </summary>
634         /// <since_tizen> 8 </since_tizen>
635         public float DecelerationRate
636         {
637             get
638             {
639                 return (float)GetValue(DecelerationRateProperty);
640             }
641             set
642             {
643                 SetValue(DecelerationRateProperty, value);
644                 NotifyPropertyChanged();
645             }
646         }
647         private float InternalDecelerationRate
648         {
649             get
650             {
651                 return decelerationRate;
652             }
653             set
654             {
655                 decelerationRate = (value < 1 && value > 0) ? value : decelerationRate;
656                 logValueOfDeceleration = (float)Math.Log(value);
657             }
658         }
659
660         /// <summary>
661         /// Threshold not to go infinite at the end of scrolling animation.
662         /// </summary>
663         [EditorBrowsable(EditorBrowsableState.Never)]
664         public float DecelerationThreshold
665         {
666             get
667             {
668                 return (float)GetValue(DecelerationThresholdProperty);
669             }
670             set
671             {
672                 SetValue(DecelerationThresholdProperty, value);
673                 NotifyPropertyChanged();
674             }
675         }
676         private float InternalDecelerationThreshold { get; set; } = 0.1f;
677
678         /// <summary>
679         /// Scrolling event will be thrown when this amount of scroll position is changed.
680         /// If this threshold becomes smaller, the tracking detail increases but the scrolling range that can be tracked becomes smaller.
681         /// If large sized ContentContainer is required, please use larger threshold value.
682         /// Default ScrollingEventThreshold value is 0.001f.
683         /// </summary>
684         [EditorBrowsable(EditorBrowsableState.Never)]
685         public float ScrollingEventThreshold
686         {
687             get
688             {
689                 return (float)GetValue(ScrollingEventThresholdProperty);
690             }
691             set
692             {
693                 SetValue(ScrollingEventThresholdProperty, value);
694                 NotifyPropertyChanged();
695             }
696         }
697         private float InternalScrollingEventThreshold
698         {
699             get
700             {
701                 return mScrollingEventThreshold;
702             }
703             set
704             {
705                 if (mScrollingEventThreshold != value && value > 0)
706                 {
707                     ContentContainer.RemovePropertyNotification(propertyNotification);
708                     propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(value));
709                     propertyNotification.Notified += OnPropertyChanged;
710                     mScrollingEventThreshold = value;
711                 }
712             }
713         }
714
715         /// <summary>
716         /// Page will be changed when velocity of panning is over threshold.
717         /// The unit of threshold is pixel per millisecond.
718         /// </summary>
719         /// <since_tizen> 8 </since_tizen>
720         public float PageFlickThreshold
721         {
722             get
723             {
724                 return (float)GetValue(PageFlickThresholdProperty);
725             }
726             set
727             {
728                 SetValue(PageFlickThresholdProperty, value);
729                 NotifyPropertyChanged();
730             }
731         }
732         private float InternalPageFlickThreshold
733         {
734             get
735             {
736                 return mPageFlickThreshold;
737             }
738             set
739             {
740                 mPageFlickThreshold = value >= 0f ? value : mPageFlickThreshold;
741             }
742         }
743
744         /// <summary>
745         /// Padding for the ScrollableBase
746         /// </summary>
747         [EditorBrowsable(EditorBrowsableState.Never)]
748         public new Extents Padding
749         {
750             get
751             {
752                 return GetValue(PaddingProperty) as Extents;
753             }
754             set
755             {
756                 SetValue(PaddingProperty, value);
757                 NotifyPropertyChanged();
758             }
759         }
760         private Extents InternalPadding
761         {
762             get
763             {
764                 return ContentContainer.Padding;
765             }
766             set
767             {
768                 ContentContainer.Padding = value;
769             }
770         }
771
772         /// <summary>
773         /// Alphafunction for scroll animation.
774         /// </summary>
775         [EditorBrowsable(EditorBrowsableState.Never)]
776         public AlphaFunction ScrollAlphaFunction
777         {
778             get
779             {
780                 return GetValue(ScrollAlphaFunctionProperty) as AlphaFunction;
781             }
782             set
783             {
784                 SetValue(ScrollAlphaFunctionProperty, value);
785                 NotifyPropertyChanged();
786             }
787         }
788         private AlphaFunction InternalScrollAlphaFunction { get; set; } = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
789
790         private bool hideScrollbar = true;
791         private float maxScrollDistance;
792         private float childTargetPosition = 0.0f;
793         private PanGestureDetector mPanGestureDetector;
794         private ScrollbarBase scrollBar;
795         private bool scrolling = false;
796         private float ratioOfScreenWidthToCompleteScroll = 0.5f;
797         private float totalDisplacementForPan = 0.0f;
798         private Size previousContainerSize = new Size();
799         private Size previousSize = new Size();
800         private PropertyNotification propertyNotification;
801         private float noticeAnimationEndBeforePosition = 0.0f;
802         private bool readyToNotice = false;
803
804         /// <summary>
805         /// Notice before animation is finished.
806         /// </summary>
807         [EditorBrowsable(EditorBrowsableState.Never)]
808         // Let's consider more whether this needs to be set as protected.
809         public float NoticeAnimationEndBeforePosition
810         {
811             get
812             {
813                 return (float)GetValue(NoticeAnimationEndBeforePositionProperty);
814             }
815             set
816             {
817                 SetValue(NoticeAnimationEndBeforePositionProperty, value);
818                 NotifyPropertyChanged();
819             }
820         }
821         private float InternalNoticeAnimationEndBeforePosition
822         {
823             get => noticeAnimationEndBeforePosition;
824             set => noticeAnimationEndBeforePosition = value;
825         }
826
827         /// <summary>
828         /// Step scroll move distance.
829         /// Key focus originally moves focusable objects, but in ScrollableBase,
830         /// if focusable object is too far or un-exist and ScrollableBase is focusable,
831         /// it can scroll move itself by key input.
832         /// this value decide how long distance will it moves in one step.
833         /// if any value is not set, step will be moved quater size of ScrollableBase length.
834         /// </summary>
835         [EditorBrowsable(EditorBrowsableState.Never)]
836         public float StepScrollDistance
837         {
838             get
839             {
840                 return (float)GetValue(StepScrollDistanceProperty);
841             }
842             set
843             {
844                 SetValue(StepScrollDistanceProperty, value);
845                 NotifyPropertyChanged();
846             }
847         }
848         private float stepScrollDistance = 0f;
849
850
851         // Let's consider more whether this needs to be set as protected.
852         private float finalTargetPosition;
853
854         private Animation scrollAnimation;
855         // Declare user alpha function delegate
856         [UnmanagedFunctionPointer(CallingConvention.StdCall)]
857         private delegate float UserAlphaFunctionDelegate(float progress);
858         private UserAlphaFunctionDelegate customScrollAlphaFunction;
859         private float velocityOfLastPan = 0.0f;
860         private float panAnimationDuration = 0.0f;
861         private float panAnimationDelta = 0.0f;
862         private float logValueOfDeceleration = 0.0f;
863         private float decelerationRate = 0.0f;
864
865         private View topOverShootingShadowView;
866         private View bottomOverShootingShadowView;
867         private View leftOverShootingShadowView;
868         private View rightOverShootingShadowView;
869         private const int overShootingShadowScaleHeightLimit = 64 * 3;
870         private const int overShootingShadowAnimationDuration = 300;
871         private Animation overShootingShadowAnimation;
872         private bool isOverShootingShadowShown = false;
873         private float startShowShadowDisplacement;
874
875         /// <summary>
876         /// Default Constructor
877         /// </summary>
878         /// <since_tizen> 8 </since_tizen>
879         public ScrollableBase() : base()
880         {
881             DecelerationRate = 0.998f;
882
883             base.Layout = new ScrollableBaseCustomLayout();
884             mPanGestureDetector = new PanGestureDetector();
885             mPanGestureDetector.Attach(this);
886             mPanGestureDetector.AddDirection(PanGestureDetector.DirectionVertical);
887             if (mPanGestureDetector.GetMaximumTouchesRequired() < 2) mPanGestureDetector.SetMaximumTouchesRequired(2);
888             mPanGestureDetector.Detected += OnPanGestureDetected;
889
890             ClippingMode = ClippingModeType.ClipToBoundingBox;
891
892             //Default Scrolling child
893             ContentContainer = new View()
894             {
895                 Name = "ContentContainer",
896                 WidthSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent,
897                 HeightSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent,
898             };
899             // Check if children's sizes change to update Scrollbar
900             ContentContainer.Relayout += OnScrollingChildRelayout;
901             propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(mScrollingEventThreshold));
902             propertyNotification.Notified += OnPropertyChanged;
903             base.Add(ContentContainer);
904             // Check if ScrollableBase's size changes to update Scrollbar
905             base.Relayout += OnScrollingChildRelayout;
906
907             Scrollbar = new Scrollbar();
908
909             //Show vertical shadow on the top (or bottom) of the scrollable when panning down (or up).
910             topOverShootingShadowView = new View
911             {
912                 BackgroundImage = FrameworkInformation.ResourcePath + "nui_component_default_scroll_over_shooting_top.png",
913                 Opacity = 1.0f,
914                 SizeHeight = 0.0f,
915                 PositionUsesPivotPoint = true,
916                 ParentOrigin = NUI.ParentOrigin.TopCenter,
917                 PivotPoint = NUI.PivotPoint.TopCenter,
918             };
919             bottomOverShootingShadowView = new View
920             {
921                 BackgroundImage = FrameworkInformation.ResourcePath + "nui_component_default_scroll_over_shooting_bottom.png",
922                 Opacity = 1.0f,
923                 SizeHeight = 0.0f,
924                 PositionUsesPivotPoint = true,
925                 ParentOrigin = NUI.ParentOrigin.BottomCenter,
926                 PivotPoint = NUI.PivotPoint.BottomCenter,
927             };
928             //Show horizontal shadow on the left (or right) of the scrollable when panning down (or up).
929             leftOverShootingShadowView = new View
930             {
931                 BackgroundImage = FrameworkInformation.ResourcePath + "nui_component_default_scroll_over_shooting_left.png",
932                 Opacity = 1.0f,
933                 SizeWidth = 0.0f,
934                 PositionUsesPivotPoint = true,
935                 ParentOrigin = NUI.ParentOrigin.CenterLeft,
936                 PivotPoint = NUI.PivotPoint.CenterLeft,
937             };
938             rightOverShootingShadowView = new View
939             {
940                 BackgroundImage = FrameworkInformation.ResourcePath + "nui_component_default_scroll_over_shooting_right.png",
941                 Opacity = 1.0f,
942                 SizeWidth = 0.0f,
943                 PositionUsesPivotPoint = true,
944                 ParentOrigin = NUI.ParentOrigin.CenterRight,
945                 PivotPoint = NUI.PivotPoint.CenterRight,
946             };
947
948             AccessibilityManager.Instance.SetAccessibilityAttribute(this, AccessibilityManager.AccessibilityAttribute.Trait, "ScrollableBase");
949
950             SetKeyboardNavigationSupport(true);
951         }
952
953         private bool OnInterruptTouchingChildTouched(object source, View.TouchEventArgs args)
954         {
955             if (args.Touch.GetState(0) == PointStateType.Down)
956             {
957                 if (scrolling && !SnapToPage)
958                 {
959                     StopScroll();
960                 }
961             }
962             return true;
963         }
964
965         private void OnPropertyChanged(object source, PropertyNotification.NotifyEventArgs args)
966         {
967             OnScroll();
968         }
969
970         /// <summary>
971         /// Called after a child has been added to the owning view.
972         /// </summary>
973         /// <param name="view">The child which has been added.</param>
974         /// <since_tizen> 8 </since_tizen>
975         public override void Add(View view)
976         {
977             ContentContainer.Add(view);
978         }
979
980         /// <summary>
981         /// Called after a child has been removed from the owning view.
982         /// </summary>
983         /// <param name="view">The child which has been removed.</param>
984         /// <since_tizen> 8 </since_tizen>
985         public override void Remove(View view)
986         {
987             if (SnapToPage && CurrentPage == Children.IndexOf(view) && CurrentPage == Children.Count - 1 && Children.Count > 1)
988             {
989                 // Target View is current page and also last child.
990                 // CurrentPage should be changed to previous page.
991                 ScrollToIndex(CurrentPage - 1);
992             }
993
994             ContentContainer.Remove(view);
995         }
996
997         private void OnScrollingChildRelayout(object source, EventArgs args)
998         {
999             // Size is changed. Calculate maxScrollDistance.
1000             bool isSizeChanged = previousContainerSize.Width != ContentContainer.Size.Width || previousContainerSize.Height != ContentContainer.Size.Height ||
1001                 previousSize.Width != Size.Width || previousSize.Height != Size.Height;
1002
1003             if (isSizeChanged)
1004             {
1005                 maxScrollDistance = CalculateMaximumScrollDistance();
1006                 if (!ReviseContainerPositionIfNeed())
1007                 {
1008                     UpdateScrollbar();
1009                 }
1010             }
1011
1012             previousContainerSize = new Size(ContentContainer.Size);
1013             previousSize = new Size(Size);
1014         }
1015
1016         private bool ReviseContainerPositionIfNeed()
1017         {
1018             bool isHorizontal = ScrollingDirection == Direction.Horizontal;
1019             float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
1020
1021             if (Math.Abs(currentPosition) > maxScrollDistance)
1022             {
1023                 StopScroll();
1024                 var targetPosition = BoundScrollPosition(-maxScrollDistance);
1025                 if (isHorizontal) ContentContainer.PositionX = targetPosition;
1026                 else ContentContainer.PositionY = targetPosition;
1027                 return true;
1028             }
1029
1030             return false;
1031         }
1032
1033         /// <summary>
1034         /// The composition of a Scrollbar can vary depending on how you use ScrollableBase.
1035         /// Set the composition that will go into the ScrollableBase according to your ScrollableBase.
1036         /// </summary>
1037         /// <since_tizen> 8 </since_tizen>
1038         [EditorBrowsable(EditorBrowsableState.Never)]
1039         protected virtual void SetScrollbar()
1040         {
1041             if (Scrollbar)
1042             {
1043                 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
1044                 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
1045                 float viewportLength = isHorizontal ? Size.Width : Size.Height;
1046                 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
1047                 Scrollbar.Initialize(contentLength, viewportLength, -currentPosition, isHorizontal);
1048             }
1049         }
1050
1051         /// Update scrollbar position and size.
1052         [EditorBrowsable(EditorBrowsableState.Never)]
1053         protected virtual void UpdateScrollbar()
1054         {
1055             if (Scrollbar)
1056             {
1057                 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
1058                 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
1059                 float viewportLength = isHorizontal ? Size.Width : Size.Height;
1060                 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
1061                 Scrollbar.Update(contentLength, viewportLength, -currentPosition);
1062
1063                 if (!hideScrollbar && fadeScrollbar)
1064                 {
1065                     Scrollbar.FadeOut();
1066                 }
1067             }
1068         }
1069
1070         /// <summary>
1071         /// Scrolls to the item at the specified index.
1072         /// </summary>
1073         /// <param name="index">Index of item.</param>
1074         /// <since_tizen> 8 </since_tizen>
1075         public void ScrollToIndex(int index)
1076         {
1077             if (ContentContainer.ChildCount - 1 < index || index < 0)
1078             {
1079                 return;
1080             }
1081
1082             if (SnapToPage)
1083             {
1084                 CurrentPage = index;
1085             }
1086
1087             float targetPosition = Math.Min(ScrollingDirection == Direction.Vertical ? Children[index].Position.Y : Children[index].Position.X, maxScrollDistance);
1088             AnimateChildTo(ScrollDuration, -targetPosition);
1089         }
1090
1091         internal void ScrollToChild(View child, bool anim = false)
1092         {
1093             if (null == FindDescendantByID(child.ID)) return;
1094
1095             bool isHorizontal = (ScrollingDirection == Direction.Horizontal);
1096
1097             float viewScreenPosition = (isHorizontal? ScreenPosition.X : ScreenPosition.Y);
1098             float childScreenPosition = (isHorizontal? child.ScreenPosition.X : child.ScreenPosition.Y);
1099             float scrollPosition = (isHorizontal? ScrollPosition.X : ScrollPosition.Y);
1100             float viewSize = (isHorizontal? SizeWidth : SizeHeight);
1101             float childSize = (isHorizontal? child.SizeWidth : child.SizeHeight);
1102
1103             if (viewScreenPosition > childScreenPosition ||
1104                 viewScreenPosition + viewSize < childScreenPosition + childSize)
1105             {// if object is outside
1106                 float targetPosition;
1107                 float dist = viewScreenPosition - childScreenPosition;
1108                 if (dist > 0)
1109                 {// if object is upper side
1110                     targetPosition = scrollPosition - dist;
1111                 }
1112                 else
1113                 {// if object is down side
1114                     targetPosition = scrollPosition - dist + childSize - viewSize;
1115                 }
1116                 ScrollTo(targetPosition, anim);
1117             }
1118         }
1119
1120         private void OnScrollDragStarted()
1121         {
1122             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
1123             ScrollDragStarted?.Invoke(this, eventArgs);
1124
1125             if (!hideScrollbar && fadeScrollbar)
1126             {
1127                 scrollBar?.FadeIn();
1128             }
1129         }
1130
1131         private void OnScrollDragEnded()
1132         {
1133             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
1134             ScrollDragEnded?.Invoke(this, eventArgs);
1135
1136             if (!hideScrollbar && fadeScrollbar)
1137             {
1138                 scrollBar?.FadeOut();
1139             }
1140         }
1141
1142         private void OnScrollAnimationStarted()
1143         {
1144             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
1145             ScrollAnimationStarted?.Invoke(this, eventArgs);
1146
1147             if (!hideScrollbar && fadeScrollbar)
1148             {
1149                 scrollBar?.FadeIn();
1150             }
1151         }
1152
1153         private void OnScrollAnimationEnded()
1154         {
1155             scrolling = false;
1156             this.InterceptTouchEvent -= OnInterruptTouchingChildTouched;
1157
1158             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
1159             ScrollAnimationEnded?.Invoke(this, eventArgs);
1160
1161             if (!hideScrollbar && fadeScrollbar)
1162             {
1163                 scrollBar?.FadeOut();
1164             }
1165         }
1166
1167         private void OnScroll()
1168         {
1169             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
1170             Scrolling?.Invoke(this, eventArgs);
1171
1172             bool isHorizontal = ScrollingDirection == Direction.Horizontal;
1173             float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
1174             float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
1175
1176             scrollBar?.Update(contentLength, Math.Abs(currentPosition));
1177             CheckPreReachedTargetPosition();
1178         }
1179
1180         private void CheckPreReachedTargetPosition()
1181         {
1182             // Check whether we reached pre-reached target position
1183             if (readyToNotice &&
1184                 ContentContainer.CurrentPosition.Y <= finalTargetPosition + NoticeAnimationEndBeforePosition &&
1185                 ContentContainer.CurrentPosition.Y >= finalTargetPosition - NoticeAnimationEndBeforePosition)
1186             {
1187                 //Notice first
1188                 readyToNotice = false;
1189                 OnPreReachedTargetPosition(finalTargetPosition);
1190             }
1191         }
1192
1193         /// <summary>
1194         /// This helps developer who wants to know before scroll is reaching target position.
1195         /// </summary>
1196         /// <param name="targetPosition">Index of item.</param>
1197         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
1198         [EditorBrowsable(EditorBrowsableState.Never)]
1199         protected virtual void OnPreReachedTargetPosition(float targetPosition)
1200         {
1201
1202         }
1203
1204         private void StopScroll()
1205         {
1206             if (scrollAnimation != null)
1207             {
1208                 if (scrollAnimation.State == Animation.States.Playing)
1209                 {
1210                     Debug.WriteLineIf(LayoutDebugScrollableBase, "StopScroll Animation Playing");
1211                     scrollAnimation.Stop(Animation.EndActions.Cancel);
1212                     OnScrollAnimationEnded();
1213                 }
1214                 scrollAnimation.Clear();
1215             }
1216         }
1217
1218         private void AnimateChildTo(int duration, float axisPosition)
1219         {
1220             Debug.WriteLineIf(LayoutDebugScrollableBase, "AnimationTo Animation Duration:" + duration + " Destination:" + axisPosition);
1221             finalTargetPosition = axisPosition;
1222
1223             StopScroll(); // Will replace previous animation so will stop existing one.
1224
1225             if (scrollAnimation == null)
1226             {
1227                 scrollAnimation = new Animation();
1228                 scrollAnimation.Finished += ScrollAnimationFinished;
1229             }
1230
1231             scrollAnimation.Duration = duration;
1232             scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSquare);
1233             scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", axisPosition, ScrollAlphaFunction);
1234             scrolling = true;
1235             OnScrollAnimationStarted();
1236             scrollAnimation.Play();
1237         }
1238
1239         /// <summary>
1240         /// Scroll to specific position with or without animation.
1241         /// </summary>
1242         /// <param name="position">Destination.</param>
1243         /// <param name="animate">Scroll with or without animation</param>
1244         /// <since_tizen> 8 </since_tizen>
1245         public void ScrollTo(float position, bool animate)
1246         {
1247             StopScroll();
1248             float currentPositionX = ContentContainer.CurrentPosition.X != 0 ? ContentContainer.CurrentPosition.X : ContentContainer.Position.X;
1249             float currentPositionY = ContentContainer.CurrentPosition.Y != 0 ? ContentContainer.CurrentPosition.Y : ContentContainer.Position.Y;
1250             float delta = ScrollingDirection == Direction.Horizontal ? currentPositionX : currentPositionY;
1251             // The argument position is the new pan position. So the new position of ScrollableBase becomes (-position).
1252             // To move ScrollableBase's position to (-position), it moves by (-position - currentPosition).
1253             delta = -position - delta;
1254
1255             ScrollBy(delta, animate);
1256         }
1257
1258         private float BoundScrollPosition(float targetPosition)
1259         {
1260             if (ScrollAvailableArea != null)
1261             {
1262                 float minScrollPosition = ScrollAvailableArea.X;
1263                 float maxScrollPosition = ScrollAvailableArea.Y;
1264
1265                 targetPosition = Math.Min(-minScrollPosition, targetPosition);
1266                 targetPosition = Math.Max(-maxScrollPosition, targetPosition);
1267             }
1268             else
1269             {
1270                 targetPosition = Math.Min(0, targetPosition);
1271                 targetPosition = Math.Max(-maxScrollDistance, targetPosition);
1272             }
1273
1274             return targetPosition;
1275         }
1276
1277         private void ScrollBy(float displacement, bool animate)
1278         {
1279             if (GetChildCount() == 0 || maxScrollDistance < 0)
1280             {
1281                 return;
1282             }
1283
1284             float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? ContentContainer.PositionX : ContentContainer.PositionY;
1285
1286             Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy childCurrentPosition:" + childCurrentPosition +
1287                 " displacement:" + displacement,
1288                 " maxScrollDistance:" + maxScrollDistance);
1289
1290             childTargetPosition = childCurrentPosition + displacement; // child current position + gesture displacement
1291
1292             Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition);
1293
1294             if (animate)
1295             {
1296                 // Calculate scroll animation duration
1297                 float scrollDistance = Math.Abs(displacement);
1298                 readyToNotice = true;
1299
1300                 AnimateChildTo(ScrollDuration, BoundScrollPosition(AdjustTargetPositionOfScrollAnimation(BoundScrollPosition(childTargetPosition))));
1301             }
1302             else
1303             {
1304                 StopScroll();
1305                 finalTargetPosition = BoundScrollPosition(childTargetPosition);
1306
1307                 // Set position of scrolling child without an animation
1308                 if (ScrollingDirection == Direction.Horizontal)
1309                 {
1310                     ContentContainer.PositionX = finalTargetPosition;
1311                 }
1312                 else
1313                 {
1314                     ContentContainer.PositionY = finalTargetPosition;
1315                 }
1316             }
1317         }
1318
1319         /// <summary>
1320         /// you can override it to clean-up your own resources.
1321         /// </summary>
1322         /// <param name="type">DisposeTypes</param>
1323         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
1324         [EditorBrowsable(EditorBrowsableState.Never)]
1325         protected override void Dispose(DisposeTypes type)
1326         {
1327             if (disposed)
1328             {
1329                 return;
1330             }
1331
1332             if (type == DisposeTypes.Explicit)
1333             {
1334                 StopOverShootingShadowAnimation();
1335                 StopScroll();
1336
1337                 if (mPanGestureDetector != null)
1338                 {
1339                     mPanGestureDetector.Detected -= OnPanGestureDetected;
1340                     mPanGestureDetector.Dispose();
1341                     mPanGestureDetector = null;
1342                 }
1343
1344                 propertyNotification.Dispose();
1345             }
1346             base.Dispose(type);
1347         }
1348
1349         private float CalculateMaximumScrollDistance()
1350         {
1351             float scrollingChildLength = 0;
1352             float scrollerLength = 0;
1353             if (ScrollingDirection == Direction.Horizontal)
1354             {
1355                 Debug.WriteLineIf(LayoutDebugScrollableBase, "Horizontal");
1356
1357                 scrollingChildLength = ContentContainer.Size.Width;
1358                 scrollerLength = Size.Width;
1359             }
1360             else
1361             {
1362                 Debug.WriteLineIf(LayoutDebugScrollableBase, "Vertical");
1363                 scrollingChildLength = ContentContainer.Size.Height;
1364                 scrollerLength = Size.Height;
1365             }
1366
1367             Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy maxScrollDistance:" + (scrollingChildLength - scrollerLength) +
1368                 " parent length:" + scrollerLength +
1369                 " scrolling child length:" + scrollingChildLength);
1370
1371             return Math.Max(scrollingChildLength - scrollerLength, 0);
1372         }
1373
1374         private void PageSnap(float velocity)
1375         {
1376             float destination;
1377
1378             Debug.WriteLineIf(LayoutDebugScrollableBase, "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan +
1379                 " currentPage[" + CurrentPage + "]");
1380
1381             //Increment current page if total displacement enough to warrant a page change.
1382             if (Math.Abs(totalDisplacementForPan) > (mPageWidth * ratioOfScreenWidthToCompleteScroll))
1383             {
1384                 if (totalDisplacementForPan < 0)
1385                 {
1386                     CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
1387                 }
1388                 else
1389                 {
1390                     CurrentPage = Math.Max(0, --CurrentPage);
1391                 }
1392             }
1393             else if (Math.Abs(velocity) > PageFlickThreshold)
1394             {
1395                 if (velocity < 0)
1396                 {
1397                     CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
1398                 }
1399                 else
1400                 {
1401                     CurrentPage = Math.Max(0, --CurrentPage);
1402                 }
1403             }
1404
1405             // Animate to new page or reposition to current page
1406             if (ScrollingDirection == Direction.Horizontal)
1407                 destination = -(Children[CurrentPage].Position.X + Children[CurrentPage].CurrentSize.Width / 2 - CurrentSize.Width / 2); // set to middle of current page
1408             else
1409                 destination = -(Children[CurrentPage].Position.Y + Children[CurrentPage].CurrentSize.Height / 2 - CurrentSize.Height / 2);
1410
1411             AnimateChildTo(ScrollDuration, destination);
1412         }
1413
1414         /// <summary>
1415         /// Enable/Disable overshooting effect. default is disabled.
1416         /// </summary>
1417         [EditorBrowsable(EditorBrowsableState.Never)]
1418         public bool EnableOverShootingEffect
1419         {
1420             get
1421             {
1422                 return (bool)GetValue(EnableOverShootingEffectProperty);
1423             }
1424             set
1425             {
1426                 SetValue(EnableOverShootingEffectProperty, value);
1427                 NotifyPropertyChanged();
1428             }
1429         }
1430         private bool InternalEnableOverShootingEffect { get; set; } = false;
1431
1432         private void AttachOverShootingShadowView()
1433         {
1434             if (!EnableOverShootingEffect)
1435                 return;
1436
1437             // stop animation if necessary.
1438             StopOverShootingShadowAnimation();
1439
1440             if (ScrollingDirection == Direction.Horizontal)
1441             {
1442                 base.Add(leftOverShootingShadowView);
1443                 base.Add(rightOverShootingShadowView);
1444
1445                 leftOverShootingShadowView.Size = new Size(0.0f, SizeHeight);
1446                 leftOverShootingShadowView.Opacity = 1.0f;
1447                 leftOverShootingShadowView.RaiseToTop();
1448
1449                 rightOverShootingShadowView.Size = new Size(0.0f, SizeHeight);
1450                 rightOverShootingShadowView.Opacity = 1.0f;
1451                 rightOverShootingShadowView.RaiseToTop();
1452             }
1453             else
1454             {
1455                 base.Add(topOverShootingShadowView);
1456                 base.Add(bottomOverShootingShadowView);
1457
1458                 topOverShootingShadowView.Size = new Size(SizeWidth, 0.0f);
1459                 topOverShootingShadowView.Opacity = 1.0f;
1460                 topOverShootingShadowView.RaiseToTop();
1461
1462                 bottomOverShootingShadowView.Size = new Size(SizeWidth, 0.0f);
1463                 bottomOverShootingShadowView.Opacity = 1.0f;
1464                 bottomOverShootingShadowView.RaiseToTop();
1465             }
1466
1467             // at the beginning, height or width of overshooting shadow is 0, so it is invisible.
1468             isOverShootingShadowShown = false;
1469         }
1470
1471         private void DragOverShootingShadow(float totalPanDisplacement, float panDisplacement)
1472         {
1473             if (!EnableOverShootingEffect)
1474                 return;
1475
1476             if (totalPanDisplacement > 0) // downwards
1477             {
1478                 // check if reaching at the top / left.
1479                 if ((int)finalTargetPosition != 0)
1480                 {
1481                     isOverShootingShadowShown = false;
1482                     return;
1483                 }
1484
1485                 // save start displacement, and re-calculate displacement.
1486                 if (!isOverShootingShadowShown)
1487                 {
1488                     startShowShadowDisplacement = totalPanDisplacement;
1489                 }
1490                 isOverShootingShadowShown = true;
1491
1492                 float newDisplacement = (int)totalPanDisplacement < (int)startShowShadowDisplacement ? 0 : totalPanDisplacement - startShowShadowDisplacement;
1493
1494                 if (ScrollingDirection == Direction.Horizontal)
1495                 {
1496                     // scale limit of height is 60%.
1497                     float heightScale = newDisplacement / overShootingShadowScaleHeightLimit;
1498                     leftOverShootingShadowView.SizeHeight = heightScale > 0.6f ? SizeHeight * 0.4f : SizeHeight * (1.0f - heightScale);
1499
1500                     // scale limit of width is 300%.
1501                     leftOverShootingShadowView.SizeWidth = newDisplacement > overShootingShadowScaleHeightLimit ? overShootingShadowScaleHeightLimit : newDisplacement;
1502
1503                     // trigger event
1504                     ScrollOutOfBoundEventArgs.Direction scrollDirection = panDisplacement > 0 ?
1505                        ScrollOutOfBoundEventArgs.Direction.Right : ScrollOutOfBoundEventArgs.Direction.Left;
1506                     OnScrollOutOfBound(scrollDirection, totalPanDisplacement);
1507                 }
1508                 else
1509                 {
1510                     // scale limit of width is 60%.
1511                     float widthScale = newDisplacement / overShootingShadowScaleHeightLimit;
1512                     topOverShootingShadowView.SizeWidth = widthScale > 0.6f ? SizeWidth * 0.4f : SizeWidth * (1.0f - widthScale);
1513
1514                     // scale limit of height is 300%.
1515                     topOverShootingShadowView.SizeHeight = newDisplacement > overShootingShadowScaleHeightLimit ? overShootingShadowScaleHeightLimit : newDisplacement;
1516
1517                     // trigger event
1518                     ScrollOutOfBoundEventArgs.Direction scrollDirection = panDisplacement > 0 ?
1519                        ScrollOutOfBoundEventArgs.Direction.Down : ScrollOutOfBoundEventArgs.Direction.Up;
1520                     OnScrollOutOfBound(scrollDirection, totalPanDisplacement);
1521                 }
1522             }
1523             else if (totalPanDisplacement < 0) // upwards
1524             {
1525                 // check if reaching at the bottom.
1526                 if (-(int)finalTargetPosition != (int)maxScrollDistance)
1527                 {
1528                     isOverShootingShadowShown = false;
1529                     return;
1530                 }
1531
1532                 // save start displacement, and re-calculate displacement.
1533                 if (!isOverShootingShadowShown)
1534                 {
1535                     startShowShadowDisplacement = totalPanDisplacement;
1536                 }
1537                 isOverShootingShadowShown = true;
1538
1539                 float newDisplacement = (int)startShowShadowDisplacement < (int)totalPanDisplacement ? 0 : startShowShadowDisplacement - totalPanDisplacement;
1540
1541                 if (ScrollingDirection == Direction.Horizontal)
1542                 {
1543                     // scale limit of height is 60%.
1544                     float heightScale = newDisplacement / overShootingShadowScaleHeightLimit;
1545                     rightOverShootingShadowView.SizeHeight = heightScale > 0.6f ? SizeHeight * 0.4f : SizeHeight * (1.0f - heightScale);
1546
1547                     // scale limit of width is 300%.
1548                     rightOverShootingShadowView.SizeWidth = newDisplacement > overShootingShadowScaleHeightLimit ? overShootingShadowScaleHeightLimit : newDisplacement;
1549
1550                     // trigger event
1551                     ScrollOutOfBoundEventArgs.Direction scrollDirection = panDisplacement > 0 ?
1552                        ScrollOutOfBoundEventArgs.Direction.Right : ScrollOutOfBoundEventArgs.Direction.Left;
1553                     OnScrollOutOfBound(scrollDirection, totalPanDisplacement);
1554                 }
1555                 else
1556                 {
1557                     // scale limit of width is 60%.
1558                     float widthScale = newDisplacement / overShootingShadowScaleHeightLimit;
1559                     bottomOverShootingShadowView.SizeWidth = widthScale > 0.6f ? SizeWidth * 0.4f : SizeWidth * (1.0f - widthScale);
1560
1561                     // scale limit of height is 300%.
1562                     bottomOverShootingShadowView.SizeHeight = newDisplacement > overShootingShadowScaleHeightLimit ? overShootingShadowScaleHeightLimit : newDisplacement;
1563
1564                     // trigger event
1565                     ScrollOutOfBoundEventArgs.Direction scrollDirection = panDisplacement > 0 ?
1566                        ScrollOutOfBoundEventArgs.Direction.Down : ScrollOutOfBoundEventArgs.Direction.Up;
1567                     OnScrollOutOfBound(scrollDirection, totalPanDisplacement);
1568                 }
1569             }
1570             else
1571             {
1572                 // if total displacement is 0, shadow would become invisible.
1573                 isOverShootingShadowShown = false;
1574             }
1575         }
1576
1577         private void PlayOverShootingShadowAnimation()
1578         {
1579             if (!EnableOverShootingEffect)
1580                 return;
1581
1582             // stop animation if necessary.
1583             StopOverShootingShadowAnimation();
1584
1585             if (overShootingShadowAnimation == null)
1586             {
1587                 overShootingShadowAnimation = new Animation(overShootingShadowAnimationDuration);
1588                 overShootingShadowAnimation.Finished += OnOverShootingShadowAnimationFinished;
1589             }
1590
1591             if (ScrollingDirection == Direction.Horizontal)
1592             {
1593                 View targetView = totalDisplacementForPan < 0 ? rightOverShootingShadowView : leftOverShootingShadowView;
1594                 overShootingShadowAnimation.AnimateTo(targetView, "SizeHeight", SizeHeight);
1595                 overShootingShadowAnimation.AnimateTo(targetView, "SizeWidth", 0.0f);
1596                 overShootingShadowAnimation.AnimateTo(targetView, "Opacity", 0.0f);
1597             }
1598             else
1599             {
1600                 View targetView = totalDisplacementForPan < 0 ? bottomOverShootingShadowView : topOverShootingShadowView;
1601                 overShootingShadowAnimation.AnimateTo(targetView, "SizeWidth", SizeWidth);
1602                 overShootingShadowAnimation.AnimateTo(targetView, "SizeHeight", 0.0f);
1603                 overShootingShadowAnimation.AnimateTo(targetView, "Opacity", 0.0f);
1604             }
1605             overShootingShadowAnimation.Play();
1606         }
1607
1608         private void StopOverShootingShadowAnimation()
1609         {
1610             if (overShootingShadowAnimation == null || overShootingShadowAnimation.State != Animation.States.Playing)
1611                 return;
1612
1613             overShootingShadowAnimation.Stop(Animation.EndActions.Cancel);
1614             OnOverShootingShadowAnimationFinished(null, null);
1615             overShootingShadowAnimation.Clear();
1616         }
1617
1618         private void OnOverShootingShadowAnimationFinished(object sender, EventArgs e)
1619         {
1620             if (ScrollingDirection == Direction.Horizontal)
1621             {
1622                 base.Remove(leftOverShootingShadowView);
1623                 base.Remove(rightOverShootingShadowView);
1624
1625                 leftOverShootingShadowView.Size = new Size(0.0f, SizeHeight);
1626                 rightOverShootingShadowView.Size = new Size(0.0f, SizeHeight);
1627             }
1628             else
1629             {
1630                 base.Remove(topOverShootingShadowView);
1631                 base.Remove(bottomOverShootingShadowView);
1632
1633                 topOverShootingShadowView.Size = new Size(SizeWidth, 0.0f);
1634                 bottomOverShootingShadowView.Size = new Size(SizeWidth, 0.0f);
1635             }
1636
1637             // after animation finished, height/width & opacity of vertical shadow both are 0, so it is invisible.
1638             isOverShootingShadowShown = false;
1639         }
1640
1641         private void OnScrollOutOfBound(ScrollOutOfBoundEventArgs.Direction direction, float displacement)
1642         {
1643             ScrollOutOfBoundEventArgs args = new ScrollOutOfBoundEventArgs(direction, displacement);
1644             ScrollOutOfBound?.Invoke(this, args);
1645         }
1646
1647         private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
1648         {
1649             e.Handled = OnPanGesture(e.PanGesture);
1650         }
1651
1652         private bool OnPanGesture(PanGesture panGesture)
1653         {
1654             bool handled = true;
1655             if (SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
1656             {
1657                 return handled;
1658             }
1659             if (panGesture.State == Gesture.StateType.Started)
1660             {
1661                 readyToNotice = false;
1662                 AttachOverShootingShadowView();
1663                 Debug.WriteLineIf(LayoutDebugScrollableBase, "Gesture Start");
1664                 if (scrolling && !SnapToPage)
1665                 {
1666                     StopScroll();
1667                 }
1668                 totalDisplacementForPan = 0.0f;
1669
1670                 // check if gesture need to propagation
1671                 var checkDisplacement = (ScrollingDirection == Direction.Horizontal) ? panGesture.Displacement.X : panGesture.Displacement.Y;
1672                 var checkChildCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? ContentContainer.PositionX : ContentContainer.PositionY;
1673                 var checkChildTargetPosition = checkChildCurrentPosition + checkDisplacement;
1674                 var checkFinalTargetPosition = BoundScrollPosition(checkChildTargetPosition);
1675                 handled = !((int)checkFinalTargetPosition == 0 || -(int)checkFinalTargetPosition == (int)maxScrollDistance);
1676                 // If you propagate a gesture event, return;
1677                 if(!handled)
1678                 {
1679                     return handled;
1680                 }
1681
1682                 //Interrupt touching when panning is started
1683                 this.InterceptTouchEvent += OnInterruptTouchingChildTouched;
1684                 OnScrollDragStarted();
1685             }
1686             else if (panGesture.State == Gesture.StateType.Continuing)
1687             {
1688                 if (ScrollingDirection == Direction.Horizontal)
1689                 {
1690                     // if vertical shadow is shown, does not scroll.
1691                     if (!isOverShootingShadowShown)
1692                     {
1693                         ScrollBy(panGesture.Displacement.X, false);
1694                     }
1695                     totalDisplacementForPan += panGesture.Displacement.X;
1696                     DragOverShootingShadow(totalDisplacementForPan, panGesture.Displacement.X);
1697                 }
1698                 else
1699                 {
1700                     // if vertical shadow is shown, does not scroll.
1701                     if (!isOverShootingShadowShown)
1702                     {
1703                         ScrollBy(panGesture.Displacement.Y, false);
1704                     }
1705                     totalDisplacementForPan += panGesture.Displacement.Y;
1706                     DragOverShootingShadow(totalDisplacementForPan, panGesture.Displacement.Y);
1707                 }
1708                 Debug.WriteLineIf(LayoutDebugScrollableBase, "OnPanGestureDetected Continue totalDisplacementForPan:" + totalDisplacementForPan);
1709
1710             }
1711             else if (panGesture.State == Gesture.StateType.Finished || panGesture.State == Gesture.StateType.Cancelled)
1712             {
1713                 PlayOverShootingShadowAnimation();
1714                 OnScrollDragEnded();
1715                 StopScroll(); // Will replace previous animation so will stop existing one.
1716
1717                 if (scrollAnimation == null)
1718                 {
1719                     scrollAnimation = new Animation();
1720                     scrollAnimation.Finished += ScrollAnimationFinished;
1721                 }
1722
1723                 float panVelocity = (ScrollingDirection == Direction.Horizontal) ? panGesture.Velocity.X : panGesture.Velocity.Y;
1724
1725                 if (SnapToPage)
1726                 {
1727                     PageSnap(panVelocity);
1728                 }
1729                 else
1730                 {
1731                     if (panVelocity == 0)
1732                     {
1733                         float currentScrollPosition = (ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
1734                         scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
1735                         scrollAnimation.Duration = 0;
1736                         scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", currentScrollPosition);
1737                         scrollAnimation.Play();
1738                     }
1739                     else
1740                     {
1741                         Decelerating(panVelocity, scrollAnimation);
1742                     }
1743                 }
1744
1745                 totalDisplacementForPan = 0;
1746                 scrolling = true;
1747                 readyToNotice = true;
1748                 OnScrollAnimationStarted();
1749             }
1750             return handled;
1751         }
1752
1753         internal void BaseRemove(View view)
1754         {
1755             base.Remove(view);
1756         }
1757
1758         internal override bool OnAccessibilityPan(PanGesture gestures)
1759         {
1760             if (SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
1761             {
1762                 return false;
1763             }
1764
1765             OnPanGesture(gestures);
1766             return true;
1767         }
1768
1769         private float CustomScrollAlphaFunction(float progress)
1770         {
1771             if (panAnimationDelta == 0)
1772             {
1773                 return 1.0f;
1774             }
1775             else
1776             {
1777                 // Parameter "progress" is normalized value. We need to multiply target duration to calculate distance.
1778                 // Can get real distance using equation of deceleration (check Decelerating function)
1779                 // After get real distance, normalize it
1780                 float realDuration = progress * panAnimationDuration;
1781                 float realDistance = velocityOfLastPan * ((float)Math.Pow(decelerationRate, realDuration) - 1) / logValueOfDeceleration;
1782                 float result = Math.Min(realDistance / Math.Abs(panAnimationDelta), 1.0f);
1783                 return result;
1784             }
1785         }
1786
1787         /// <summary>
1788         /// you can override it to custom your decelerating
1789         /// </summary>
1790         /// <param name="velocity">Velocity of current pan.</param>
1791         /// <param name="animation">Scroll animation.</param>
1792         [EditorBrowsable(EditorBrowsableState.Never)]
1793         protected virtual void Decelerating(float velocity, Animation animation)
1794         {
1795             if (animation == null) throw new ArgumentNullException(nameof(animation));
1796             // Decelerating using deceleration equation ===========
1797             //
1798             // V   : velocity (pixel per millisecond)
1799             // V0  : initial velocity
1800             // d   : deceleration rate,
1801             // t   : time
1802             // X   : final position after decelerating
1803             // log : natural logarithm
1804             //
1805             // V(t) = V0 * d pow t;
1806             // X(t) = V0 * (d pow t - 1) / log d;  <-- Integrate the velocity function
1807             // X(∞) = V0 * d / (1 - d); <-- Result using infinite T can be final position because T is tending to infinity.
1808             //
1809             // Because of final T is tending to infinity, we should use threshold value to finish.
1810             // Final T = log(-threshold * log d / |V0| ) / log d;
1811
1812             velocityOfLastPan = Math.Abs(velocity);
1813
1814             float currentScrollPosition = -(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
1815             panAnimationDelta = (velocityOfLastPan * decelerationRate) / (1 - decelerationRate);
1816             panAnimationDelta = velocity > 0 ? -panAnimationDelta : panAnimationDelta;
1817
1818             float destination = -(panAnimationDelta + currentScrollPosition);
1819             float adjustDestination = AdjustTargetPositionOfScrollAnimation(destination);
1820             float maxPosition = ScrollAvailableArea != null ? ScrollAvailableArea.Y : maxScrollDistance;
1821             float minPosition = ScrollAvailableArea != null ? ScrollAvailableArea.X : 0;
1822
1823             if (destination < -maxPosition || destination > minPosition)
1824             {
1825                 panAnimationDelta = velocity > 0 ? (currentScrollPosition - minPosition) : (maxPosition - currentScrollPosition);
1826                 destination = velocity > 0 ? minPosition : -maxPosition;
1827
1828                 if (panAnimationDelta == 0)
1829                 {
1830                     panAnimationDuration = 0.0f;
1831                 }
1832                 else
1833                 {
1834                     panAnimationDuration = (float)Math.Log((panAnimationDelta * logValueOfDeceleration / velocityOfLastPan + 1), decelerationRate);
1835                 }
1836
1837                 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1838                     "OverRange======================= \n" +
1839                     "[decelerationRate] " + decelerationRate + "\n" +
1840                     "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1841                     "[Velocity] " + velocityOfLastPan + "\n" +
1842                     "[CurrentPosition] " + currentScrollPosition + "\n" +
1843                     "[CandidateDelta] " + panAnimationDelta + "\n" +
1844                     "[Destination] " + destination + "\n" +
1845                     "[Duration] " + panAnimationDuration + "\n" +
1846                     "================================ \n"
1847                 );
1848             }
1849             else
1850             {
1851                 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1852
1853                 if (adjustDestination != destination)
1854                 {
1855                     destination = adjustDestination;
1856                     panAnimationDelta = destination + currentScrollPosition;
1857                     velocityOfLastPan = Math.Abs(panAnimationDelta * logValueOfDeceleration / ((float)Math.Pow(decelerationRate, panAnimationDuration) - 1));
1858                     panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1859                 }
1860
1861                 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1862                     "================================ \n" +
1863                     "[decelerationRate] " + decelerationRate + "\n" +
1864                     "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1865                     "[Velocity] " + velocityOfLastPan + "\n" +
1866                     "[CurrentPosition] " + currentScrollPosition + "\n" +
1867                     "[CandidateDelta] " + panAnimationDelta + "\n" +
1868                     "[Destination] " + destination + "\n" +
1869                     "[Duration] " + panAnimationDuration + "\n" +
1870                     "================================ \n"
1871                 );
1872             }
1873
1874             finalTargetPosition = destination;
1875
1876             customScrollAlphaFunction = new UserAlphaFunctionDelegate(CustomScrollAlphaFunction);
1877             animation.DefaultAlphaFunction = new AlphaFunction(customScrollAlphaFunction);
1878             GC.KeepAlive(customScrollAlphaFunction);
1879             animation.Duration = (int)panAnimationDuration;
1880             animation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", destination);
1881             animation.Play();
1882         }
1883
1884         private void ScrollAnimationFinished(object sender, EventArgs e)
1885         {
1886             OnScrollAnimationEnded();
1887         }
1888
1889         /// <summary>
1890         /// Adjust scrolling position by own scrolling rules.
1891         /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1892         /// </summary>
1893         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
1894         [EditorBrowsable(EditorBrowsableState.Never)]
1895         protected virtual float AdjustTargetPositionOfScrollAnimation(float position)
1896         {
1897             return position;
1898         }
1899
1900         /// <summary>
1901         /// Scroll position given to ScrollTo.
1902         /// This is the position in the opposite direction to the position of ContentContainer.
1903         /// </summary>
1904         /// <since_tizen> 8 </since_tizen>
1905         public Position ScrollPosition
1906         {
1907             get
1908             {
1909                 return new Position(-ContentContainer.Position);
1910             }
1911         }
1912
1913         /// <summary>
1914         /// Current scroll position in the middle of ScrollTo animation.
1915         /// This is the position in the opposite direction to the current position of ContentContainer.
1916         /// </summary>
1917         /// <since_tizen> 8 </since_tizen>
1918         public Position ScrollCurrentPosition
1919         {
1920             get
1921             {
1922                 return new Position(-ContentContainer.CurrentPosition);
1923             }
1924         }
1925
1926         /// <summary>
1927         /// Remove all children in ContentContainer.
1928         /// </summary>
1929         /// <param name="dispose">If true, removed child is disposed.</param>
1930         [EditorBrowsable(EditorBrowsableState.Never)]
1931         public void RemoveAllChildren(bool dispose = false)
1932         {
1933             RecursiveRemoveChildren(ContentContainer, dispose);
1934         }
1935
1936         private void RecursiveRemoveChildren(View parent, bool dispose)
1937         {
1938             if (parent == null)
1939             {
1940                 return;
1941             }
1942             int maxChild = (int)parent.GetChildCount();
1943             for (int i = maxChild - 1; i >= 0; --i)
1944             {
1945                 View child = parent.GetChildAt((uint)i);
1946                 if (child == null)
1947                 {
1948                     continue;
1949                 }
1950                 RecursiveRemoveChildren(child, dispose);
1951                 parent.Remove(child);
1952                 if (dispose)
1953                 {
1954                     child.Dispose();
1955                 }
1956             }
1957         }
1958
1959         internal bool IsChildNearlyVisble(View child, float offset = 0)
1960         {
1961             if (ScreenPosition.X - offset < child.ScreenPosition.X + child.SizeWidth &&
1962                 ScreenPosition.X + SizeWidth + offset > child.ScreenPosition.X &&
1963                 ScreenPosition.Y - offset < child.ScreenPosition.Y + child.SizeHeight &&
1964                 ScreenPosition.Y + SizeHeight + offset > child.ScreenPosition.Y)
1965             {
1966                 return true;
1967             }
1968             else
1969             {
1970                 return false;
1971             }
1972         }
1973
1974
1975         /// <inheritdoc/>
1976         [EditorBrowsable(EditorBrowsableState.Never)]
1977         public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
1978         {
1979             bool isHorizontal = (ScrollingDirection == Direction.Horizontal);
1980             float targetPosition = -(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
1981             float stepDistance = (stepScrollDistance != 0? stepScrollDistance : (isHorizontal ? Size.Width * 0.25f :  Size.Height * 0.25f));
1982
1983             View nextFocusedView = FocusManager.Instance.GetNearestFocusableActor(this, currentFocusedView, direction);
1984
1985             if (nextFocusedView != null)
1986             {
1987                 if (null != FindDescendantByID(nextFocusedView.ID))
1988                 {
1989                     if (IsChildNearlyVisble(nextFocusedView, stepDistance) == true)
1990                     {
1991                         ScrollToChild(nextFocusedView, true);
1992                     }
1993                     else
1994                     {
1995                         if ((isHorizontal && direction == View.FocusDirection.Right) ||
1996                             (!isHorizontal && direction == View.FocusDirection.Down))
1997                         {
1998                             targetPosition += stepDistance;
1999                             targetPosition = targetPosition > maxScrollDistance ? maxScrollDistance : targetPosition;
2000
2001                         }
2002                         else if ((isHorizontal && direction == View.FocusDirection.Left) ||
2003                                  (!isHorizontal && direction == View.FocusDirection.Up))
2004                         {
2005                             targetPosition -= stepDistance;
2006                             targetPosition = targetPosition < 0 ? 0 : targetPosition;
2007                         }
2008
2009                         ScrollTo(targetPosition, true);
2010                     }
2011                 }
2012             }
2013             else
2014             {
2015                 if((isHorizontal && direction == View.FocusDirection.Right) ||
2016                     (!isHorizontal && direction == View.FocusDirection.Down))
2017                 {
2018                     targetPosition += stepDistance;
2019                     targetPosition = targetPosition > maxScrollDistance ? maxScrollDistance : targetPosition;
2020
2021                 }
2022                 else if((isHorizontal && direction == View.FocusDirection.Left) ||
2023                         (!isHorizontal && direction == View.FocusDirection.Up))
2024                 {
2025                     targetPosition -= stepDistance;
2026                     targetPosition = targetPosition < 0 ? 0 : targetPosition;
2027                 }
2028
2029                 ScrollTo(targetPosition, true);
2030
2031                 // End of scroll. escape.
2032                 if ((targetPosition == 0 || targetPosition == maxScrollDistance) == false)
2033                 {
2034                     return this;
2035                 }
2036
2037             }
2038             return nextFocusedView;
2039         }
2040
2041         /// <inheritdoc/>
2042         [EditorBrowsable(EditorBrowsableState.Never)]
2043         protected override bool AccessibilityScrollToChild(View child)
2044         {
2045             if (child == null)
2046             {
2047                 return false;
2048             }
2049
2050             if (ScrollingDirection == Direction.Horizontal)
2051             {
2052                 if (child.ScreenPosition.X + child.Size.Width <= this.ScreenPosition.X)
2053                 {
2054                     if (SnapToPage)
2055                     {
2056                         PageSnap(PageFlickThreshold + 1);
2057                     }
2058                     else
2059                     {
2060                         ScrollTo((float)(child.ScreenPosition.X - ContentContainer.ScreenPosition.X), false);
2061                     }
2062                 }
2063                 else if (child.ScreenPosition.X >= this.ScreenPosition.X + this.Size.Width)
2064                 {
2065                     if (SnapToPage)
2066                     {
2067                         PageSnap(-(PageFlickThreshold + 1));
2068                     }
2069                     else
2070                     {
2071                         ScrollTo((float)(child.ScreenPosition.X + child.Size.Width - ContentContainer.ScreenPosition.X - this.Size.Width), false);
2072                     }
2073                 }
2074             }
2075             else
2076             {
2077                 if (child.ScreenPosition.Y + child.Size.Height <= this.ScreenPosition.Y)
2078                 {
2079                     if (SnapToPage)
2080                     {
2081                         PageSnap(PageFlickThreshold + 1);
2082                     }
2083                     else
2084                     {
2085                         ScrollTo((float)(child.ScreenPosition.Y - ContentContainer.ScreenPosition.Y), false);
2086                     }
2087                 }
2088                 else if (child.ScreenPosition.Y >= this.ScreenPosition.Y + this.Size.Height)
2089                 {
2090                     if (SnapToPage)
2091                     {
2092                         PageSnap(-(PageFlickThreshold + 1));
2093                     }
2094                     else
2095                     {
2096                         ScrollTo((float)(child.ScreenPosition.Y + child.Size.Height - ContentContainer.ScreenPosition.Y - this.Size.Height), false);
2097                     }
2098                 }
2099             }
2100
2101             return true;
2102         }
2103     }
2104
2105 } // namespace