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