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