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