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