1 /* Copyright (c) 2020 Samsung Electronics Co., Ltd.
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
7 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 using Tizen.NUI.BaseComponents;
18 using System.Collections.Generic;
19 using System.ComponentModel;
20 using System.Diagnostics;
21 using System.Runtime.InteropServices;
23 namespace Tizen.NUI.Components
26 /// ScrollEventArgs is a class to record scroll event arguments which will sent to user.
28 /// <since_tizen> 8 </since_tizen>
29 public class ScrollEventArgs : EventArgs
31 private Position position;
34 /// Default constructor.
36 /// <param name="position">Current scroll position</param>
37 /// <since_tizen> 8 </since_tizen>
38 public ScrollEventArgs(Position position)
40 this.position = position;
44 /// Current position of ContentContainer.
46 /// <since_tizen> 8 </since_tizen>
47 public Position Position
57 /// This class provides a View that can scroll a single View with a layout. This View can be a nest of Views.
59 /// <since_tizen> 8 </since_tizen>
60 public class ScrollableBase : Control
62 static bool LayoutDebugScrollableBase = false; // Debug flag
63 private Direction mScrollingDirection = Direction.Vertical;
64 private bool mScrollEnabled = true;
65 private int mScrollDuration = 125;
66 private int mPageWidth = 0;
67 private float mPageFlickThreshold = 0.4f;
69 private class ScrollableBaseCustomLayout : LayoutGroup
71 protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
73 Extents padding = Padding;
74 float totalHeight = padding.Top + padding.Bottom;
75 float totalWidth = padding.Start + padding.End;
77 MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK;
78 MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK;
80 Direction scrollingDirection = Direction.Vertical;
81 ScrollableBase scrollableBase = this.Owner as ScrollableBase;
84 scrollingDirection = scrollableBase.ScrollingDirection;
87 // measure child, should be a single scrolling child
88 foreach (LayoutItem childLayout in LayoutChildren)
90 if (childLayout != null)
93 // Use an Unspecified MeasureSpecification mode so scrolling child is not restricted to it's parents size in Height (for vertical scrolling)
94 // or Width for horizontal scrolling
95 MeasureSpecification unrestrictedMeasureSpec = new MeasureSpecification(heightMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
97 if (scrollingDirection == Direction.Vertical)
99 MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), unrestrictedMeasureSpec, new LayoutLength(0)); // Height unrestricted by parent
103 MeasureChildWithMargins(childLayout, unrestrictedMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0)); // Width unrestricted by parent
106 float childWidth = childLayout.MeasuredWidth.Size.AsDecimal();
107 float childHeight = childLayout.MeasuredHeight.Size.AsDecimal();
109 // Determine the width and height needed by the children using their given position and size.
110 // Children could overlap so find the left most and right most child.
111 Position2D childPosition = childLayout.Owner.Position2D;
112 float childLeft = childPosition.X;
113 float childTop = childPosition.Y;
115 // Store current width and height needed to contain all children.
116 Extents childMargin = childLayout.Margin;
117 totalWidth = childWidth + childMargin.Start + childMargin.End;
118 totalHeight = childHeight + childMargin.Top + childMargin.Bottom;
120 if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
122 childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
124 if (childLayout.MeasuredHeight.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
126 childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
132 MeasuredSize widthSizeAndState = ResolveSizeAndState(new LayoutLength(totalWidth + Padding.Start + Padding.End), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
133 MeasuredSize heightSizeAndState = ResolveSizeAndState(new LayoutLength(totalHeight + Padding.Top + Padding.Bottom), heightMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
134 totalWidth = widthSizeAndState.Size.AsDecimal();
135 totalHeight = heightSizeAndState.Size.AsDecimal();
137 // Ensure layout respects it's given minimum size
138 totalWidth = Math.Max(totalWidth, SuggestedMinimumWidth.AsDecimal());
139 totalHeight = Math.Max(totalHeight, SuggestedMinimumHeight.AsDecimal());
141 widthSizeAndState.State = childWidthState;
142 heightSizeAndState.State = childHeightState;
144 SetMeasuredDimensions(ResolveSizeAndState(new LayoutLength(totalWidth + Padding.Start + Padding.End), widthMeasureSpec, childWidthState),
145 ResolveSizeAndState(new LayoutLength(totalHeight + Padding.Top + Padding.Bottom), heightMeasureSpec, childHeightState));
147 // Size of ScrollableBase is changed. Change Page width too.
148 scrollableBase.mPageWidth = (int)MeasuredWidth.Size.AsRoundedValue();
151 protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
153 foreach (LayoutItem childLayout in LayoutChildren)
155 if (childLayout != null)
157 LayoutLength childWidth = childLayout.MeasuredWidth.Size;
158 LayoutLength childHeight = childLayout.MeasuredHeight.Size;
160 Position2D childPosition = childLayout.Owner.Position2D;
161 Extents padding = Padding;
162 Extents childMargin = childLayout.Margin;
164 LayoutLength childLeft = new LayoutLength(childPosition.X + childMargin.Start + padding.Start);
165 LayoutLength childTop = new LayoutLength(childPosition.Y + childMargin.Top + padding.Top);
167 childLayout.Layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
171 } // ScrollableBaseCustomLayout
174 /// The direction axis to scroll.
176 /// <since_tizen> 8 </since_tizen>
177 public enum Direction
182 /// <since_tizen> 8 </since_tizen>
188 /// <since_tizen> 8 </since_tizen>
193 /// Scrolling direction mode.
194 /// Default is Vertical scrolling.
196 /// <since_tizen> 8 </since_tizen>
197 public Direction ScrollingDirection
201 return mScrollingDirection;
205 if (value != mScrollingDirection)
207 mScrollingDirection = value;
208 mPanGestureDetector.RemoveDirection(value == Direction.Horizontal ?
209 PanGestureDetector.DirectionVertical : PanGestureDetector.DirectionHorizontal);
210 mPanGestureDetector.AddDirection(value == Direction.Horizontal ?
211 PanGestureDetector.DirectionHorizontal : PanGestureDetector.DirectionVertical);
213 ContentContainer.WidthSpecification = mScrollingDirection == Direction.Vertical ?
214 LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent;
215 ContentContainer.HeightSpecification = mScrollingDirection == Direction.Vertical ?
216 LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent;
222 /// Enable or disable scrolling.
224 /// <since_tizen> 8 </since_tizen>
225 public bool ScrollEnabled
229 return mScrollEnabled;
233 if (value != mScrollEnabled)
235 mScrollEnabled = value;
238 mPanGestureDetector.Detected += OnPanGestureDetected;
242 mPanGestureDetector.Detected -= OnPanGestureDetected;
249 /// Pages mode, enables moving to the next or return to current page depending on pan displacement.
250 /// Default is false.
252 /// <since_tizen> 8 </since_tizen>
253 public bool SnapToPage { set; get; } = false;
256 /// Get current page.
257 /// Working property with SnapToPage property.
259 /// <since_tizen> 8 </since_tizen>
260 public int CurrentPage { get; private set; } = 0;
263 /// Duration of scroll animation.
264 /// Default value is 125ms.
266 /// <since_tizen> 8 </since_tizen>
267 public int ScrollDuration
271 mScrollDuration = value >= 0 ? value : mScrollDuration;
275 return mScrollDuration;
280 /// Scroll Available area.
282 /// <since_tizen> 8 </since_tizen>
283 public Vector2 ScrollAvailableArea { set; get; }
286 /// An event emitted when user starts dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
288 /// <since_tizen> 8 </since_tizen>
289 public event EventHandler<ScrollEventArgs> ScrollDragStarted;
292 /// An event emitted when user stops dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
294 /// <since_tizen> 8 </since_tizen>
295 public event EventHandler<ScrollEventArgs> ScrollDragEnded;
299 /// An event emitted when the scrolling slide animation starts, user can subscribe or unsubscribe to this event handler.<br />
301 /// <since_tizen> 8 </since_tizen>
302 public event EventHandler<ScrollEventArgs> ScrollAnimationStarted;
305 /// An event emitted when the scrolling slide animation ends, user can subscribe or unsubscribe to this event handler.<br />
307 /// <since_tizen> 8 </since_tizen>
308 public event EventHandler<ScrollEventArgs> ScrollAnimationEnded;
312 /// An event emitted when scrolling, user can subscribe or unsubscribe to this event handler.<br />
314 /// <since_tizen> 8 </since_tizen>
315 public event EventHandler<ScrollEventArgs> Scrolling;
319 /// Scrollbar for ScrollableBase.
321 /// <since_tizen> 8 </since_tizen>
322 public ScrollbarBase Scrollbar
332 scrollBar.Unparent();
336 scrollBar.Name = "ScrollBar";
353 /// Always hide Scrollbar.
355 /// <since_tizen> 8 </since_tizen>
356 public bool HideScrollbar
360 return hideScrollbar;
364 hideScrollbar = value;
381 /// Container which has content of ScrollableBase.
383 /// <since_tizen> 8 </since_tizen>
384 public View ContentContainer { get; private set; }
387 /// Set the layout on this View. Replaces any existing Layout.
389 /// <since_tizen> 8 </since_tizen>
390 public new LayoutItem Layout
394 return ContentContainer.Layout;
398 ContentContainer.Layout = value;
399 if (ContentContainer.Layout != null)
401 ContentContainer.Layout.SetPositionByLayout = false;
407 /// List of children of Container.
409 /// <since_tizen> 8 </since_tizen>
410 public new List<View> Children
414 return ContentContainer.Children;
419 /// Deceleration rate of scrolling by finger.
420 /// Rate should be bigger than 0 and smaller than 1.
421 /// Default value is 0.998f;
423 /// <since_tizen> 8 </since_tizen>
424 public float DecelerationRate
428 return decelerationRate;
432 decelerationRate = (value < 1 && value > 0) ? value : decelerationRate;
433 logValueOfDeceleration = (float)Math.Log(value);
438 /// Threashold not to go infinit at the end of scrolling animation.
440 [EditorBrowsable(EditorBrowsableState.Never)]
441 public float DecelerationThreshold { get; set; } = 0.1f;
444 /// Page will be changed when velocity of panning is over threshold.
445 /// The unit of threshold is pixel per milisec.
447 /// <since_tizen> 8 </since_tizen>
448 public float PageFlickThreshold
452 return mPageFlickThreshold;
456 mPageFlickThreshold = value >= 0f ? value : mPageFlickThreshold;
461 /// Alphafunction for scroll animation.
463 [EditorBrowsable(EditorBrowsableState.Never)]
464 public AlphaFunction ScrollAlphaFunction { get; set; } = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
466 private bool hideScrollbar = true;
467 private float maxScrollDistance;
468 private float childTargetPosition = 0.0f;
469 private PanGestureDetector mPanGestureDetector;
470 private View mInterruptTouchingChild;
471 private ScrollbarBase scrollBar;
472 private bool scrolling = false;
473 private float ratioOfScreenWidthToCompleteScroll = 0.5f;
474 private float totalDisplacementForPan = 0.0f;
475 private Size previousContainerSize = new Size();
476 private PropertyNotification propertyNotification;
477 private float noticeAnimationEndBeforePosition = 0.0f;
478 private bool readyToNotice = false;
481 /// Notice before animation is finished.
483 [EditorBrowsable(EditorBrowsableState.Never)]
484 // Let's consider more whether this needs to be set as protected.
485 public float NoticeAnimationEndBeforePosition { get => noticeAnimationEndBeforePosition; set => noticeAnimationEndBeforePosition = value; }
487 // Let's consider more whether this needs to be set as protected.
488 private float finalTargetPosition;
490 private Animation scrollAnimation;
491 // Declare user alpha function delegate
492 [UnmanagedFunctionPointer(CallingConvention.StdCall)]
493 private delegate float UserAlphaFunctionDelegate(float progress);
494 private UserAlphaFunctionDelegate customScrollAlphaFunction;
495 private float velocityOfLastPan = 0.0f;
496 private float panAnimationDuration = 0.0f;
497 private float panAnimationDelta = 0.0f;
498 private float logValueOfDeceleration = 0.0f;
499 private float decelerationRate = 0.0f;
502 /// Default Constructor
504 /// <since_tizen> 8 </since_tizen>
505 public ScrollableBase() : base()
507 DecelerationRate = 0.998f;
509 base.Layout = new ScrollableBaseCustomLayout();
510 mPanGestureDetector = new PanGestureDetector();
511 mPanGestureDetector.Attach(this);
512 mPanGestureDetector.AddDirection(PanGestureDetector.DirectionVertical);
513 mPanGestureDetector.Detected += OnPanGestureDetected;
515 ClippingMode = ClippingModeType.ClipChildren;
517 //Default Scrolling child
518 ContentContainer = new View()
520 WidthSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent,
521 HeightSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent,
522 Layout = new AbsoluteLayout() { SetPositionByLayout = false },
524 ContentContainer.Relayout += OnScrollingChildRelayout;
525 propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(1.0f));
526 propertyNotification.Notified += OnPropertyChanged;
527 base.Add(ContentContainer);
529 //Interrupt touching when panning is started
530 mInterruptTouchingChild = new View()
532 Size = new Size(Window.Instance.WindowSize),
533 BackgroundColor = Color.Transparent,
535 mInterruptTouchingChild.TouchEvent += OnIterruptTouchingChildTouched;
536 Scrollbar = new Scrollbar();
539 private bool OnIterruptTouchingChildTouched(object source, View.TouchEventArgs args)
541 if (args.Touch.GetState(0) == PointStateType.Down)
543 if (scrolling && !SnapToPage)
551 private void OnPropertyChanged(object source, PropertyNotification.NotifyEventArgs args)
557 /// Called after a child has been added to the owning view.
559 /// <param name="view">The child which has been added.</param>
560 /// <since_tizen> 8 </since_tizen>
561 public override void Add(View view)
563 ContentContainer.Add(view);
567 /// Called after a child has been removed from the owning view.
569 /// <param name="view">The child which has been removed.</param>
570 /// <since_tizen> 8 </since_tizen>
571 public override void Remove(View view)
573 if (SnapToPage && CurrentPage == Children.IndexOf(view) && CurrentPage == Children.Count - 1)
575 // Target View is current page and also last child.
576 // CurrentPage should be changed to previous page.
577 CurrentPage = Math.Max(0, CurrentPage - 1);
578 ScrollToIndex(CurrentPage);
581 ContentContainer.Remove(view);
584 private void OnScrollingChildRelayout(object source, EventArgs args)
586 // Size is changed. Calculate maxScrollDistance.
587 bool isSizeChanged = previousContainerSize.Width != ContentContainer.Size.Width || previousContainerSize.Height != ContentContainer.Size.Height;
591 maxScrollDistance = CalculateMaximumScrollDistance();
595 previousContainerSize = ContentContainer.Size;
599 /// The composition of a Scrollbar can vary depending on how you use ScrollableBase.
600 /// Set the composition that will go into the ScrollableBase according to your ScrollableBase.
602 /// <since_tizen> 8 </since_tizen>
603 [EditorBrowsable(EditorBrowsableState.Never)]
604 protected virtual void SetScrollbar()
608 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
609 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
610 float viewportLength = isHorizontal ? Size.Width : Size.Height;
611 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
612 Scrollbar.Initialize(contentLength, viewportLength, currentPosition, isHorizontal);
617 /// Scrolls to the item at the specified index.
619 /// <param name="index">Index of item.</param>
620 /// <since_tizen> 8 </since_tizen>
621 public void ScrollToIndex(int index)
623 if (ContentContainer.ChildCount - 1 < index || index < 0)
633 float targetPosition = Math.Min(ScrollingDirection == Direction.Vertical ? Children[index].Position.Y : Children[index].Position.X, maxScrollDistance);
634 AnimateChildTo(ScrollDuration, -targetPosition);
637 private void OnScrollDragStarted()
639 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
640 ScrollDragStarted?.Invoke(this, eventArgs);
643 private void OnScrollDragEnded()
645 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
646 ScrollDragEnded?.Invoke(this, eventArgs);
649 private void OnScrollAnimationStarted()
651 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
652 ScrollAnimationStarted?.Invoke(this, eventArgs);
655 private void OnScrollAnimationEnded()
658 base.Remove(mInterruptTouchingChild);
660 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
661 ScrollAnimationEnded?.Invoke(this, eventArgs);
664 private void OnScroll()
666 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
667 Scrolling?.Invoke(this, eventArgs);
669 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
670 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
671 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
673 scrollBar.Update(contentLength, Math.Abs(currentPosition));
674 CheckPreReachedTargetPosition();
677 private void CheckPreReachedTargetPosition()
679 // Check whether we reached pre-reached target position
681 ContentContainer.CurrentPosition.Y <= finalTargetPosition + NoticeAnimationEndBeforePosition &&
682 ContentContainer.CurrentPosition.Y >= finalTargetPosition - NoticeAnimationEndBeforePosition)
685 readyToNotice = false;
686 OnPreReachedTargetPosition(finalTargetPosition);
691 /// This helps developer who wants to know before scroll is reaching target position.
693 /// <param name="targetPosition">Index of item.</param>
694 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
695 [EditorBrowsable(EditorBrowsableState.Never)]
696 protected virtual void OnPreReachedTargetPosition(float targetPosition)
701 private void StopScroll()
703 if (scrollAnimation != null)
705 if (scrollAnimation.State == Animation.States.Playing)
707 Debug.WriteLineIf(LayoutDebugScrollableBase, "StopScroll Animation Playing");
708 scrollAnimation.Stop(Animation.EndActions.Cancel);
709 OnScrollAnimationEnded();
711 scrollAnimation.Clear();
715 private void AnimateChildTo(int duration, float axisPosition)
717 Debug.WriteLineIf(LayoutDebugScrollableBase, "AnimationTo Animation Duration:" + duration + " Destination:" + axisPosition);
718 finalTargetPosition = axisPosition;
720 StopScroll(); // Will replace previous animation so will stop existing one.
722 if (scrollAnimation == null)
724 scrollAnimation = new Animation();
725 scrollAnimation.Finished += ScrollAnimationFinished;
728 scrollAnimation.Duration = duration;
729 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSquare);
730 scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", axisPosition, ScrollAlphaFunction);
732 OnScrollAnimationStarted();
733 scrollAnimation.Play();
737 /// Scroll to specific position with or without animation.
739 /// <param name="position">Destination.</param>
740 /// <param name="animate">Scroll with or without animation</param>
741 /// <since_tizen> 8 </since_tizen>
742 public void ScrollTo(float position, bool animate)
744 float currentPositionX = ContentContainer.CurrentPosition.X != 0 ? ContentContainer.CurrentPosition.X : ContentContainer.Position.X;
745 float currentPositionY = ContentContainer.CurrentPosition.Y != 0 ? ContentContainer.CurrentPosition.Y : ContentContainer.Position.Y;
746 float delta = ScrollingDirection == Direction.Horizontal ? currentPositionX : currentPositionY;
747 // The argument position is the new pan position. So the new position of ScrollableBase becomes (-position).
748 // To move ScrollableBase's position to (-position), it moves by (-position - currentPosition).
749 delta = -position - delta;
751 ScrollBy(delta, animate);
754 private float BoundScrollPosition(float targetPosition)
756 if (ScrollAvailableArea != null)
758 float minScrollPosition = ScrollAvailableArea.X;
759 float maxScrollPosition = ScrollAvailableArea.Y;
761 targetPosition = Math.Min(-minScrollPosition, targetPosition);
762 targetPosition = Math.Max(-maxScrollPosition, targetPosition);
766 targetPosition = Math.Min(0, targetPosition);
767 targetPosition = Math.Max(-maxScrollDistance, targetPosition);
770 return targetPosition;
773 private void ScrollBy(float displacement, bool animate)
775 if (GetChildCount() == 0 || maxScrollDistance < 0)
780 float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? ContentContainer.PositionX : ContentContainer.PositionY;
782 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy childCurrentPosition:" + childCurrentPosition +
783 " displacement:" + displacement,
784 " maxScrollDistance:" + maxScrollDistance);
786 childTargetPosition = childCurrentPosition + displacement; // child current position + gesture displacement
789 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition);
793 // Calculate scroll animaton duration
794 float scrollDistance = Math.Abs(displacement);
795 readyToNotice = true;
797 AnimateChildTo(ScrollDuration, BoundScrollPosition(AdjustTargetPositionOfScrollAnimation(BoundScrollPosition(childTargetPosition))));
801 finalTargetPosition = BoundScrollPosition(childTargetPosition);
803 // Set position of scrolling child without an animation
804 if (ScrollingDirection == Direction.Horizontal)
806 ContentContainer.PositionX = finalTargetPosition;
810 ContentContainer.PositionY = finalTargetPosition;
817 /// you can override it to clean-up your own resources.
819 /// <param name="type">DisposeTypes</param>
820 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
821 [EditorBrowsable(EditorBrowsableState.Never)]
822 protected override void Dispose(DisposeTypes type)
829 if (type == DisposeTypes.Explicit)
833 if (mPanGestureDetector != null)
835 mPanGestureDetector.Detected -= OnPanGestureDetected;
836 mPanGestureDetector.Dispose();
837 mPanGestureDetector = null;
843 private float CalculateMaximumScrollDistance()
845 float scrollingChildLength = 0;
846 float scrollerLength = 0;
847 if (ScrollingDirection == Direction.Horizontal)
849 Debug.WriteLineIf(LayoutDebugScrollableBase, "Horizontal");
851 scrollingChildLength = ContentContainer.Size.Width;
852 scrollerLength = Size.Width;
856 Debug.WriteLineIf(LayoutDebugScrollableBase, "Vertical");
857 scrollingChildLength = ContentContainer.Size.Height;
858 scrollerLength = Size.Height;
861 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy maxScrollDistance:" + (scrollingChildLength - scrollerLength) +
862 " parent length:" + scrollerLength +
863 " scrolling child length:" + scrollingChildLength);
865 return Math.Max(scrollingChildLength - scrollerLength, 0);
868 private void PageSnap(float velocity)
870 Debug.WriteLineIf(LayoutDebugScrollableBase, "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan +
871 " currentPage[" + CurrentPage + "]");
873 //Increment current page if total displacement enough to warrant a page change.
874 if (Math.Abs(totalDisplacementForPan) > (mPageWidth * ratioOfScreenWidthToCompleteScroll))
876 if (totalDisplacementForPan < 0)
878 CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
882 CurrentPage = Math.Max(0, --CurrentPage);
885 else if (Math.Abs(velocity) > PageFlickThreshold)
889 CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
893 CurrentPage = Math.Max(0, --CurrentPage);
897 // Animate to new page or reposition to current page
898 float destinationX = -(Children[CurrentPage].Position.X + Children[CurrentPage].CurrentSize.Width / 2 - CurrentSize.Width / 2); // set to middle of current page
899 Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to page[" + CurrentPage + "] to:" + destinationX + " from:" + ContentContainer.PositionX);
900 AnimateChildTo(ScrollDuration, destinationX);
903 private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
905 OnPanGesture(e.PanGesture);
908 private void OnPanGesture(PanGesture panGesture)
910 if (SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
915 if (panGesture.State == Gesture.StateType.Started)
917 readyToNotice = false;
918 base.Add(mInterruptTouchingChild);
919 Debug.WriteLineIf(LayoutDebugScrollableBase, "Gesture Start");
920 if (scrolling && !SnapToPage)
924 totalDisplacementForPan = 0.0f;
925 OnScrollDragStarted();
927 else if (panGesture.State == Gesture.StateType.Continuing)
929 if (ScrollingDirection == Direction.Horizontal)
931 ScrollBy(panGesture.Displacement.X, false);
932 totalDisplacementForPan += panGesture.Displacement.X;
936 ScrollBy(panGesture.Displacement.Y, false);
937 totalDisplacementForPan += panGesture.Displacement.Y;
939 Debug.WriteLineIf(LayoutDebugScrollableBase, "OnPanGestureDetected Continue totalDisplacementForPan:" + totalDisplacementForPan);
941 else if (panGesture.State == Gesture.StateType.Finished)
944 StopScroll(); // Will replace previous animation so will stop existing one.
946 if (scrollAnimation == null)
948 scrollAnimation = new Animation();
949 scrollAnimation.Finished += ScrollAnimationFinished;
952 float panVelocity = (ScrollingDirection == Direction.Horizontal) ? panGesture.Velocity.X : panGesture.Velocity.Y;
956 PageSnap(panVelocity);
960 if (panVelocity == 0)
962 float currentScrollPosition = (ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
963 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
964 scrollAnimation.Duration = 0;
965 scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", currentScrollPosition);
966 scrollAnimation.Play();
970 Decelerating(panVelocity, scrollAnimation);
974 totalDisplacementForPan = 0;
976 readyToNotice = true;
977 OnScrollAnimationStarted();
981 internal override bool OnAccessibilityPan(PanGesture gestures)
983 if (SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
988 OnPanGesture(gestures);
992 private float CustomScrollAlphaFunction(float progress)
994 if (panAnimationDelta == 0)
1000 // Parameter "progress" is normalized value. We need to multiply target duration to calculate distance.
1001 // Can get real distance using equation of deceleration (check Decelerating function)
1002 // After get real distance, normalize it
1003 float realDuration = progress * panAnimationDuration;
1004 float realDistance = velocityOfLastPan * ((float)Math.Pow(decelerationRate, realDuration) - 1) / logValueOfDeceleration;
1005 float result = Math.Min(realDistance / Math.Abs(panAnimationDelta), 1.0f);
1011 /// you can override it to custom your decelerating
1013 /// <param name="velocity">Velocity of current pan.</param>
1014 /// <param name="animation">Scroll animation.</param>
1015 [EditorBrowsable(EditorBrowsableState.Never)]
1016 protected virtual void Decelerating(float velocity, Animation animation)
1018 // Decelerating using deceleration equation ===========
1020 // V : velocity (pixel per milisecond)
1021 // V0 : initial velocity
1022 // d : deceleration rate,
1024 // X : final position after decelerating
1025 // log : natural logarithm
1027 // V(t) = V0 * d pow t;
1028 // X(t) = V0 * (d pow t - 1) / log d; <-- Integrate the velocity function
1029 // X(∞) = V0 * d / (1 - d); <-- Result using inifit T can be final position because T is tending to infinity.
1031 // Because of final T is tending to inifity, we should use threshold value to finish.
1032 // Final T = log(-threshold * log d / |V0| ) / log d;
1034 velocityOfLastPan = Math.Abs(velocity);
1036 float currentScrollPosition = -(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
1037 panAnimationDelta = (velocityOfLastPan * decelerationRate) / (1 - decelerationRate);
1038 panAnimationDelta = velocity > 0 ? -panAnimationDelta : panAnimationDelta;
1040 float destination = -(panAnimationDelta + currentScrollPosition);
1041 float adjustDestination = AdjustTargetPositionOfScrollAnimation(destination);
1042 float maxPosition = ScrollAvailableArea != null ? ScrollAvailableArea.Y : maxScrollDistance;
1043 float minPosition = ScrollAvailableArea != null ? ScrollAvailableArea.X : 0;
1045 if (destination < -maxPosition || destination > minPosition)
1047 panAnimationDelta = velocity > 0 ? (currentScrollPosition - minPosition) : (maxPosition - currentScrollPosition);
1048 destination = velocity > 0 ? minPosition : -maxPosition;
1050 if (panAnimationDelta == 0)
1052 panAnimationDuration = 0.0f;
1056 panAnimationDuration = (float)Math.Log((panAnimationDelta * logValueOfDeceleration / velocityOfLastPan + 1), decelerationRate);
1059 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1060 "OverRange======================= \n" +
1061 "[decelerationRate] " + decelerationRate + "\n" +
1062 "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1063 "[Velocity] " + velocityOfLastPan + "\n" +
1064 "[CurrentPosition] " + currentScrollPosition + "\n" +
1065 "[CandidateDelta] " + panAnimationDelta + "\n" +
1066 "[Destination] " + destination + "\n" +
1067 "[Duration] " + panAnimationDuration + "\n" +
1068 "================================ \n"
1073 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1075 if (adjustDestination != destination)
1077 destination = adjustDestination;
1078 panAnimationDelta = destination + currentScrollPosition;
1079 velocityOfLastPan = Math.Abs(panAnimationDelta * logValueOfDeceleration / ((float)Math.Pow(decelerationRate, panAnimationDuration) - 1));
1080 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1083 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1084 "================================ \n" +
1085 "[decelerationRate] " + decelerationRate + "\n" +
1086 "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1087 "[Velocity] " + velocityOfLastPan + "\n" +
1088 "[CurrentPosition] " + currentScrollPosition + "\n" +
1089 "[CandidateDelta] " + panAnimationDelta + "\n" +
1090 "[Destination] " + destination + "\n" +
1091 "[Duration] " + panAnimationDuration + "\n" +
1092 "================================ \n"
1096 finalTargetPosition = destination;
1098 customScrollAlphaFunction = new UserAlphaFunctionDelegate(CustomScrollAlphaFunction);
1099 animation.DefaultAlphaFunction = new AlphaFunction(customScrollAlphaFunction);
1100 GC.KeepAlive(customScrollAlphaFunction);
1101 animation.Duration = (int)panAnimationDuration;
1102 animation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", destination);
1106 private void ScrollAnimationFinished(object sender, EventArgs e)
1108 OnScrollAnimationEnded();
1112 /// Adjust scrolling position by own scrolling rules.
1113 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1115 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
1116 [EditorBrowsable(EditorBrowsableState.Never)]
1117 protected virtual float AdjustTargetPositionOfScrollAnimation(float position)
1123 /// Scroll position given to ScrollTo.
1124 /// This is the position in the opposite direction to the position of ContentContainer.
1126 /// <since_tizen> 8 </since_tizen>
1127 public Position ScrollPosition
1131 return new Position(-ContentContainer.Position);
1136 /// Current scroll position in the middle of ScrollTo animation.
1137 /// This is the position in the opposite direction to the current position of ContentContainer.
1139 /// <since_tizen> 8 </since_tizen>
1140 public Position ScrollCurrentPosition
1144 return new Position(-ContentContainer.CurrentPosition);