[NUI] Let scroll again when displacement for OutOfBound effect become 0
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / ScrollableBase.cs
1 /* Copyright (c) 2020 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;
18 using Tizen.NUI.BaseComponents;
19 using System.Collections.Generic;
20 using System.ComponentModel;
21 using System.Diagnostics;
22 using System.Runtime.InteropServices;
23 using Tizen.NUI.Accessibility;
24
25 namespace Tizen.NUI.Components
26 {
27     /// <summary>
28     /// ScrollEventArgs is a class to record scroll event arguments which will sent to user.
29     /// </summary>
30     /// <since_tizen> 8 </since_tizen>
31     public class ScrollEventArgs : EventArgs
32     {
33         private Position position;
34
35         /// <summary>
36         /// Default constructor.
37         /// </summary>
38         /// <param name="position">Current scroll position</param>
39         /// <since_tizen> 8 </since_tizen>
40         public ScrollEventArgs(Position position)
41         {
42             this.position = position;
43         }
44
45         /// <summary>
46         /// Current position of ContentContainer.
47         /// </summary>
48         /// <since_tizen> 8 </since_tizen>
49         public Position Position
50         {
51             get
52             {
53                 return position;
54             }
55         }
56     }
57
58     /// <summary>
59     /// ScrollOutofBoundEventArgs is to record scroll out-of-bound event arguments which will be sent to user.
60     /// </summary>
61     [EditorBrowsable(EditorBrowsableState.Never)]
62     public class ScrollOutOfBoundEventArgs : EventArgs
63     {
64         /// <summary>
65         /// The direction to be touched.
66         /// </summary>
67         [EditorBrowsable(EditorBrowsableState.Never)]
68         public enum Direction
69         {
70             /// <summary>
71             /// Upwards.
72             /// </summary>
73             [EditorBrowsable(EditorBrowsableState.Never)]
74             Up,
75
76             /// <summary>
77             /// Downwards.
78             /// </summary>
79             [EditorBrowsable(EditorBrowsableState.Never)]
80             Down,
81         }
82
83         /// <summary>
84         /// Constructor.
85         /// </summary>
86         /// <param name="direction">Current pan direction</param>
87         /// <param name="displacement">Current total displacement</param>
88         [EditorBrowsable(EditorBrowsableState.Never)]
89         public ScrollOutOfBoundEventArgs(Direction direction, float displacement)
90         {
91             PanDirection = direction;
92             Displacement = displacement;
93         }
94
95         /// <summary>
96         /// Current pan direction of ContentContainer.
97         /// </summary>
98         [EditorBrowsable(EditorBrowsableState.Never)]
99         public Direction PanDirection
100         {
101             get;
102         }
103
104         /// <summary>
105         /// Current total displacement of ContentContainer.
106         /// if its value is greater than 0, it is at the top/left;
107         /// if less than 0, it is at the bottom/right.
108         /// </summary>
109         [EditorBrowsable(EditorBrowsableState.Never)]
110         public float Displacement
111         {
112             get;
113         }
114     }
115
116     /// <summary>
117     /// This class provides a View that can scroll a single View with a layout. This View can be a nest of Views.
118     /// </summary>
119     /// <since_tizen> 8 </since_tizen>
120     public class ScrollableBase : Control
121     {
122         static bool LayoutDebugScrollableBase = false; // Debug flag
123         private Direction mScrollingDirection = Direction.Vertical;
124         private bool mScrollEnabled = true;
125         private int mScrollDuration = 125;
126         private int mPageWidth = 0;
127         private float mPageFlickThreshold = 0.4f;
128         private float mScrollingEventThreshold = 0.001f;
129
130         private class ScrollableBaseCustomLayout : LayoutGroup
131         {
132             protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
133             {
134                 MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK;
135                 MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK;
136
137                 Direction scrollingDirection = Direction.Vertical;
138                 ScrollableBase scrollableBase = this.Owner as ScrollableBase;
139                 if (scrollableBase != null)
140                 {
141                     scrollingDirection = scrollableBase.ScrollingDirection;
142                 }
143
144                 float totalWidth = 0.0f;
145                 float totalHeight = 0.0f;
146
147                 // measure child, should be a single scrolling child
148                 foreach (LayoutItem childLayout in LayoutChildren)
149                 {
150                     if (childLayout != null && childLayout.Owner.Name == "ContentContainer")
151                     {
152                         // Get size of child
153                         // Use an Unspecified MeasureSpecification mode so scrolling child is not restricted to it's parents size in Height (for vertical scrolling)
154                         // or Width for horizontal scrolling
155                         if (scrollingDirection == Direction.Vertical)
156                         {
157                             MeasureSpecification unrestrictedMeasureSpec = new MeasureSpecification(heightMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
158                             MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), unrestrictedMeasureSpec, new LayoutLength(0)); // Height unrestricted by parent
159                         }
160                         else
161                         {
162                             MeasureSpecification unrestrictedMeasureSpec = new MeasureSpecification(widthMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
163                             MeasureChildWithMargins(childLayout, unrestrictedMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0)); // Width unrestricted by parent
164                         }
165
166                         totalWidth = (childLayout.MeasuredWidth.Size + (childLayout.Padding.Start + childLayout.Padding.End)).AsDecimal();
167                         totalHeight = (childLayout.MeasuredHeight.Size + (childLayout.Padding.Top + childLayout.Padding.Bottom)).AsDecimal();
168
169                         if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
170                         {
171                             childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
172                         }
173                         if (childLayout.MeasuredHeight.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
174                         {
175                             childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
176                         }
177                     }
178                 }
179
180                 MeasuredSize widthSizeAndState = ResolveSizeAndState(new LayoutLength(totalWidth), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
181                 MeasuredSize heightSizeAndState = ResolveSizeAndState(new LayoutLength(totalHeight), heightMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
182                 totalWidth = widthSizeAndState.Size.AsDecimal();
183                 totalHeight = heightSizeAndState.Size.AsDecimal();
184
185                 // Ensure layout respects it's given minimum size
186                 totalWidth = Math.Max(totalWidth, SuggestedMinimumWidth.AsDecimal());
187                 totalHeight = Math.Max(totalHeight, SuggestedMinimumHeight.AsDecimal());
188
189                 widthSizeAndState.State = childWidthState;
190                 heightSizeAndState.State = childHeightState;
191
192                 SetMeasuredDimensions(ResolveSizeAndState(new LayoutLength(totalWidth), widthMeasureSpec, childWidthState),
193                     ResolveSizeAndState(new LayoutLength(totalHeight), heightMeasureSpec, childHeightState));
194
195                 // Size of ScrollableBase is changed. Change Page width too.
196                 if (scrollableBase != null)
197                 {
198                     scrollableBase.mPageWidth = (int)MeasuredWidth.Size.AsRoundedValue();
199                     scrollableBase.OnScrollingChildRelayout(null, null);
200                 }
201             }
202
203             protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
204             {
205                 foreach (LayoutItem childLayout in LayoutChildren)
206                 {
207                     if (childLayout != null && childLayout.Owner.Name == "ContentContainer")
208                     {
209                         LayoutLength childWidth = childLayout.MeasuredWidth.Size;
210                         LayoutLength childHeight = childLayout.MeasuredHeight.Size;
211
212                         Position2D childPosition = childLayout.Owner.Position2D;
213
214                         LayoutLength childLeft = new LayoutLength(childPosition.X);
215                         LayoutLength childTop = new LayoutLength(childPosition.Y);
216
217                         childLayout.Layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight, true);
218                     }
219                 }
220             }
221         } //  ScrollableBaseCustomLayout
222
223         /// <summary>
224         /// The direction axis to scroll.
225         /// </summary>
226         /// <since_tizen> 8 </since_tizen>
227         public enum Direction
228         {
229             /// <summary>
230             /// Horizontal axis.
231             /// </summary>
232             /// <since_tizen> 8 </since_tizen>
233             Horizontal,
234
235             /// <summary>
236             /// Vertical axis.
237             /// </summary>
238             /// <since_tizen> 8 </since_tizen>
239             Vertical
240         }
241
242         /// <summary>
243         /// Scrolling direction mode.
244         /// Default is Vertical scrolling.
245         /// </summary>
246         /// <since_tizen> 8 </since_tizen>
247         public Direction ScrollingDirection
248         {
249             get
250             {
251                 return mScrollingDirection;
252             }
253             set
254             {
255                 if (value != mScrollingDirection)
256                 {
257                     mScrollingDirection = value;
258                     mPanGestureDetector.ClearAngles();
259                     mPanGestureDetector.AddDirection(value == Direction.Horizontal ?
260                         PanGestureDetector.DirectionHorizontal : PanGestureDetector.DirectionVertical);
261
262                     ContentContainer.WidthSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent;
263                     ContentContainer.HeightSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent;
264                 }
265             }
266         }
267
268         /// <summary>
269         /// Enable or disable scrolling.
270         /// </summary>
271         /// <since_tizen> 8 </since_tizen>
272         public bool ScrollEnabled
273         {
274             get
275             {
276                 return mScrollEnabled;
277             }
278             set
279             {
280                 if (value != mScrollEnabled)
281                 {
282                     mScrollEnabled = value;
283                     if (mScrollEnabled)
284                     {
285                         mPanGestureDetector.Detected += OnPanGestureDetected;
286                     }
287                     else
288                     {
289                         mPanGestureDetector.Detected -= OnPanGestureDetected;
290                     }
291                 }
292             }
293         }
294
295         /// <summary>
296         /// Pages mode, enables moving to the next or return to current page depending on pan displacement.
297         /// Default is false.
298         /// </summary>
299         /// <since_tizen> 8 </since_tizen>
300         public bool SnapToPage { set; get; } = false;
301
302         /// <summary>
303         /// Get current page.
304         /// Working property with SnapToPage property.
305         /// </summary>
306         /// <since_tizen> 8 </since_tizen>
307         public int CurrentPage { get; private set; } = 0;
308
309         /// <summary>
310         /// Duration of scroll animation.
311         /// Default value is 125ms.
312         /// </summary>
313         /// <since_tizen> 8 </since_tizen>
314         public int ScrollDuration
315         {
316             set
317             {
318                 mScrollDuration = value >= 0 ? value : mScrollDuration;
319             }
320             get
321             {
322                 return mScrollDuration;
323             }
324         }
325
326         /// <summary>
327         /// Scroll Available area.
328         /// </summary>
329         /// <since_tizen> 8 </since_tizen>
330         public Vector2 ScrollAvailableArea { set; get; }
331
332         /// <summary>
333         /// An event emitted when user starts dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
334         /// </summary>
335         /// <since_tizen> 8 </since_tizen>
336         public event EventHandler<ScrollEventArgs> ScrollDragStarted;
337
338         /// <summary>
339         /// An event emitted when user stops dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
340         /// </summary>
341         /// <since_tizen> 8 </since_tizen>
342         public event EventHandler<ScrollEventArgs> ScrollDragEnded;
343
344         /// <summary>
345         /// An event emitted when the scrolling slide animation starts, user can subscribe or unsubscribe to this event handler.<br />
346         /// </summary>
347         /// <since_tizen> 8 </since_tizen>
348         public event EventHandler<ScrollEventArgs> ScrollAnimationStarted;
349
350         /// <summary>
351         /// An event emitted when the scrolling slide animation ends, user can subscribe or unsubscribe to this event handler.<br />
352         /// </summary>
353         /// <since_tizen> 8 </since_tizen>
354         public event EventHandler<ScrollEventArgs> ScrollAnimationEnded;
355
356         /// <summary>
357         /// An event emitted when scrolling, user can subscribe or unsubscribe to this event handler.<br />
358         /// </summary>
359         /// <since_tizen> 8 </since_tizen>
360         public event EventHandler<ScrollEventArgs> Scrolling;
361
362         /// <summary>
363         /// An event emitted when scrolling out of bound, user can subscribe or unsubscribe to this event handler.<br />
364         /// </summary>
365         [EditorBrowsable(EditorBrowsableState.Never)]
366         public event EventHandler<ScrollOutOfBoundEventArgs> ScrollOutOfBound;
367
368         /// <summary>
369         /// Scrollbar for ScrollableBase.
370         /// </summary>
371         /// <since_tizen> 8 </since_tizen>
372         public ScrollbarBase Scrollbar
373         {
374             get
375             {
376                 return scrollBar;
377             }
378             set
379             {
380                 if (scrollBar)
381                 {
382                     scrollBar.Unparent();
383                 }
384                 scrollBar = value;
385
386                 if (scrollBar != null)
387                 {
388                     scrollBar.Name = "ScrollBar";
389                     base.Add(scrollBar);
390
391                     if (hideScrollbar)
392                     {
393                         scrollBar.Hide();
394                     }
395                     else
396                     {
397                         scrollBar.Show();
398                     }
399
400                     SetScrollbar();
401                 }
402             }
403         }
404
405         /// <summary>
406         /// Always hide Scrollbar.
407         /// </summary>
408         /// <since_tizen> 8 </since_tizen>
409         public bool HideScrollbar
410         {
411             get
412             {
413                 return hideScrollbar;
414             }
415             set
416             {
417                 hideScrollbar = value;
418
419                 if (scrollBar)
420                 {
421                     if (value)
422                     {
423                         scrollBar.Hide();
424                     }
425                     else
426                     {
427                         scrollBar.Show();
428                     }
429                 }
430             }
431         }
432
433         /// <summary>
434         /// Container which has content of ScrollableBase.
435         /// </summary>
436         /// <since_tizen> 8 </since_tizen>
437         public View ContentContainer { get; private set; }
438
439         /// <summary>
440         /// Set the layout on this View. Replaces any existing Layout.
441         /// </summary>
442         /// <since_tizen> 8 </since_tizen>
443         public new LayoutItem Layout
444         {
445             get
446             {
447                 return ContentContainer.Layout;
448             }
449             set
450             {
451                 ContentContainer.Layout = value;
452             }
453         }
454
455         /// <summary>
456         /// List of children of Container.
457         /// </summary>
458         /// <since_tizen> 8 </since_tizen>
459         public new List<View> Children
460         {
461             get
462             {
463                 return ContentContainer.Children;
464             }
465         }
466
467         /// <summary>
468         /// Deceleration rate of scrolling by finger.
469         /// Rate should be bigger than 0 and smaller than 1.
470         /// Default value is 0.998f;
471         /// </summary>
472         /// <since_tizen> 8 </since_tizen>
473         public float DecelerationRate
474         {
475             get
476             {
477                 return decelerationRate;
478             }
479             set
480             {
481                 decelerationRate = (value < 1 && value > 0) ? value : decelerationRate;
482                 logValueOfDeceleration = (float)Math.Log(value);
483             }
484         }
485
486         /// <summary>
487         /// Threashold not to go infinit at the end of scrolling animation.
488         /// </summary>
489         [EditorBrowsable(EditorBrowsableState.Never)]
490         public float DecelerationThreshold { get; set; } = 0.1f;
491
492         /// <summary>
493         /// Scrolling event will be thrown when this amount of scroll position is changed.
494         /// If this threshold becomes smaller, the tracking detail increases but the scrolling range that can be tracked becomes smaller.
495         /// If large sized ContentContainer is required, please use larger threshold value.
496         /// Default ScrollingEventThreshold value is 0.001f.
497         /// </summary>
498         [EditorBrowsable(EditorBrowsableState.Never)]
499         public float ScrollingEventThreshold
500         {
501             get
502             {
503                 return mScrollingEventThreshold;
504             }
505             set
506             {
507                 if (mScrollingEventThreshold != value && value > 0)
508                 {
509                     ContentContainer.RemovePropertyNotification(propertyNotification);
510                     propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(value));
511                     propertyNotification.Notified += OnPropertyChanged;
512                     mScrollingEventThreshold = value;
513                 }
514             }
515         }
516
517         /// <summary>
518         /// Page will be changed when velocity of panning is over threshold.
519         /// The unit of threshold is pixel per milisec.
520         /// </summary>
521         /// <since_tizen> 8 </since_tizen>
522         public float PageFlickThreshold
523         {
524             get
525             {
526                 return mPageFlickThreshold;
527             }
528             set
529             {
530                 mPageFlickThreshold = value >= 0f ? value : mPageFlickThreshold;
531             }
532         }
533
534         /// <summary>
535         /// Padding for the ScrollableBase
536         /// </summary>
537         [EditorBrowsable(EditorBrowsableState.Never)]
538         public Extents Padding
539         {
540             get
541             {
542                 return ContentContainer.Padding;
543             }
544             set
545             {
546                 ContentContainer.Padding = value;
547             }
548         }
549
550         /// <summary>
551         /// Alphafunction for scroll animation.
552         /// </summary>
553         [EditorBrowsable(EditorBrowsableState.Never)]
554         public AlphaFunction ScrollAlphaFunction { get; set; } = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
555
556         private bool hideScrollbar = true;
557         private float maxScrollDistance;
558         private float childTargetPosition = 0.0f;
559         private PanGestureDetector mPanGestureDetector;
560         private View mInterruptTouchingChild;
561         private ScrollbarBase scrollBar;
562         private bool scrolling = false;
563         private float ratioOfScreenWidthToCompleteScroll = 0.5f;
564         private float totalDisplacementForPan = 0.0f;
565         private Size previousContainerSize = new Size();
566         private Size previousSize = new Size();
567         private PropertyNotification propertyNotification;
568         private float noticeAnimationEndBeforePosition = 0.0f;
569         private bool readyToNotice = false;
570
571         /// <summary>
572         /// Notice before animation is finished.
573         /// </summary>
574         [EditorBrowsable(EditorBrowsableState.Never)]
575         // Let's consider more whether this needs to be set as protected.
576         public float NoticeAnimationEndBeforePosition
577         {
578             get => noticeAnimationEndBeforePosition;
579             set => noticeAnimationEndBeforePosition = value;
580         }
581
582         // Let's consider more whether this needs to be set as protected.
583         private float finalTargetPosition;
584
585         private Animation scrollAnimation;
586         // Declare user alpha function delegate
587         [UnmanagedFunctionPointer(CallingConvention.StdCall)]
588         private delegate float UserAlphaFunctionDelegate(float progress);
589         private UserAlphaFunctionDelegate customScrollAlphaFunction;
590         private float velocityOfLastPan = 0.0f;
591         private float panAnimationDuration = 0.0f;
592         private float panAnimationDelta = 0.0f;
593         private float logValueOfDeceleration = 0.0f;
594         private float decelerationRate = 0.0f;
595
596         private View verticalTopShadowView;
597         private View verticalBottomShadowView;
598         private const int verticalShadowScaleHeightLimit = 64 * 3;
599         private const int verticalShadowAnimationDuration = 300;
600         private Animation verticalShadowAnimation;
601         private bool isVerticalShadowShown = false;
602         private float startShowShadowDisplacement;
603
604         /// <summary>
605         /// Default Constructor
606         /// </summary>
607         /// <since_tizen> 8 </since_tizen>
608         public ScrollableBase() : base()
609         {
610             DecelerationRate = 0.998f;
611
612             base.Layout = new ScrollableBaseCustomLayout();
613             mPanGestureDetector = new PanGestureDetector();
614             mPanGestureDetector.Attach(this);
615             mPanGestureDetector.AddDirection(PanGestureDetector.DirectionVertical);
616             mPanGestureDetector.Detected += OnPanGestureDetected;
617
618             ClippingMode = ClippingModeType.ClipChildren;
619
620             //Default Scrolling child
621             ContentContainer = new View()
622             {
623                 Name = "ContentContainer",
624                 ExcludeLayouting = false,
625                 WidthSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent,
626                 HeightSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent,
627             };
628             ContentContainer.Relayout += OnScrollingChildRelayout;
629             propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(mScrollingEventThreshold));
630             propertyNotification.Notified += OnPropertyChanged;
631             base.Add(ContentContainer);
632
633             //Interrupt touching when panning is started
634             mInterruptTouchingChild = new View()
635             {
636                 Size = new Size(Window.Instance.WindowSize),
637                 BackgroundColor = Color.Transparent,
638             };
639             mInterruptTouchingChild.TouchEvent += OnIterruptTouchingChildTouched;
640             Scrollbar = new Scrollbar();
641
642             //Show vertical shadow on the top (or bottom) of the scrollable when panning down (or up).
643             verticalTopShadowView = new View
644             {
645                 BackgroundImage = Tizen.NUI.FrameworkInformation.ResourcePath + "nui_component_default_scroll_over_shooting_top.png",
646                 Opacity = 1.0f,
647                 SizeHeight = 0.0f,
648                 PositionUsesPivotPoint = true,
649                 ParentOrigin = NUI.ParentOrigin.TopCenter,
650                 PivotPoint = NUI.PivotPoint.TopCenter,
651             };
652             verticalBottomShadowView = new View
653             {
654                 BackgroundImage = Tizen.NUI.FrameworkInformation.ResourcePath + "nui_component_default_scroll_over_shooting_bottom.png",
655                 Opacity = 1.0f,
656                 SizeHeight = 0.0f,
657                 PositionUsesPivotPoint = true,
658                 ParentOrigin = NUI.ParentOrigin.BottomCenter,
659                 PivotPoint = NUI.PivotPoint.BottomCenter,
660             };
661
662             AccessibilityManager.Instance.SetAccessibilityAttribute(this, AccessibilityManager.AccessibilityAttribute.Trait, "ScrollableBase");
663         }
664
665         private bool OnIterruptTouchingChildTouched(object source, View.TouchEventArgs args)
666         {
667             if (args.Touch.GetState(0) == PointStateType.Down)
668             {
669                 if (scrolling && !SnapToPage)
670                 {
671                     StopScroll();
672                 }
673             }
674             return true;
675         }
676
677         private void OnPropertyChanged(object source, PropertyNotification.NotifyEventArgs args)
678         {
679             OnScroll();
680         }
681
682         /// <summary>
683         /// Called after a child has been added to the owning view.
684         /// </summary>
685         /// <param name="view">The child which has been added.</param>
686         /// <since_tizen> 8 </since_tizen>
687         public override void Add(View view)
688         {
689             ContentContainer.Add(view);
690         }
691
692         /// <summary>
693         /// Called after a child has been removed from the owning view.
694         /// </summary>
695         /// <param name="view">The child which has been removed.</param>
696         /// <since_tizen> 8 </since_tizen>
697         public override void Remove(View view)
698         {
699             if (SnapToPage && CurrentPage == Children.IndexOf(view) && CurrentPage == Children.Count - 1)
700             {
701                 // Target View is current page and also last child.
702                 // CurrentPage should be changed to previous page.
703                 CurrentPage = Math.Max(0, CurrentPage - 1);
704                 ScrollToIndex(CurrentPage);
705             }
706
707             ContentContainer.Remove(view);
708         }
709
710         private void OnScrollingChildRelayout(object source, EventArgs args)
711         {
712             // Size is changed. Calculate maxScrollDistance.
713             bool isSizeChanged = previousContainerSize.Width != ContentContainer.Size.Width || previousContainerSize.Height != ContentContainer.Size.Height ||
714                 previousSize.Width != Size.Width || previousSize.Height != Size.Height;
715
716             if (isSizeChanged)
717             {
718                 maxScrollDistance = CalculateMaximumScrollDistance();
719                 SetScrollbar();
720             }
721
722             previousContainerSize = ContentContainer.Size;
723             previousSize = Size;
724         }
725
726         /// <summary>
727         /// The composition of a Scrollbar can vary depending on how you use ScrollableBase.
728         /// Set the composition that will go into the ScrollableBase according to your ScrollableBase.
729         /// </summary>
730         /// <since_tizen> 8 </since_tizen>
731         [EditorBrowsable(EditorBrowsableState.Never)]
732         protected virtual void SetScrollbar()
733         {
734             if (Scrollbar)
735             {
736                 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
737                 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
738                 float viewportLength = isHorizontal ? Size.Width : Size.Height;
739                 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
740                 Scrollbar.Initialize(contentLength, viewportLength, currentPosition, isHorizontal);
741             }
742         }
743
744         /// <summary>
745         /// Scrolls to the item at the specified index.
746         /// </summary>
747         /// <param name="index">Index of item.</param>
748         /// <since_tizen> 8 </since_tizen>
749         public void ScrollToIndex(int index)
750         {
751             if (ContentContainer.ChildCount - 1 < index || index < 0)
752             {
753                 return;
754             }
755
756             if (SnapToPage)
757             {
758                 CurrentPage = index;
759             }
760
761             float targetPosition = Math.Min(ScrollingDirection == Direction.Vertical ? Children[index].Position.Y : Children[index].Position.X, maxScrollDistance);
762             AnimateChildTo(ScrollDuration, -targetPosition);
763         }
764
765         private void OnScrollDragStarted()
766         {
767             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
768             ScrollDragStarted?.Invoke(this, eventArgs);
769         }
770
771         private void OnScrollDragEnded()
772         {
773             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
774             ScrollDragEnded?.Invoke(this, eventArgs);
775         }
776
777         private void OnScrollAnimationStarted()
778         {
779             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
780             ScrollAnimationStarted?.Invoke(this, eventArgs);
781         }
782
783         private void OnScrollAnimationEnded()
784         {
785             scrolling = false;
786             base.Remove(mInterruptTouchingChild);
787
788             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
789             ScrollAnimationEnded?.Invoke(this, eventArgs);
790         }
791
792         private void OnScroll()
793         {
794             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
795             Scrolling?.Invoke(this, eventArgs);
796
797             bool isHorizontal = ScrollingDirection == Direction.Horizontal;
798             float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
799             float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
800
801             scrollBar?.Update(contentLength, Math.Abs(currentPosition));
802             CheckPreReachedTargetPosition();
803         }
804
805         private void CheckPreReachedTargetPosition()
806         {
807             // Check whether we reached pre-reached target position
808             if (readyToNotice &&
809                 ContentContainer.CurrentPosition.Y <= finalTargetPosition + NoticeAnimationEndBeforePosition &&
810                 ContentContainer.CurrentPosition.Y >= finalTargetPosition - NoticeAnimationEndBeforePosition)
811             {
812                 //Notice first
813                 readyToNotice = false;
814                 OnPreReachedTargetPosition(finalTargetPosition);
815             }
816         }
817
818         /// <summary>
819         /// This helps developer who wants to know before scroll is reaching target position.
820         /// </summary>
821         /// <param name="targetPosition">Index of item.</param>
822         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
823         [EditorBrowsable(EditorBrowsableState.Never)]
824         protected virtual void OnPreReachedTargetPosition(float targetPosition)
825         {
826
827         }
828
829         private void StopScroll()
830         {
831             if (scrollAnimation != null)
832             {
833                 if (scrollAnimation.State == Animation.States.Playing)
834                 {
835                     Debug.WriteLineIf(LayoutDebugScrollableBase, "StopScroll Animation Playing");
836                     scrollAnimation.Stop(Animation.EndActions.Cancel);
837                     OnScrollAnimationEnded();
838                 }
839                 scrollAnimation.Clear();
840             }
841         }
842
843         private void AnimateChildTo(int duration, float axisPosition)
844         {
845             Debug.WriteLineIf(LayoutDebugScrollableBase, "AnimationTo Animation Duration:" + duration + " Destination:" + axisPosition);
846             finalTargetPosition = axisPosition;
847
848             StopScroll(); // Will replace previous animation so will stop existing one.
849
850             if (scrollAnimation == null)
851             {
852                 scrollAnimation = new Animation();
853                 scrollAnimation.Finished += ScrollAnimationFinished;
854             }
855
856             scrollAnimation.Duration = duration;
857             scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSquare);
858             scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", axisPosition, ScrollAlphaFunction);
859             scrolling = true;
860             OnScrollAnimationStarted();
861             scrollAnimation.Play();
862         }
863
864         /// <summary>
865         /// Scroll to specific position with or without animation.
866         /// </summary>
867         /// <param name="position">Destination.</param>
868         /// <param name="animate">Scroll with or without animation</param>
869         /// <since_tizen> 8 </since_tizen>
870         public void ScrollTo(float position, bool animate)
871         {
872             float currentPositionX = ContentContainer.CurrentPosition.X != 0 ? ContentContainer.CurrentPosition.X : ContentContainer.Position.X;
873             float currentPositionY = ContentContainer.CurrentPosition.Y != 0 ? ContentContainer.CurrentPosition.Y : ContentContainer.Position.Y;
874             float delta = ScrollingDirection == Direction.Horizontal ? currentPositionX : currentPositionY;
875             // The argument position is the new pan position. So the new position of ScrollableBase becomes (-position).
876             // To move ScrollableBase's position to (-position), it moves by (-position - currentPosition).
877             delta = -position - delta;
878
879             ScrollBy(delta, animate);
880         }
881
882         private float BoundScrollPosition(float targetPosition)
883         {
884             if (ScrollAvailableArea != null)
885             {
886                 float minScrollPosition = ScrollAvailableArea.X;
887                 float maxScrollPosition = ScrollAvailableArea.Y;
888
889                 targetPosition = Math.Min(-minScrollPosition, targetPosition);
890                 targetPosition = Math.Max(-maxScrollPosition, targetPosition);
891             }
892             else
893             {
894                 targetPosition = Math.Min(0, targetPosition);
895                 targetPosition = Math.Max(-maxScrollDistance, targetPosition);
896             }
897
898             return targetPosition;
899         }
900
901         private void ScrollBy(float displacement, bool animate)
902         {
903             if (GetChildCount() == 0 || maxScrollDistance < 0)
904             {
905                 return;
906             }
907
908             float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? ContentContainer.PositionX : ContentContainer.PositionY;
909
910             Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy childCurrentPosition:" + childCurrentPosition +
911                 " displacement:" + displacement,
912                 " maxScrollDistance:" + maxScrollDistance);
913
914             childTargetPosition = childCurrentPosition + displacement; // child current position + gesture displacement
915
916             Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition);
917
918             if (animate)
919             {
920                 // Calculate scroll animaton duration
921                 float scrollDistance = Math.Abs(displacement);
922                 readyToNotice = true;
923
924                 AnimateChildTo(ScrollDuration, BoundScrollPosition(AdjustTargetPositionOfScrollAnimation(BoundScrollPosition(childTargetPosition))));
925             }
926             else
927             {
928                 finalTargetPosition = BoundScrollPosition(childTargetPosition);
929
930                 // Set position of scrolling child without an animation
931                 if (ScrollingDirection == Direction.Horizontal)
932                 {
933                     ContentContainer.PositionX = finalTargetPosition;
934                 }
935                 else
936                 {
937                     ContentContainer.PositionY = finalTargetPosition;
938                 }
939             }
940         }
941
942         /// <summary>
943         /// you can override it to clean-up your own resources.
944         /// </summary>
945         /// <param name="type">DisposeTypes</param>
946         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
947         [EditorBrowsable(EditorBrowsableState.Never)]
948         protected override void Dispose(DisposeTypes type)
949         {
950             if (disposed)
951             {
952                 return;
953             }
954
955             if (type == DisposeTypes.Explicit)
956             {
957                 StopVerticalShadowAnimation();
958                 StopScroll();
959
960                 if (mPanGestureDetector != null)
961                 {
962                     mPanGestureDetector.Detected -= OnPanGestureDetected;
963                     mPanGestureDetector.Dispose();
964                     mPanGestureDetector = null;
965                 }
966
967                 propertyNotification.Dispose();
968             }
969             base.Dispose(type);
970         }
971
972         private float CalculateMaximumScrollDistance()
973         {
974             float scrollingChildLength = 0;
975             float scrollerLength = 0;
976             if (ScrollingDirection == Direction.Horizontal)
977             {
978                 Debug.WriteLineIf(LayoutDebugScrollableBase, "Horizontal");
979
980                 scrollingChildLength = ContentContainer.Size.Width;
981                 scrollerLength = Size.Width;
982             }
983             else
984             {
985                 Debug.WriteLineIf(LayoutDebugScrollableBase, "Vertical");
986                 scrollingChildLength = ContentContainer.Size.Height;
987                 scrollerLength = Size.Height;
988             }
989
990             Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy maxScrollDistance:" + (scrollingChildLength - scrollerLength) +
991                 " parent length:" + scrollerLength +
992                 " scrolling child length:" + scrollingChildLength);
993
994             return Math.Max(scrollingChildLength - scrollerLength, 0);
995         }
996
997         private void PageSnap(float velocity)
998         {
999             Debug.WriteLineIf(LayoutDebugScrollableBase, "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan +
1000                 " currentPage[" + CurrentPage + "]");
1001
1002             //Increment current page if total displacement enough to warrant a page change.
1003             if (Math.Abs(totalDisplacementForPan) > (mPageWidth * ratioOfScreenWidthToCompleteScroll))
1004             {
1005                 if (totalDisplacementForPan < 0)
1006                 {
1007                     CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
1008                 }
1009                 else
1010                 {
1011                     CurrentPage = Math.Max(0, --CurrentPage);
1012                 }
1013             }
1014             else if (Math.Abs(velocity) > PageFlickThreshold)
1015             {
1016                 if (velocity < 0)
1017                 {
1018                     CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
1019                 }
1020                 else
1021                 {
1022                     CurrentPage = Math.Max(0, --CurrentPage);
1023                 }
1024             }
1025
1026             // Animate to new page or reposition to current page
1027             float destinationX = -(Children[CurrentPage].Position.X + Children[CurrentPage].CurrentSize.Width / 2 - CurrentSize.Width / 2); // set to middle of current page
1028             Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to page[" + CurrentPage + "] to:" + destinationX + " from:" + ContentContainer.PositionX);
1029             AnimateChildTo(ScrollDuration, destinationX);
1030         }
1031
1032         /// <summary>
1033         /// Enable/Disable overshooting effect. default is disabled.
1034         /// </summary>
1035         [EditorBrowsable(EditorBrowsableState.Never)]
1036         public bool EnableOverShootingEffect { get; set; } = false;
1037
1038         private void AttachShadowView()
1039         {
1040             if (!EnableOverShootingEffect)
1041                 return;
1042
1043             if (ScrollingDirection != Direction.Vertical)
1044                 return;
1045
1046             // stop animation if necessary.
1047             StopVerticalShadowAnimation();
1048
1049             base.Add(verticalTopShadowView);
1050             base.Add(verticalBottomShadowView);
1051
1052             verticalTopShadowView.Size = new Size(SizeWidth, 0.0f);
1053             verticalTopShadowView.Opacity = 1.0f;
1054             verticalTopShadowView.RaiseToTop();
1055
1056             verticalBottomShadowView.Size = new Size(SizeWidth, 0.0f);
1057             verticalBottomShadowView.Opacity = 1.0f;
1058             verticalBottomShadowView.RaiseToTop();
1059
1060             // at the beginning, height of vertical shadow is 0, so it is invisible.
1061             isVerticalShadowShown = false;
1062         }
1063
1064         private void DragVerticalShadow(float totalPanDisplacement, float panDisplacement)
1065         {
1066             if (!EnableOverShootingEffect)
1067                 return;
1068
1069             if (ScrollingDirection != Direction.Vertical)
1070                 return;
1071
1072             if (totalPanDisplacement > 0) // downwards
1073             {
1074                 // check if reaching at the top.
1075                 if ((int)finalTargetPosition != 0)
1076                 {
1077                     isVerticalShadowShown = false;
1078                     return;
1079                 }
1080
1081                 // save start displacement, and re-calculate displacement.
1082                 if (!isVerticalShadowShown)
1083                 {
1084                     startShowShadowDisplacement = totalPanDisplacement;
1085                 }
1086                 isVerticalShadowShown = true;
1087
1088                 // trigger event
1089                 ScrollOutOfBoundEventArgs.Direction direction = panDisplacement > 0 ?
1090                     ScrollOutOfBoundEventArgs.Direction.Down : ScrollOutOfBoundEventArgs.Direction.Up;
1091                 OnScrollOutOfBound(direction, totalPanDisplacement);
1092
1093                 float newDisplacement = (int)totalPanDisplacement < (int)startShowShadowDisplacement ? 0 : totalPanDisplacement - startShowShadowDisplacement;
1094
1095                 // scale limit of width is 60%.
1096                 float widthScale = newDisplacement / verticalShadowScaleHeightLimit;
1097                 verticalTopShadowView.SizeWidth = widthScale > 0.6f ? SizeWidth * 0.4f : SizeWidth * (1.0f - widthScale);
1098
1099                 // scale limit of height is 300%.
1100                 verticalTopShadowView.SizeHeight = newDisplacement > verticalShadowScaleHeightLimit ? verticalShadowScaleHeightLimit : newDisplacement;
1101             }
1102             else if (totalPanDisplacement < 0) // upwards
1103             {
1104                 // check if reaching at the bottom.
1105                 if (-(int)finalTargetPosition != (int)maxScrollDistance)
1106                 {
1107                     isVerticalShadowShown = false;
1108                     return;
1109                 }
1110
1111                 // save start displacement, and re-calculate displacement.
1112                 if (!isVerticalShadowShown)
1113                 {
1114                     startShowShadowDisplacement = totalPanDisplacement;
1115                 }
1116                 isVerticalShadowShown = true;
1117
1118                 // trigger event
1119                 ScrollOutOfBoundEventArgs.Direction direction = panDisplacement > 0 ?
1120                     ScrollOutOfBoundEventArgs.Direction.Down : ScrollOutOfBoundEventArgs.Direction.Up;
1121                 OnScrollOutOfBound(direction, totalPanDisplacement);
1122
1123                 float newDisplacement = (int)startShowShadowDisplacement < (int)totalPanDisplacement ? 0 : startShowShadowDisplacement - totalPanDisplacement;
1124
1125                 // scale limit of width is 60%.
1126                 float widthScale = newDisplacement / verticalShadowScaleHeightLimit;
1127                 verticalBottomShadowView.SizeWidth = widthScale > 0.6f ? SizeWidth * 0.4f : SizeWidth * (1.0f - widthScale);
1128
1129                 // scale limit of height is 300%.
1130                 verticalBottomShadowView.SizeHeight = newDisplacement > verticalShadowScaleHeightLimit ? verticalShadowScaleHeightLimit : newDisplacement;
1131             }
1132             else
1133             {
1134                 // if total displacement is 0, shadow would become invisible.
1135                 isVerticalShadowShown = false;
1136             }
1137         }
1138
1139         private void PlayVerticalShadowAnimation()
1140         {
1141             if (!EnableOverShootingEffect)
1142                 return;
1143
1144             if (ScrollingDirection != Direction.Vertical)
1145                 return;
1146
1147             // stop animation if necessary.
1148             StopVerticalShadowAnimation();
1149
1150             if (verticalShadowAnimation == null)
1151             {
1152                 verticalShadowAnimation = new Animation(verticalShadowAnimationDuration);
1153                 verticalShadowAnimation.Finished += OnVerticalShadowAnimationFinished;
1154             }
1155
1156             View targetView = totalDisplacementForPan < 0 ? verticalBottomShadowView : verticalTopShadowView;
1157             verticalShadowAnimation.AnimateTo(targetView, "SizeWidth", SizeWidth);
1158             verticalShadowAnimation.AnimateTo(targetView, "SizeHeight", 0.0f);
1159             verticalShadowAnimation.AnimateTo(targetView, "Opacity", 0.0f);
1160             verticalShadowAnimation.Play();
1161         }
1162
1163         private void StopVerticalShadowAnimation()
1164         {
1165             if (verticalShadowAnimation == null || verticalShadowAnimation.State != Animation.States.Playing)
1166                 return;
1167
1168             verticalShadowAnimation.Stop(Animation.EndActions.Cancel);
1169             OnVerticalShadowAnimationFinished(null, null);
1170             verticalShadowAnimation.Clear();
1171         }
1172
1173         private void OnVerticalShadowAnimationFinished(object sender, EventArgs e)
1174         {
1175             base.Remove(verticalTopShadowView);
1176             base.Remove(verticalBottomShadowView);
1177
1178             verticalTopShadowView.Size = new Size(SizeWidth, 0.0f);
1179             verticalBottomShadowView.Size = new Size(SizeWidth, 0.0f);
1180
1181             // after animation finished, height & opacity of vertical shadow both are 0, so it is invisible.
1182             isVerticalShadowShown = false;
1183         }
1184
1185         private void OnScrollOutOfBound(ScrollOutOfBoundEventArgs.Direction direction, float displacement)
1186         {
1187             ScrollOutOfBoundEventArgs args = new ScrollOutOfBoundEventArgs(direction, displacement);
1188             ScrollOutOfBound?.Invoke(this, args);
1189         }
1190
1191         private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
1192         {
1193             OnPanGesture(e.PanGesture);
1194         }
1195
1196         private void OnPanGesture(PanGesture panGesture)
1197         {
1198             if (SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
1199             {
1200                 return;
1201             }
1202
1203             if (panGesture.State == Gesture.StateType.Started)
1204             {
1205                 readyToNotice = false;
1206                 base.Add(mInterruptTouchingChild);
1207                 AttachShadowView();
1208                 Debug.WriteLineIf(LayoutDebugScrollableBase, "Gesture Start");
1209                 if (scrolling && !SnapToPage)
1210                 {
1211                     StopScroll();
1212                 }
1213                 totalDisplacementForPan = 0.0f;
1214                 OnScrollDragStarted();
1215             }
1216             else if (panGesture.State == Gesture.StateType.Continuing)
1217             {
1218                 if (ScrollingDirection == Direction.Horizontal)
1219                 {
1220                     ScrollBy(panGesture.Displacement.X, false);
1221                     totalDisplacementForPan += panGesture.Displacement.X;
1222                 }
1223                 else
1224                 {
1225                     // if vertical shadow is shown, does not scroll.
1226                     if (!isVerticalShadowShown)
1227                     {
1228                         ScrollBy(panGesture.Displacement.Y, false);
1229                     }
1230                     totalDisplacementForPan += panGesture.Displacement.Y;
1231                     DragVerticalShadow(totalDisplacementForPan, panGesture.Displacement.Y);
1232                 }
1233                 Debug.WriteLineIf(LayoutDebugScrollableBase, "OnPanGestureDetected Continue totalDisplacementForPan:" + totalDisplacementForPan);
1234             }
1235             else if (panGesture.State == Gesture.StateType.Finished || panGesture.State == Gesture.StateType.Cancelled)
1236             {
1237                 PlayVerticalShadowAnimation();
1238                 OnScrollDragEnded();
1239                 StopScroll(); // Will replace previous animation so will stop existing one.
1240
1241                 if (scrollAnimation == null)
1242                 {
1243                     scrollAnimation = new Animation();
1244                     scrollAnimation.Finished += ScrollAnimationFinished;
1245                 }
1246
1247                 float panVelocity = (ScrollingDirection == Direction.Horizontal) ? panGesture.Velocity.X : panGesture.Velocity.Y;
1248
1249                 if (SnapToPage)
1250                 {
1251                     PageSnap(panVelocity);
1252                 }
1253                 else
1254                 {
1255                     if (panVelocity == 0)
1256                     {
1257                         float currentScrollPosition = (ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
1258                         scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
1259                         scrollAnimation.Duration = 0;
1260                         scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", currentScrollPosition);
1261                         scrollAnimation.Play();
1262                     }
1263                     else
1264                     {
1265                         Decelerating(panVelocity, scrollAnimation);
1266                     }
1267                 }
1268
1269                 totalDisplacementForPan = 0;
1270                 scrolling = true;
1271                 readyToNotice = true;
1272                 OnScrollAnimationStarted();
1273             }
1274         }
1275
1276         internal override bool OnAccessibilityPan(PanGesture gestures)
1277         {
1278             if (SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
1279             {
1280                 return false;
1281             }
1282
1283             OnPanGesture(gestures);
1284             return true;
1285         }
1286
1287         private float CustomScrollAlphaFunction(float progress)
1288         {
1289             if (panAnimationDelta == 0)
1290             {
1291                 return 1.0f;
1292             }
1293             else
1294             {
1295                 // Parameter "progress" is normalized value. We need to multiply target duration to calculate distance.
1296                 // Can get real distance using equation of deceleration (check Decelerating function)
1297                 // After get real distance, normalize it
1298                 float realDuration = progress * panAnimationDuration;
1299                 float realDistance = velocityOfLastPan * ((float)Math.Pow(decelerationRate, realDuration) - 1) / logValueOfDeceleration;
1300                 float result = Math.Min(realDistance / Math.Abs(panAnimationDelta), 1.0f);
1301                 return result;
1302             }
1303         }
1304
1305         /// <summary>
1306         /// you can override it to custom your decelerating
1307         /// </summary>
1308         /// <param name="velocity">Velocity of current pan.</param>
1309         /// <param name="animation">Scroll animation.</param>
1310         [EditorBrowsable(EditorBrowsableState.Never)]
1311         protected virtual void Decelerating(float velocity, Animation animation)
1312         {
1313             // Decelerating using deceleration equation ===========
1314             //
1315             // V   : velocity (pixel per milisecond)
1316             // V0  : initial velocity
1317             // d   : deceleration rate,
1318             // t   : time
1319             // X   : final position after decelerating
1320             // log : natural logarithm
1321             //
1322             // V(t) = V0 * d pow t;
1323             // X(t) = V0 * (d pow t - 1) / log d;  <-- Integrate the velocity function
1324             // X(∞) = V0 * d / (1 - d); <-- Result using inifit T can be final position because T is tending to infinity.
1325             //
1326             // Because of final T is tending to inifity, we should use threshold value to finish.
1327             // Final T = log(-threshold * log d / |V0| ) / log d;
1328
1329             velocityOfLastPan = Math.Abs(velocity);
1330
1331             float currentScrollPosition = -(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
1332             panAnimationDelta = (velocityOfLastPan * decelerationRate) / (1 - decelerationRate);
1333             panAnimationDelta = velocity > 0 ? -panAnimationDelta : panAnimationDelta;
1334
1335             float destination = -(panAnimationDelta + currentScrollPosition);
1336             float adjustDestination = AdjustTargetPositionOfScrollAnimation(destination);
1337             float maxPosition = ScrollAvailableArea != null ? ScrollAvailableArea.Y : maxScrollDistance;
1338             float minPosition = ScrollAvailableArea != null ? ScrollAvailableArea.X : 0;
1339
1340             if (destination < -maxPosition || destination > minPosition)
1341             {
1342                 panAnimationDelta = velocity > 0 ? (currentScrollPosition - minPosition) : (maxPosition - currentScrollPosition);
1343                 destination = velocity > 0 ? minPosition : -maxPosition;
1344
1345                 if (panAnimationDelta == 0)
1346                 {
1347                     panAnimationDuration = 0.0f;
1348                 }
1349                 else
1350                 {
1351                     panAnimationDuration = (float)Math.Log((panAnimationDelta * logValueOfDeceleration / velocityOfLastPan + 1), decelerationRate);
1352                 }
1353
1354                 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1355                     "OverRange======================= \n" +
1356                     "[decelerationRate] " + decelerationRate + "\n" +
1357                     "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1358                     "[Velocity] " + velocityOfLastPan + "\n" +
1359                     "[CurrentPosition] " + currentScrollPosition + "\n" +
1360                     "[CandidateDelta] " + panAnimationDelta + "\n" +
1361                     "[Destination] " + destination + "\n" +
1362                     "[Duration] " + panAnimationDuration + "\n" +
1363                     "================================ \n"
1364                 );
1365             }
1366             else
1367             {
1368                 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1369
1370                 if (adjustDestination != destination)
1371                 {
1372                     destination = adjustDestination;
1373                     panAnimationDelta = destination + currentScrollPosition;
1374                     velocityOfLastPan = Math.Abs(panAnimationDelta * logValueOfDeceleration / ((float)Math.Pow(decelerationRate, panAnimationDuration) - 1));
1375                     panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1376                 }
1377
1378                 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1379                     "================================ \n" +
1380                     "[decelerationRate] " + decelerationRate + "\n" +
1381                     "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1382                     "[Velocity] " + velocityOfLastPan + "\n" +
1383                     "[CurrentPosition] " + currentScrollPosition + "\n" +
1384                     "[CandidateDelta] " + panAnimationDelta + "\n" +
1385                     "[Destination] " + destination + "\n" +
1386                     "[Duration] " + panAnimationDuration + "\n" +
1387                     "================================ \n"
1388                 );
1389             }
1390
1391             finalTargetPosition = destination;
1392
1393             customScrollAlphaFunction = new UserAlphaFunctionDelegate(CustomScrollAlphaFunction);
1394             animation.DefaultAlphaFunction = new AlphaFunction(customScrollAlphaFunction);
1395             GC.KeepAlive(customScrollAlphaFunction);
1396             animation.Duration = (int)panAnimationDuration;
1397             animation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", destination);
1398             animation.Play();
1399         }
1400
1401         private void ScrollAnimationFinished(object sender, EventArgs e)
1402         {
1403             OnScrollAnimationEnded();
1404         }
1405
1406         /// <summary>
1407         /// Adjust scrolling position by own scrolling rules.
1408         /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1409         /// </summary>
1410         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
1411         [EditorBrowsable(EditorBrowsableState.Never)]
1412         protected virtual float AdjustTargetPositionOfScrollAnimation(float position)
1413         {
1414             return position;
1415         }
1416
1417         /// <summary>
1418         /// Scroll position given to ScrollTo.
1419         /// This is the position in the opposite direction to the position of ContentContainer.
1420         /// </summary>
1421         /// <since_tizen> 8 </since_tizen>
1422         public Position ScrollPosition
1423         {
1424             get
1425             {
1426                 return new Position(-ContentContainer.Position);
1427             }
1428         }
1429
1430         /// <summary>
1431         /// Current scroll position in the middle of ScrollTo animation.
1432         /// This is the position in the opposite direction to the current position of ContentContainer.
1433         /// </summary>
1434         /// <since_tizen> 8 </since_tizen>
1435         public Position ScrollCurrentPosition
1436         {
1437             get
1438             {
1439                 return new Position(-ContentContainer.CurrentPosition);
1440             }
1441         }
1442
1443         /// <summary>
1444         /// Remove all children in ContentContainer.
1445         /// </summary>
1446         /// <param name="dispose">If true, removed child is disposed.</param>
1447         [EditorBrowsable(EditorBrowsableState.Never)]
1448         public void RemoveAllChildren(bool dispose = false)
1449         {
1450             RecursiveRemoveChildren(ContentContainer, dispose);
1451         }
1452
1453         private void RecursiveRemoveChildren(View parent, bool dispose)
1454         {
1455             if (parent == null)
1456             {
1457                 return;
1458             }
1459             int maxChild = (int)parent.GetChildCount();
1460             for (int i = maxChild - 1; i >= 0; --i)
1461             {
1462                 View child = parent.GetChildAt((uint)i);
1463                 if (child == null)
1464                 {
1465                     continue;
1466                 }
1467                 RecursiveRemoveChildren(child, dispose);
1468                 parent.Remove(child);
1469                 if (dispose)
1470                 {
1471                     child.Dispose();
1472                 }
1473             }
1474         }
1475
1476     }
1477
1478 } // namespace