[NUI] Fix TV TCT fails
[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                     base.Remove(scrollBar);
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.ClipToBoundingBox;
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 #if (PROFILE_MOBILE)
717             AccessibilityManager.Instance.SetAccessibilityAttribute(this, AccessibilityManager.AccessibilityAttribute.Trait, "ScrollableBase");
718 #endif
719         }
720
721         private bool OnIterruptTouchingChildTouched(object source, View.TouchEventArgs args)
722         {
723             if (args.Touch.GetState(0) == PointStateType.Down)
724             {
725                 if (scrolling && !SnapToPage)
726                 {
727                     StopScroll();
728                 }
729             }
730             return true;
731         }
732
733         private void OnPropertyChanged(object source, PropertyNotification.NotifyEventArgs args)
734         {
735             OnScroll();
736         }
737
738         /// <summary>
739         /// Called after a child has been added to the owning view.
740         /// </summary>
741         /// <param name="view">The child which has been added.</param>
742         /// <since_tizen> 8 </since_tizen>
743         public override void Add(View view)
744         {
745             ContentContainer.Add(view);
746         }
747
748         /// <summary>
749         /// Called after a child has been removed from the owning view.
750         /// </summary>
751         /// <param name="view">The child which has been removed.</param>
752         /// <since_tizen> 8 </since_tizen>
753         public override void Remove(View view)
754         {
755             if (SnapToPage && CurrentPage == Children.IndexOf(view) && CurrentPage == Children.Count - 1)
756             {
757                 // Target View is current page and also last child.
758                 // CurrentPage should be changed to previous page.
759                 CurrentPage = Math.Max(0, CurrentPage - 1);
760                 ScrollToIndex(CurrentPage);
761             }
762
763             ContentContainer.Remove(view);
764         }
765
766         private void OnScrollingChildRelayout(object source, EventArgs args)
767         {
768             // Size is changed. Calculate maxScrollDistance.
769             bool isSizeChanged = previousContainerSize.Width != ContentContainer.Size.Width || previousContainerSize.Height != ContentContainer.Size.Height ||
770                 previousSize.Width != Size.Width || previousSize.Height != Size.Height;
771
772             if (isSizeChanged)
773             {
774                 maxScrollDistance = CalculateMaximumScrollDistance();
775                 SetScrollbar();
776             }
777
778             previousContainerSize = ContentContainer.Size;
779             previousSize = Size;
780         }
781
782         /// <summary>
783         /// The composition of a Scrollbar can vary depending on how you use ScrollableBase.
784         /// Set the composition that will go into the ScrollableBase according to your ScrollableBase.
785         /// </summary>
786         /// <since_tizen> 8 </since_tizen>
787         [EditorBrowsable(EditorBrowsableState.Never)]
788         protected virtual void SetScrollbar()
789         {
790             if (Scrollbar)
791             {
792                 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
793                 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
794                 float viewportLength = isHorizontal ? Size.Width : Size.Height;
795                 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
796                 Scrollbar.Initialize(contentLength, viewportLength, currentPosition, isHorizontal);
797             }
798         }
799
800         /// <summary>
801         /// Scrolls to the item at the specified index.
802         /// </summary>
803         /// <param name="index">Index of item.</param>
804         /// <since_tizen> 8 </since_tizen>
805         public void ScrollToIndex(int index)
806         {
807             if (ContentContainer.ChildCount - 1 < index || index < 0)
808             {
809                 return;
810             }
811
812             if (SnapToPage)
813             {
814                 CurrentPage = index;
815             }
816
817             float targetPosition = Math.Min(ScrollingDirection == Direction.Vertical ? Children[index].Position.Y : Children[index].Position.X, maxScrollDistance);
818             AnimateChildTo(ScrollDuration, -targetPosition);
819         }
820
821         private void OnScrollDragStarted()
822         {
823             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
824             ScrollDragStarted?.Invoke(this, eventArgs);
825         }
826
827         private void OnScrollDragEnded()
828         {
829             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
830             ScrollDragEnded?.Invoke(this, eventArgs);
831         }
832
833         private void OnScrollAnimationStarted()
834         {
835             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
836             ScrollAnimationStarted?.Invoke(this, eventArgs);
837         }
838
839         private void OnScrollAnimationEnded()
840         {
841             scrolling = false;
842             base.Remove(mInterruptTouchingChild);
843
844             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
845             ScrollAnimationEnded?.Invoke(this, eventArgs);
846         }
847
848         private void OnScroll()
849         {
850             ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
851             Scrolling?.Invoke(this, eventArgs);
852
853             bool isHorizontal = ScrollingDirection == Direction.Horizontal;
854             float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
855             float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
856
857             scrollBar?.Update(contentLength, Math.Abs(currentPosition));
858             CheckPreReachedTargetPosition();
859         }
860
861         private void CheckPreReachedTargetPosition()
862         {
863             // Check whether we reached pre-reached target position
864             if (readyToNotice &&
865                 ContentContainer.CurrentPosition.Y <= finalTargetPosition + NoticeAnimationEndBeforePosition &&
866                 ContentContainer.CurrentPosition.Y >= finalTargetPosition - NoticeAnimationEndBeforePosition)
867             {
868                 //Notice first
869                 readyToNotice = false;
870                 OnPreReachedTargetPosition(finalTargetPosition);
871             }
872         }
873
874         /// <summary>
875         /// This helps developer who wants to know before scroll is reaching target position.
876         /// </summary>
877         /// <param name="targetPosition">Index of item.</param>
878         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
879         [EditorBrowsable(EditorBrowsableState.Never)]
880         protected virtual void OnPreReachedTargetPosition(float targetPosition)
881         {
882
883         }
884
885         private void StopScroll()
886         {
887             if (scrollAnimation != null)
888             {
889                 if (scrollAnimation.State == Animation.States.Playing)
890                 {
891                     Debug.WriteLineIf(LayoutDebugScrollableBase, "StopScroll Animation Playing");
892                     scrollAnimation.Stop(Animation.EndActions.Cancel);
893                     OnScrollAnimationEnded();
894                 }
895                 scrollAnimation.Clear();
896             }
897         }
898
899         private void AnimateChildTo(int duration, float axisPosition)
900         {
901             Debug.WriteLineIf(LayoutDebugScrollableBase, "AnimationTo Animation Duration:" + duration + " Destination:" + axisPosition);
902             finalTargetPosition = axisPosition;
903
904             StopScroll(); // Will replace previous animation so will stop existing one.
905
906             if (scrollAnimation == null)
907             {
908                 scrollAnimation = new Animation();
909                 scrollAnimation.Finished += ScrollAnimationFinished;
910             }
911
912             scrollAnimation.Duration = duration;
913             scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSquare);
914             scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", axisPosition, ScrollAlphaFunction);
915             scrolling = true;
916             OnScrollAnimationStarted();
917             scrollAnimation.Play();
918         }
919
920         /// <summary>
921         /// Scroll to specific position with or without animation.
922         /// </summary>
923         /// <param name="position">Destination.</param>
924         /// <param name="animate">Scroll with or without animation</param>
925         /// <since_tizen> 8 </since_tizen>
926         public void ScrollTo(float position, bool animate)
927         {
928             float currentPositionX = ContentContainer.CurrentPosition.X != 0 ? ContentContainer.CurrentPosition.X : ContentContainer.Position.X;
929             float currentPositionY = ContentContainer.CurrentPosition.Y != 0 ? ContentContainer.CurrentPosition.Y : ContentContainer.Position.Y;
930             float delta = ScrollingDirection == Direction.Horizontal ? currentPositionX : currentPositionY;
931             // The argument position is the new pan position. So the new position of ScrollableBase becomes (-position).
932             // To move ScrollableBase's position to (-position), it moves by (-position - currentPosition).
933             delta = -position - delta;
934
935             ScrollBy(delta, animate);
936         }
937
938         private float BoundScrollPosition(float targetPosition)
939         {
940             if (ScrollAvailableArea != null)
941             {
942                 float minScrollPosition = ScrollAvailableArea.X;
943                 float maxScrollPosition = ScrollAvailableArea.Y;
944
945                 targetPosition = Math.Min(-minScrollPosition, targetPosition);
946                 targetPosition = Math.Max(-maxScrollPosition, targetPosition);
947             }
948             else
949             {
950                 targetPosition = Math.Min(0, targetPosition);
951                 targetPosition = Math.Max(-maxScrollDistance, targetPosition);
952             }
953
954             return targetPosition;
955         }
956
957         private void ScrollBy(float displacement, bool animate)
958         {
959             if (GetChildCount() == 0 || maxScrollDistance < 0)
960             {
961                 return;
962             }
963
964             float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? ContentContainer.PositionX : ContentContainer.PositionY;
965
966             Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy childCurrentPosition:" + childCurrentPosition +
967                 " displacement:" + displacement,
968                 " maxScrollDistance:" + maxScrollDistance);
969
970             childTargetPosition = childCurrentPosition + displacement; // child current position + gesture displacement
971
972             Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition);
973
974             if (animate)
975             {
976                 // Calculate scroll animaton duration
977                 float scrollDistance = Math.Abs(displacement);
978                 readyToNotice = true;
979
980                 AnimateChildTo(ScrollDuration, BoundScrollPosition(AdjustTargetPositionOfScrollAnimation(BoundScrollPosition(childTargetPosition))));
981             }
982             else
983             {
984                 finalTargetPosition = BoundScrollPosition(childTargetPosition);
985
986                 // Set position of scrolling child without an animation
987                 if (ScrollingDirection == Direction.Horizontal)
988                 {
989                     ContentContainer.PositionX = finalTargetPosition;
990                 }
991                 else
992                 {
993                     ContentContainer.PositionY = finalTargetPosition;
994                 }
995             }
996         }
997
998         /// <summary>
999         /// you can override it to clean-up your own resources.
1000         /// </summary>
1001         /// <param name="type">DisposeTypes</param>
1002         /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
1003         [EditorBrowsable(EditorBrowsableState.Never)]
1004         protected override void Dispose(DisposeTypes type)
1005         {
1006             if (disposed)
1007             {
1008                 return;
1009             }
1010
1011             if (type == DisposeTypes.Explicit)
1012             {
1013 #if (PROFILE_MOBILE)
1014                 AccessibilityManager.Instance.DeleteAccessibilityAttribute(this);
1015 #endif
1016                 StopVerticalShadowAnimation();
1017                 StopScroll();
1018
1019                 if (mPanGestureDetector != null)
1020                 {
1021                     mPanGestureDetector.Detected -= OnPanGestureDetected;
1022                     mPanGestureDetector.Dispose();
1023                     mPanGestureDetector = null;
1024                 }
1025
1026                 propertyNotification.Dispose();
1027             }
1028             base.Dispose(type);
1029         }
1030
1031         private float CalculateMaximumScrollDistance()
1032         {
1033             float scrollingChildLength = 0;
1034             float scrollerLength = 0;
1035             if (ScrollingDirection == Direction.Horizontal)
1036             {
1037                 Debug.WriteLineIf(LayoutDebugScrollableBase, "Horizontal");
1038
1039                 scrollingChildLength = ContentContainer.Size.Width;
1040                 scrollerLength = Size.Width;
1041             }
1042             else
1043             {
1044                 Debug.WriteLineIf(LayoutDebugScrollableBase, "Vertical");
1045                 scrollingChildLength = ContentContainer.Size.Height;
1046                 scrollerLength = Size.Height;
1047             }
1048
1049             Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy maxScrollDistance:" + (scrollingChildLength - scrollerLength) +
1050                 " parent length:" + scrollerLength +
1051                 " scrolling child length:" + scrollingChildLength);
1052
1053             return Math.Max(scrollingChildLength - scrollerLength, 0);
1054         }
1055
1056         private void PageSnap(float velocity)
1057         {
1058             Debug.WriteLineIf(LayoutDebugScrollableBase, "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan +
1059                 " currentPage[" + CurrentPage + "]");
1060
1061             //Increment current page if total displacement enough to warrant a page change.
1062             if (Math.Abs(totalDisplacementForPan) > (mPageWidth * ratioOfScreenWidthToCompleteScroll))
1063             {
1064                 if (totalDisplacementForPan < 0)
1065                 {
1066                     CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
1067                 }
1068                 else
1069                 {
1070                     CurrentPage = Math.Max(0, --CurrentPage);
1071                 }
1072             }
1073             else if (Math.Abs(velocity) > PageFlickThreshold)
1074             {
1075                 if (velocity < 0)
1076                 {
1077                     CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
1078                 }
1079                 else
1080                 {
1081                     CurrentPage = Math.Max(0, --CurrentPage);
1082                 }
1083             }
1084
1085             // Animate to new page or reposition to current page
1086             float destinationX = -(Children[CurrentPage].Position.X + Children[CurrentPage].CurrentSize.Width / 2 - CurrentSize.Width / 2); // set to middle of current page
1087             Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to page[" + CurrentPage + "] to:" + destinationX + " from:" + ContentContainer.PositionX);
1088             AnimateChildTo(ScrollDuration, destinationX);
1089         }
1090
1091         /// <summary>
1092         /// Enable/Disable overshooting effect. default is disabled.
1093         /// </summary>
1094         [EditorBrowsable(EditorBrowsableState.Never)]
1095         public bool EnableOverShootingEffect { get; set; } = false;
1096
1097         private void AttachShadowView()
1098         {
1099             if (!EnableOverShootingEffect)
1100                 return;
1101
1102             if (ScrollingDirection != Direction.Vertical)
1103                 return;
1104
1105             // stop animation if necessary.
1106             StopVerticalShadowAnimation();
1107
1108             base.Add(verticalTopShadowView);
1109             base.Add(verticalBottomShadowView);
1110
1111             verticalTopShadowView.Size = new Size(SizeWidth, 0.0f);
1112             verticalTopShadowView.Opacity = 1.0f;
1113             verticalTopShadowView.RaiseToTop();
1114
1115             verticalBottomShadowView.Size = new Size(SizeWidth, 0.0f);
1116             verticalBottomShadowView.Opacity = 1.0f;
1117             verticalBottomShadowView.RaiseToTop();
1118
1119             // at the beginning, height of vertical shadow is 0, so it is invisible.
1120             isVerticalShadowShown = false;
1121         }
1122
1123         private void DragVerticalShadow(float totalPanDisplacement, float panDisplacement)
1124         {
1125             if (!EnableOverShootingEffect)
1126                 return;
1127
1128             if (ScrollingDirection != Direction.Vertical)
1129                 return;
1130
1131             if (totalPanDisplacement > 0) // downwards
1132             {
1133                 // check if reaching at the top.
1134                 if ((int)finalTargetPosition != 0)
1135                 {
1136                     isVerticalShadowShown = false;
1137                     return;
1138                 }
1139
1140                 // save start displacement, and re-calculate displacement.
1141                 if (!isVerticalShadowShown)
1142                 {
1143                     startShowShadowDisplacement = totalPanDisplacement;
1144                 }
1145                 isVerticalShadowShown = true;
1146
1147                 // trigger event
1148                 ScrollOutOfBoundWithDisplacementEventArgs.Direction direction = panDisplacement > 0 ?
1149                     ScrollOutOfBoundWithDisplacementEventArgs.Direction.Down : ScrollOutOfBoundWithDisplacementEventArgs.Direction.Up;
1150                 OnScrollOutOfBoundWithDisplacement(direction, totalPanDisplacement);
1151
1152                 float newDisplacement = (int)totalPanDisplacement < (int)startShowShadowDisplacement ? 0 : totalPanDisplacement - startShowShadowDisplacement;
1153
1154                 // scale limit of width is 60%.
1155                 float widthScale = newDisplacement / verticalShadowScaleHeightLimit;
1156                 verticalTopShadowView.SizeWidth = widthScale > 0.6f ? SizeWidth * 0.4f : SizeWidth * (1.0f - widthScale);
1157
1158                 // scale limit of height is 300%.
1159                 verticalTopShadowView.SizeHeight = newDisplacement > verticalShadowScaleHeightLimit ? verticalShadowScaleHeightLimit : newDisplacement;
1160             }
1161             else if (totalPanDisplacement < 0) // upwards
1162             {
1163                 // check if reaching at the bottom.
1164                 if (-(int)finalTargetPosition != (int)maxScrollDistance)
1165                 {
1166                     isVerticalShadowShown = false;
1167                     return;
1168                 }
1169
1170                 // save start displacement, and re-calculate displacement.
1171                 if (!isVerticalShadowShown)
1172                 {
1173                     startShowShadowDisplacement = totalPanDisplacement;
1174                 }
1175                 isVerticalShadowShown = true;
1176
1177                 // trigger event
1178                 ScrollOutOfBoundWithDisplacementEventArgs.Direction direction = panDisplacement > 0 ?
1179                     ScrollOutOfBoundWithDisplacementEventArgs.Direction.Down : ScrollOutOfBoundWithDisplacementEventArgs.Direction.Up;
1180                 OnScrollOutOfBoundWithDisplacement(direction, totalPanDisplacement);
1181
1182                 float newDisplacement = (int)startShowShadowDisplacement < (int)totalPanDisplacement ? 0 : startShowShadowDisplacement - totalPanDisplacement;
1183
1184                 // scale limit of width is 60%.
1185                 float widthScale = newDisplacement / verticalShadowScaleHeightLimit;
1186                 verticalBottomShadowView.SizeWidth = widthScale > 0.6f ? SizeWidth * 0.4f : SizeWidth * (1.0f - widthScale);
1187
1188                 // scale limit of height is 300%.
1189                 verticalBottomShadowView.SizeHeight = newDisplacement > verticalShadowScaleHeightLimit ? verticalShadowScaleHeightLimit : newDisplacement;
1190             }
1191             else
1192             {
1193                 // if total displacement is 0, shadow would become invisible.
1194                 isVerticalShadowShown = false;
1195             }
1196         }
1197
1198         private void PlayVerticalShadowAnimation()
1199         {
1200             if (!EnableOverShootingEffect)
1201                 return;
1202
1203             if (ScrollingDirection != Direction.Vertical)
1204                 return;
1205
1206             // stop animation if necessary.
1207             StopVerticalShadowAnimation();
1208
1209             if (verticalShadowAnimation == null)
1210             {
1211                 verticalShadowAnimation = new Animation(verticalShadowAnimationDuration);
1212                 verticalShadowAnimation.Finished += OnVerticalShadowAnimationFinished;
1213             }
1214
1215             View targetView = totalDisplacementForPan < 0 ? verticalBottomShadowView : verticalTopShadowView;
1216             verticalShadowAnimation.AnimateTo(targetView, "SizeWidth", SizeWidth);
1217             verticalShadowAnimation.AnimateTo(targetView, "SizeHeight", 0.0f);
1218             verticalShadowAnimation.AnimateTo(targetView, "Opacity", 0.0f);
1219             verticalShadowAnimation.Play();
1220         }
1221
1222         private void StopVerticalShadowAnimation()
1223         {
1224             if (verticalShadowAnimation == null || verticalShadowAnimation.State != Animation.States.Playing)
1225                 return;
1226
1227             verticalShadowAnimation.Stop(Animation.EndActions.Cancel);
1228             OnVerticalShadowAnimationFinished(null, null);
1229             verticalShadowAnimation.Clear();
1230         }
1231
1232         private void OnVerticalShadowAnimationFinished(object sender, EventArgs e)
1233         {
1234             base.Remove(verticalTopShadowView);
1235             base.Remove(verticalBottomShadowView);
1236
1237             verticalTopShadowView.Size = new Size(SizeWidth, 0.0f);
1238             verticalBottomShadowView.Size = new Size(SizeWidth, 0.0f);
1239
1240             // after animation finished, height & opacity of vertical shadow both are 0, so it is invisible.
1241             isVerticalShadowShown = false;
1242         }
1243
1244         private void OnScrollOutOfBoundWithDisplacement(ScrollOutOfBoundWithDisplacementEventArgs.Direction direction, float displacement)
1245         {
1246             ScrollOutOfBoundWithDisplacementEventArgs args = new ScrollOutOfBoundWithDisplacementEventArgs(direction, displacement);
1247             ScrollOutOfBoundWithDisplacement?.Invoke(this, args);
1248         }
1249
1250         private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
1251         {
1252             OnPanGesture(e.PanGesture);
1253         }
1254
1255         private void OnPanGesture(PanGesture panGesture)
1256         {
1257             if (SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
1258             {
1259                 return;
1260             }
1261
1262             if (panGesture.State == Gesture.StateType.Started)
1263             {
1264                 readyToNotice = false;
1265                 base.Add(mInterruptTouchingChild);
1266                 AttachShadowView();
1267                 Debug.WriteLineIf(LayoutDebugScrollableBase, "Gesture Start");
1268                 if (scrolling && !SnapToPage)
1269                 {
1270                     StopScroll();
1271                 }
1272                 totalDisplacementForPan = 0.0f;
1273                 OnScrollDragStarted();
1274             }
1275             else if (panGesture.State == Gesture.StateType.Continuing)
1276             {
1277                 if (ScrollingDirection == Direction.Horizontal)
1278                 {
1279                     ScrollBy(panGesture.Displacement.X, false);
1280                     totalDisplacementForPan += panGesture.Displacement.X;
1281                 }
1282                 else
1283                 {
1284                     // if vertical shadow is shown, does not scroll.
1285                     if (!isVerticalShadowShown)
1286                     {
1287                         ScrollBy(panGesture.Displacement.Y, false);
1288                     }
1289                     totalDisplacementForPan += panGesture.Displacement.Y;
1290                     DragVerticalShadow(totalDisplacementForPan, panGesture.Displacement.Y);
1291                 }
1292                 Debug.WriteLineIf(LayoutDebugScrollableBase, "OnPanGestureDetected Continue totalDisplacementForPan:" + totalDisplacementForPan);
1293             }
1294             else if (panGesture.State == Gesture.StateType.Finished || panGesture.State == Gesture.StateType.Cancelled)
1295             {
1296                 PlayVerticalShadowAnimation();
1297                 OnScrollDragEnded();
1298                 StopScroll(); // Will replace previous animation so will stop existing one.
1299
1300                 if (scrollAnimation == null)
1301                 {
1302                     scrollAnimation = new Animation();
1303                     scrollAnimation.Finished += ScrollAnimationFinished;
1304                 }
1305
1306                 float panVelocity = (ScrollingDirection == Direction.Horizontal) ? panGesture.Velocity.X : panGesture.Velocity.Y;
1307
1308                 if (SnapToPage)
1309                 {
1310                     PageSnap(panVelocity);
1311                 }
1312                 else
1313                 {
1314                     if (panVelocity == 0)
1315                     {
1316                         float currentScrollPosition = (ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
1317                         scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
1318                         scrollAnimation.Duration = 0;
1319                         scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", currentScrollPosition);
1320                         scrollAnimation.Play();
1321                     }
1322                     else
1323                     {
1324                         Decelerating(panVelocity, scrollAnimation);
1325                     }
1326                 }
1327
1328                 totalDisplacementForPan = 0;
1329                 scrolling = true;
1330                 readyToNotice = true;
1331                 OnScrollAnimationStarted();
1332             }
1333         }
1334
1335         internal override bool OnAccessibilityPan(PanGesture gestures)
1336         {
1337             if (SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
1338             {
1339                 return false;
1340             }
1341
1342             OnPanGesture(gestures);
1343             return true;
1344         }
1345
1346         private float CustomScrollAlphaFunction(float progress)
1347         {
1348             if (panAnimationDelta == 0)
1349             {
1350                 return 1.0f;
1351             }
1352             else
1353             {
1354                 // Parameter "progress" is normalized value. We need to multiply target duration to calculate distance.
1355                 // Can get real distance using equation of deceleration (check Decelerating function)
1356                 // After get real distance, normalize it
1357                 float realDuration = progress * panAnimationDuration;
1358                 float realDistance = velocityOfLastPan * ((float)Math.Pow(decelerationRate, realDuration) - 1) / logValueOfDeceleration;
1359                 float result = Math.Min(realDistance / Math.Abs(panAnimationDelta), 1.0f);
1360                 return result;
1361             }
1362         }
1363
1364         /// <summary>
1365         /// you can override it to custom your decelerating
1366         /// </summary>
1367         /// <param name="velocity">Velocity of current pan.</param>
1368         /// <param name="animation">Scroll animation.</param>
1369         [EditorBrowsable(EditorBrowsableState.Never)]
1370         protected virtual void Decelerating(float velocity, Animation animation)
1371         {
1372             // Decelerating using deceleration equation ===========
1373             //
1374             // V   : velocity (pixel per milisecond)
1375             // V0  : initial velocity
1376             // d   : deceleration rate,
1377             // t   : time
1378             // X   : final position after decelerating
1379             // log : natural logarithm
1380             //
1381             // V(t) = V0 * d pow t;
1382             // X(t) = V0 * (d pow t - 1) / log d;  <-- Integrate the velocity function
1383             // X(∞) = V0 * d / (1 - d); <-- Result using inifit T can be final position because T is tending to infinity.
1384             //
1385             // Because of final T is tending to inifity, we should use threshold value to finish.
1386             // Final T = log(-threshold * log d / |V0| ) / log d;
1387
1388             velocityOfLastPan = Math.Abs(velocity);
1389
1390             float currentScrollPosition = -(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
1391             panAnimationDelta = (velocityOfLastPan * decelerationRate) / (1 - decelerationRate);
1392             panAnimationDelta = velocity > 0 ? -panAnimationDelta : panAnimationDelta;
1393
1394             float destination = -(panAnimationDelta + currentScrollPosition);
1395             float adjustDestination = AdjustTargetPositionOfScrollAnimation(destination);
1396             float maxPosition = ScrollAvailableArea != null ? ScrollAvailableArea.Y : maxScrollDistance;
1397             float minPosition = ScrollAvailableArea != null ? ScrollAvailableArea.X : 0;
1398
1399             if (destination < -maxPosition || destination > minPosition)
1400             {
1401                 panAnimationDelta = velocity > 0 ? (currentScrollPosition - minPosition) : (maxPosition - currentScrollPosition);
1402                 destination = velocity > 0 ? minPosition : -maxPosition;
1403
1404                 if (panAnimationDelta == 0)
1405                 {
1406                     panAnimationDuration = 0.0f;
1407                 }
1408                 else
1409                 {
1410                     panAnimationDuration = (float)Math.Log((panAnimationDelta * logValueOfDeceleration / velocityOfLastPan + 1), decelerationRate);
1411                 }
1412
1413                 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1414                     "OverRange======================= \n" +
1415                     "[decelerationRate] " + decelerationRate + "\n" +
1416                     "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1417                     "[Velocity] " + velocityOfLastPan + "\n" +
1418                     "[CurrentPosition] " + currentScrollPosition + "\n" +
1419                     "[CandidateDelta] " + panAnimationDelta + "\n" +
1420                     "[Destination] " + destination + "\n" +
1421                     "[Duration] " + panAnimationDuration + "\n" +
1422                     "================================ \n"
1423                 );
1424             }
1425             else
1426             {
1427                 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1428
1429                 if (adjustDestination != destination)
1430                 {
1431                     destination = adjustDestination;
1432                     panAnimationDelta = destination + currentScrollPosition;
1433                     velocityOfLastPan = Math.Abs(panAnimationDelta * logValueOfDeceleration / ((float)Math.Pow(decelerationRate, panAnimationDuration) - 1));
1434                     panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1435                 }
1436
1437                 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1438                     "================================ \n" +
1439                     "[decelerationRate] " + decelerationRate + "\n" +
1440                     "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1441                     "[Velocity] " + velocityOfLastPan + "\n" +
1442                     "[CurrentPosition] " + currentScrollPosition + "\n" +
1443                     "[CandidateDelta] " + panAnimationDelta + "\n" +
1444                     "[Destination] " + destination + "\n" +
1445                     "[Duration] " + panAnimationDuration + "\n" +
1446                     "================================ \n"
1447                 );
1448             }
1449
1450             finalTargetPosition = destination;
1451
1452             customScrollAlphaFunction = new UserAlphaFunctionDelegate(CustomScrollAlphaFunction);
1453             animation.DefaultAlphaFunction = new AlphaFunction(customScrollAlphaFunction);
1454             GC.KeepAlive(customScrollAlphaFunction);
1455             animation.Duration = (int)panAnimationDuration;
1456             animation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", destination);
1457             animation.Play();
1458         }
1459
1460         private void ScrollAnimationFinished(object sender, EventArgs e)
1461         {
1462             OnScrollAnimationEnded();
1463         }
1464
1465         /// <summary>
1466         /// Adjust scrolling position by own scrolling rules.
1467         /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1468         /// </summary>
1469         /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
1470         [EditorBrowsable(EditorBrowsableState.Never)]
1471         protected virtual float AdjustTargetPositionOfScrollAnimation(float position)
1472         {
1473             return position;
1474         }
1475
1476         /// <summary>
1477         /// Scroll position given to ScrollTo.
1478         /// This is the position in the opposite direction to the position of ContentContainer.
1479         /// </summary>
1480         /// <since_tizen> 8 </since_tizen>
1481         public Position ScrollPosition
1482         {
1483             get
1484             {
1485                 return new Position(-ContentContainer.Position);
1486             }
1487         }
1488
1489         /// <summary>
1490         /// Current scroll position in the middle of ScrollTo animation.
1491         /// This is the position in the opposite direction to the current position of ContentContainer.
1492         /// </summary>
1493         /// <since_tizen> 8 </since_tizen>
1494         public Position ScrollCurrentPosition
1495         {
1496             get
1497             {
1498                 return new Position(-ContentContainer.CurrentPosition);
1499             }
1500         }
1501
1502         /// <summary>
1503         /// Remove all children in ContentContainer.
1504         /// </summary>
1505         /// <param name="dispose">If true, removed child is disposed.</param>
1506         [EditorBrowsable(EditorBrowsableState.Never)]
1507         public void RemoveAllChildren(bool dispose = false)
1508         {
1509             RecursiveRemoveChildren(ContentContainer, dispose);
1510         }
1511
1512         private void RecursiveRemoveChildren(View parent, bool dispose)
1513         {
1514             if (parent == null)
1515             {
1516                 return;
1517             }
1518             int maxChild = (int)parent.GetChildCount();
1519             for (int i = maxChild - 1; i >= 0; --i)
1520             {
1521                 View child = parent.GetChildAt((uint)i);
1522                 if (child == null)
1523                 {
1524                     continue;
1525                 }
1526                 RecursiveRemoveChildren(child, dispose);
1527                 parent.Remove(child);
1528                 if (dispose)
1529                 {
1530                     child.Dispose();
1531                 }
1532             }
1533         }
1534
1535     }
1536
1537 } // namespace