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