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;
22 using Tizen.NUI.Accessibility;
24 namespace Tizen.NUI.Components
27 /// ScrollEventArgs is a class to record scroll event arguments which will sent to user.
29 /// <since_tizen> 8 </since_tizen>
30 public class ScrollEventArgs : EventArgs
32 private Position position;
35 /// Default constructor.
37 /// <param name="position">Current scroll position</param>
38 /// <since_tizen> 8 </since_tizen>
39 public ScrollEventArgs(Position position)
41 this.position = position;
45 /// Current position of ContentContainer.
47 /// <since_tizen> 8 </since_tizen>
48 public Position Position
58 /// This class provides a View that can scroll a single View with a layout. This View can be a nest of Views.
60 /// <since_tizen> 8 </since_tizen>
61 public class ScrollableBase : Control
63 static bool LayoutDebugScrollableBase = false; // Debug flag
64 private Direction mScrollingDirection = Direction.Vertical;
65 private bool mScrollEnabled = true;
66 private int mScrollDuration = 125;
67 private int mPageWidth = 0;
68 private float mPageFlickThreshold = 0.4f;
69 private float mScrollingEventThreshold = 0.00001f;
71 private class ScrollableBaseCustomLayout : LayoutGroup
73 protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
75 Extents padding = Padding;
76 float totalHeight = padding.Top + padding.Bottom;
77 float totalWidth = padding.Start + padding.End;
79 MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK;
80 MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK;
82 Direction scrollingDirection = Direction.Vertical;
83 ScrollableBase scrollableBase = this.Owner as ScrollableBase;
86 scrollingDirection = scrollableBase.ScrollingDirection;
89 // measure child, should be a single scrolling child
90 foreach (LayoutItem childLayout in LayoutChildren)
92 if (childLayout != null)
95 // Use an Unspecified MeasureSpecification mode so scrolling child is not restricted to it's parents size in Height (for vertical scrolling)
96 // or Width for horizontal scrolling
97 MeasureSpecification unrestrictedMeasureSpec = new MeasureSpecification(heightMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
99 if (scrollingDirection == Direction.Vertical)
101 MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), unrestrictedMeasureSpec, new LayoutLength(0)); // Height unrestricted by parent
105 MeasureChildWithMargins(childLayout, unrestrictedMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0)); // Width unrestricted by parent
108 float childWidth = childLayout.MeasuredWidth.Size.AsDecimal();
109 float childHeight = childLayout.MeasuredHeight.Size.AsDecimal();
111 // Determine the width and height needed by the children using their given position and size.
112 // Children could overlap so find the left most and right most child.
113 Position2D childPosition = childLayout.Owner.Position2D;
114 float childLeft = childPosition.X;
115 float childTop = childPosition.Y;
117 // Store current width and height needed to contain all children.
118 Extents childMargin = childLayout.Margin;
119 totalWidth = childWidth + childMargin.Start + childMargin.End;
120 totalHeight = childHeight + childMargin.Top + childMargin.Bottom;
122 if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
124 childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
126 if (childLayout.MeasuredHeight.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
128 childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
134 MeasuredSize widthSizeAndState = ResolveSizeAndState(new LayoutLength(totalWidth + Padding.Start + Padding.End), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
135 MeasuredSize heightSizeAndState = ResolveSizeAndState(new LayoutLength(totalHeight + Padding.Top + Padding.Bottom), heightMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
136 totalWidth = widthSizeAndState.Size.AsDecimal();
137 totalHeight = heightSizeAndState.Size.AsDecimal();
139 // Ensure layout respects it's given minimum size
140 totalWidth = Math.Max(totalWidth, SuggestedMinimumWidth.AsDecimal());
141 totalHeight = Math.Max(totalHeight, SuggestedMinimumHeight.AsDecimal());
143 widthSizeAndState.State = childWidthState;
144 heightSizeAndState.State = childHeightState;
146 SetMeasuredDimensions(ResolveSizeAndState(new LayoutLength(totalWidth + Padding.Start + Padding.End), widthMeasureSpec, childWidthState),
147 ResolveSizeAndState(new LayoutLength(totalHeight + Padding.Top + Padding.Bottom), heightMeasureSpec, childHeightState));
149 // Size of ScrollableBase is changed. Change Page width too.
150 scrollableBase.mPageWidth = (int)MeasuredWidth.Size.AsRoundedValue();
153 protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
155 foreach (LayoutItem childLayout in LayoutChildren)
157 if (childLayout != null)
159 LayoutLength childWidth = childLayout.MeasuredWidth.Size;
160 LayoutLength childHeight = childLayout.MeasuredHeight.Size;
162 Position2D childPosition = childLayout.Owner.Position2D;
163 Extents padding = Padding;
164 Extents childMargin = childLayout.Margin;
166 LayoutLength childLeft = new LayoutLength(childPosition.X + childMargin.Start + padding.Start);
167 LayoutLength childTop = new LayoutLength(childPosition.Y + childMargin.Top + padding.Top);
169 childLayout.Layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
173 } // ScrollableBaseCustomLayout
176 /// The direction axis to scroll.
178 /// <since_tizen> 8 </since_tizen>
179 public enum Direction
184 /// <since_tizen> 8 </since_tizen>
190 /// <since_tizen> 8 </since_tizen>
195 /// Scrolling direction mode.
196 /// Default is Vertical scrolling.
198 /// <since_tizen> 8 </since_tizen>
199 public Direction ScrollingDirection
203 return mScrollingDirection;
207 if (value != mScrollingDirection)
209 mScrollingDirection = value;
210 mPanGestureDetector.RemoveDirection(value == Direction.Horizontal ?
211 PanGestureDetector.DirectionVertical : PanGestureDetector.DirectionHorizontal);
212 mPanGestureDetector.AddDirection(value == Direction.Horizontal ?
213 PanGestureDetector.DirectionHorizontal : PanGestureDetector.DirectionVertical);
215 ContentContainer.WidthSpecification = mScrollingDirection == Direction.Vertical ?
216 LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent;
217 ContentContainer.HeightSpecification = mScrollingDirection == Direction.Vertical ?
218 LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent;
224 /// Enable or disable scrolling.
226 /// <since_tizen> 8 </since_tizen>
227 public bool ScrollEnabled
231 return mScrollEnabled;
235 if (value != mScrollEnabled)
237 mScrollEnabled = value;
240 mPanGestureDetector.Detected += OnPanGestureDetected;
244 mPanGestureDetector.Detected -= OnPanGestureDetected;
251 /// Pages mode, enables moving to the next or return to current page depending on pan displacement.
252 /// Default is false.
254 /// <since_tizen> 8 </since_tizen>
255 public bool SnapToPage { set; get; } = false;
258 /// Get current page.
259 /// Working property with SnapToPage property.
261 /// <since_tizen> 8 </since_tizen>
262 public int CurrentPage { get; private set; } = 0;
265 /// Duration of scroll animation.
266 /// Default value is 125ms.
268 /// <since_tizen> 8 </since_tizen>
269 public int ScrollDuration
273 mScrollDuration = value >= 0 ? value : mScrollDuration;
277 return mScrollDuration;
282 /// Scroll Available area.
284 /// <since_tizen> 8 </since_tizen>
285 public Vector2 ScrollAvailableArea { set; get; }
288 /// An event emitted when user starts dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
290 /// <since_tizen> 8 </since_tizen>
291 public event EventHandler<ScrollEventArgs> ScrollDragStarted;
294 /// An event emitted when user stops dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
296 /// <since_tizen> 8 </since_tizen>
297 public event EventHandler<ScrollEventArgs> ScrollDragEnded;
301 /// An event emitted when the scrolling slide animation starts, user can subscribe or unsubscribe to this event handler.<br />
303 /// <since_tizen> 8 </since_tizen>
304 public event EventHandler<ScrollEventArgs> ScrollAnimationStarted;
307 /// An event emitted when the scrolling slide animation ends, user can subscribe or unsubscribe to this event handler.<br />
309 /// <since_tizen> 8 </since_tizen>
310 public event EventHandler<ScrollEventArgs> ScrollAnimationEnded;
314 /// An event emitted when scrolling, user can subscribe or unsubscribe to this event handler.<br />
316 /// <since_tizen> 8 </since_tizen>
317 public event EventHandler<ScrollEventArgs> Scrolling;
321 /// Scrollbar for ScrollableBase.
323 /// <since_tizen> 8 </since_tizen>
324 public ScrollbarBase Scrollbar
334 scrollBar.Unparent();
338 scrollBar.Name = "ScrollBar";
355 /// Always hide Scrollbar.
357 /// <since_tizen> 8 </since_tizen>
358 public bool HideScrollbar
362 return hideScrollbar;
366 hideScrollbar = value;
383 /// Container which has content of ScrollableBase.
385 /// <since_tizen> 8 </since_tizen>
386 public View ContentContainer { get; private set; }
389 /// Set the layout on this View. Replaces any existing Layout.
391 /// <since_tizen> 8 </since_tizen>
392 public new LayoutItem Layout
396 return ContentContainer.Layout;
400 ContentContainer.Layout = value;
401 if (ContentContainer.Layout != null)
403 ContentContainer.Layout.SetPositionByLayout = false;
409 /// List of children of Container.
411 /// <since_tizen> 8 </since_tizen>
412 public new List<View> Children
416 return ContentContainer.Children;
421 /// Deceleration rate of scrolling by finger.
422 /// Rate should be bigger than 0 and smaller than 1.
423 /// Default value is 0.998f;
425 /// <since_tizen> 8 </since_tizen>
426 public float DecelerationRate
430 return decelerationRate;
434 decelerationRate = (value < 1 && value > 0) ? value : decelerationRate;
435 logValueOfDeceleration = (float)Math.Log(value);
440 /// Threashold not to go infinit at the end of scrolling animation.
442 [EditorBrowsable(EditorBrowsableState.Never)]
443 public float DecelerationThreshold { get; set; } = 0.1f;
446 /// Scrolling event will be thrown when this amount of scroll positino is changed.
448 [EditorBrowsable(EditorBrowsableState.Never)]
449 public float ScrollingEventThreshold
453 return mScrollingEventThreshold;
457 if (mScrollingEventThreshold != value && value > 0)
459 ContentContainer.RemovePropertyNotification(propertyNotification);
460 propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(value));
461 propertyNotification.Notified += OnPropertyChanged;
462 mScrollingEventThreshold = value;
469 /// Page will be changed when velocity of panning is over threshold.
470 /// The unit of threshold is pixel per milisec.
472 /// <since_tizen> 8 </since_tizen>
473 public float PageFlickThreshold
477 return mPageFlickThreshold;
481 mPageFlickThreshold = value >= 0f ? value : mPageFlickThreshold;
486 /// Alphafunction for scroll animation.
488 [EditorBrowsable(EditorBrowsableState.Never)]
489 public AlphaFunction ScrollAlphaFunction { get; set; } = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
491 private bool hideScrollbar = true;
492 private float maxScrollDistance;
493 private float childTargetPosition = 0.0f;
494 private PanGestureDetector mPanGestureDetector;
495 private View mInterruptTouchingChild;
496 private ScrollbarBase scrollBar;
497 private bool scrolling = false;
498 private float ratioOfScreenWidthToCompleteScroll = 0.5f;
499 private float totalDisplacementForPan = 0.0f;
500 private Size previousContainerSize = new Size();
501 private PropertyNotification propertyNotification;
502 private float noticeAnimationEndBeforePosition = 0.0f;
503 private bool readyToNotice = false;
506 /// Notice before animation is finished.
508 [EditorBrowsable(EditorBrowsableState.Never)]
509 // Let's consider more whether this needs to be set as protected.
510 public float NoticeAnimationEndBeforePosition { get => noticeAnimationEndBeforePosition; set => noticeAnimationEndBeforePosition = value; }
512 // Let's consider more whether this needs to be set as protected.
513 private float finalTargetPosition;
515 private Animation scrollAnimation;
516 // Declare user alpha function delegate
517 [UnmanagedFunctionPointer(CallingConvention.StdCall)]
518 private delegate float UserAlphaFunctionDelegate(float progress);
519 private UserAlphaFunctionDelegate customScrollAlphaFunction;
520 private float velocityOfLastPan = 0.0f;
521 private float panAnimationDuration = 0.0f;
522 private float panAnimationDelta = 0.0f;
523 private float logValueOfDeceleration = 0.0f;
524 private float decelerationRate = 0.0f;
527 /// Default Constructor
529 /// <since_tizen> 8 </since_tizen>
530 public ScrollableBase() : base()
532 DecelerationRate = 0.998f;
534 base.Layout = new ScrollableBaseCustomLayout();
535 mPanGestureDetector = new PanGestureDetector();
536 mPanGestureDetector.Attach(this);
537 mPanGestureDetector.AddDirection(PanGestureDetector.DirectionVertical);
538 mPanGestureDetector.Detected += OnPanGestureDetected;
540 ClippingMode = ClippingModeType.ClipChildren;
542 //Default Scrolling child
543 ContentContainer = new View()
545 WidthSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent,
546 HeightSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent,
547 Layout = new AbsoluteLayout() { SetPositionByLayout = false },
549 ContentContainer.Relayout += OnScrollingChildRelayout;
550 propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(mScrollingEventThreshold));
551 propertyNotification.Notified += OnPropertyChanged;
552 base.Add(ContentContainer);
554 //Interrupt touching when panning is started
555 mInterruptTouchingChild = new View()
557 Size = new Size(Window.Instance.WindowSize),
558 BackgroundColor = Color.Transparent,
560 mInterruptTouchingChild.TouchEvent += OnIterruptTouchingChildTouched;
561 Scrollbar = new Scrollbar();
563 AccessibilityManager.Instance.SetAccessibilityAttribute(this, AccessibilityManager.AccessibilityAttribute.Trait, "ScrollableBase");
566 private bool OnIterruptTouchingChildTouched(object source, View.TouchEventArgs args)
568 if (args.Touch.GetState(0) == PointStateType.Down)
570 if (scrolling && !SnapToPage)
578 private void OnPropertyChanged(object source, PropertyNotification.NotifyEventArgs args)
584 /// Called after a child has been added to the owning view.
586 /// <param name="view">The child which has been added.</param>
587 /// <since_tizen> 8 </since_tizen>
588 public override void Add(View view)
590 ContentContainer.Add(view);
594 /// Called after a child has been removed from the owning view.
596 /// <param name="view">The child which has been removed.</param>
597 /// <since_tizen> 8 </since_tizen>
598 public override void Remove(View view)
600 if (SnapToPage && CurrentPage == Children.IndexOf(view) && CurrentPage == Children.Count - 1)
602 // Target View is current page and also last child.
603 // CurrentPage should be changed to previous page.
604 CurrentPage = Math.Max(0, CurrentPage - 1);
605 ScrollToIndex(CurrentPage);
608 ContentContainer.Remove(view);
611 private void OnScrollingChildRelayout(object source, EventArgs args)
613 // Size is changed. Calculate maxScrollDistance.
614 bool isSizeChanged = previousContainerSize.Width != ContentContainer.Size.Width || previousContainerSize.Height != ContentContainer.Size.Height;
618 maxScrollDistance = CalculateMaximumScrollDistance();
622 previousContainerSize = ContentContainer.Size;
626 /// The composition of a Scrollbar can vary depending on how you use ScrollableBase.
627 /// Set the composition that will go into the ScrollableBase according to your ScrollableBase.
629 /// <since_tizen> 8 </since_tizen>
630 [EditorBrowsable(EditorBrowsableState.Never)]
631 protected virtual void SetScrollbar()
635 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
636 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
637 float viewportLength = isHorizontal ? Size.Width : Size.Height;
638 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
639 Scrollbar.Initialize(contentLength, viewportLength, currentPosition, isHorizontal);
644 /// Scrolls to the item at the specified index.
646 /// <param name="index">Index of item.</param>
647 /// <since_tizen> 8 </since_tizen>
648 public void ScrollToIndex(int index)
650 if (ContentContainer.ChildCount - 1 < index || index < 0)
660 float targetPosition = Math.Min(ScrollingDirection == Direction.Vertical ? Children[index].Position.Y : Children[index].Position.X, maxScrollDistance);
661 AnimateChildTo(ScrollDuration, -targetPosition);
664 private void OnScrollDragStarted()
666 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
667 ScrollDragStarted?.Invoke(this, eventArgs);
670 private void OnScrollDragEnded()
672 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
673 ScrollDragEnded?.Invoke(this, eventArgs);
676 private void OnScrollAnimationStarted()
678 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
679 ScrollAnimationStarted?.Invoke(this, eventArgs);
682 private void OnScrollAnimationEnded()
685 base.Remove(mInterruptTouchingChild);
687 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
688 ScrollAnimationEnded?.Invoke(this, eventArgs);
691 private void OnScroll()
693 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
694 Scrolling?.Invoke(this, eventArgs);
696 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
697 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
698 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
700 scrollBar.Update(contentLength, Math.Abs(currentPosition));
701 CheckPreReachedTargetPosition();
704 private void CheckPreReachedTargetPosition()
706 // Check whether we reached pre-reached target position
708 ContentContainer.CurrentPosition.Y <= finalTargetPosition + NoticeAnimationEndBeforePosition &&
709 ContentContainer.CurrentPosition.Y >= finalTargetPosition - NoticeAnimationEndBeforePosition)
712 readyToNotice = false;
713 OnPreReachedTargetPosition(finalTargetPosition);
718 /// This helps developer who wants to know before scroll is reaching target position.
720 /// <param name="targetPosition">Index of item.</param>
721 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
722 [EditorBrowsable(EditorBrowsableState.Never)]
723 protected virtual void OnPreReachedTargetPosition(float targetPosition)
728 private void StopScroll()
730 if (scrollAnimation != null)
732 if (scrollAnimation.State == Animation.States.Playing)
734 Debug.WriteLineIf(LayoutDebugScrollableBase, "StopScroll Animation Playing");
735 scrollAnimation.Stop(Animation.EndActions.Cancel);
736 OnScrollAnimationEnded();
738 scrollAnimation.Clear();
742 private void AnimateChildTo(int duration, float axisPosition)
744 Debug.WriteLineIf(LayoutDebugScrollableBase, "AnimationTo Animation Duration:" + duration + " Destination:" + axisPosition);
745 finalTargetPosition = axisPosition;
747 StopScroll(); // Will replace previous animation so will stop existing one.
749 if (scrollAnimation == null)
751 scrollAnimation = new Animation();
752 scrollAnimation.Finished += ScrollAnimationFinished;
755 scrollAnimation.Duration = duration;
756 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSquare);
757 scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", axisPosition, ScrollAlphaFunction);
759 OnScrollAnimationStarted();
760 scrollAnimation.Play();
764 /// Scroll to specific position with or without animation.
766 /// <param name="position">Destination.</param>
767 /// <param name="animate">Scroll with or without animation</param>
768 /// <since_tizen> 8 </since_tizen>
769 public void ScrollTo(float position, bool animate)
771 float currentPositionX = ContentContainer.CurrentPosition.X != 0 ? ContentContainer.CurrentPosition.X : ContentContainer.Position.X;
772 float currentPositionY = ContentContainer.CurrentPosition.Y != 0 ? ContentContainer.CurrentPosition.Y : ContentContainer.Position.Y;
773 float delta = ScrollingDirection == Direction.Horizontal ? currentPositionX : currentPositionY;
774 // The argument position is the new pan position. So the new position of ScrollableBase becomes (-position).
775 // To move ScrollableBase's position to (-position), it moves by (-position - currentPosition).
776 delta = -position - delta;
778 ScrollBy(delta, animate);
781 private float BoundScrollPosition(float targetPosition)
783 if (ScrollAvailableArea != null)
785 float minScrollPosition = ScrollAvailableArea.X;
786 float maxScrollPosition = ScrollAvailableArea.Y;
788 targetPosition = Math.Min(-minScrollPosition, targetPosition);
789 targetPosition = Math.Max(-maxScrollPosition, targetPosition);
793 targetPosition = Math.Min(0, targetPosition);
794 targetPosition = Math.Max(-maxScrollDistance, targetPosition);
797 return targetPosition;
800 private void ScrollBy(float displacement, bool animate)
802 if (GetChildCount() == 0 || maxScrollDistance < 0)
807 float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? ContentContainer.PositionX : ContentContainer.PositionY;
809 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy childCurrentPosition:" + childCurrentPosition +
810 " displacement:" + displacement,
811 " maxScrollDistance:" + maxScrollDistance);
813 childTargetPosition = childCurrentPosition + displacement; // child current position + gesture displacement
816 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition);
820 // Calculate scroll animaton duration
821 float scrollDistance = Math.Abs(displacement);
822 readyToNotice = true;
824 AnimateChildTo(ScrollDuration, BoundScrollPosition(AdjustTargetPositionOfScrollAnimation(BoundScrollPosition(childTargetPosition))));
828 finalTargetPosition = BoundScrollPosition(childTargetPosition);
830 // Set position of scrolling child without an animation
831 if (ScrollingDirection == Direction.Horizontal)
833 ContentContainer.PositionX = finalTargetPosition;
837 ContentContainer.PositionY = finalTargetPosition;
844 /// you can override it to clean-up your own resources.
846 /// <param name="type">DisposeTypes</param>
847 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
848 [EditorBrowsable(EditorBrowsableState.Never)]
849 protected override void Dispose(DisposeTypes type)
856 if (type == DisposeTypes.Explicit)
860 if (mPanGestureDetector != null)
862 mPanGestureDetector.Detected -= OnPanGestureDetected;
863 mPanGestureDetector.Dispose();
864 mPanGestureDetector = null;
867 propertyNotification.Dispose();
872 private float CalculateMaximumScrollDistance()
874 float scrollingChildLength = 0;
875 float scrollerLength = 0;
876 if (ScrollingDirection == Direction.Horizontal)
878 Debug.WriteLineIf(LayoutDebugScrollableBase, "Horizontal");
880 scrollingChildLength = ContentContainer.Size.Width;
881 scrollerLength = Size.Width;
885 Debug.WriteLineIf(LayoutDebugScrollableBase, "Vertical");
886 scrollingChildLength = ContentContainer.Size.Height;
887 scrollerLength = Size.Height;
890 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy maxScrollDistance:" + (scrollingChildLength - scrollerLength) +
891 " parent length:" + scrollerLength +
892 " scrolling child length:" + scrollingChildLength);
894 return Math.Max(scrollingChildLength - scrollerLength, 0);
897 private void PageSnap(float velocity)
899 Debug.WriteLineIf(LayoutDebugScrollableBase, "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan +
900 " currentPage[" + CurrentPage + "]");
902 //Increment current page if total displacement enough to warrant a page change.
903 if (Math.Abs(totalDisplacementForPan) > (mPageWidth * ratioOfScreenWidthToCompleteScroll))
905 if (totalDisplacementForPan < 0)
907 CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
911 CurrentPage = Math.Max(0, --CurrentPage);
914 else if (Math.Abs(velocity) > PageFlickThreshold)
918 CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
922 CurrentPage = Math.Max(0, --CurrentPage);
926 // Animate to new page or reposition to current page
927 float destinationX = -(Children[CurrentPage].Position.X + Children[CurrentPage].CurrentSize.Width / 2 - CurrentSize.Width / 2); // set to middle of current page
928 Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to page[" + CurrentPage + "] to:" + destinationX + " from:" + ContentContainer.PositionX);
929 AnimateChildTo(ScrollDuration, destinationX);
932 private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
934 OnPanGesture(e.PanGesture);
937 private void OnPanGesture(PanGesture panGesture)
939 if (SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
944 if (panGesture.State == Gesture.StateType.Started)
946 readyToNotice = false;
947 base.Add(mInterruptTouchingChild);
948 Debug.WriteLineIf(LayoutDebugScrollableBase, "Gesture Start");
949 if (scrolling && !SnapToPage)
953 totalDisplacementForPan = 0.0f;
954 OnScrollDragStarted();
956 else if (panGesture.State == Gesture.StateType.Continuing)
958 if (ScrollingDirection == Direction.Horizontal)
960 ScrollBy(panGesture.Displacement.X, false);
961 totalDisplacementForPan += panGesture.Displacement.X;
965 ScrollBy(panGesture.Displacement.Y, false);
966 totalDisplacementForPan += panGesture.Displacement.Y;
968 Debug.WriteLineIf(LayoutDebugScrollableBase, "OnPanGestureDetected Continue totalDisplacementForPan:" + totalDisplacementForPan);
970 else if (panGesture.State == Gesture.StateType.Finished || panGesture.State == Gesture.StateType.Cancelled)
973 StopScroll(); // Will replace previous animation so will stop existing one.
975 if (scrollAnimation == null)
977 scrollAnimation = new Animation();
978 scrollAnimation.Finished += ScrollAnimationFinished;
981 float panVelocity = (ScrollingDirection == Direction.Horizontal) ? panGesture.Velocity.X : panGesture.Velocity.Y;
985 PageSnap(panVelocity);
989 if (panVelocity == 0)
991 float currentScrollPosition = (ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
992 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
993 scrollAnimation.Duration = 0;
994 scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", currentScrollPosition);
995 scrollAnimation.Play();
999 Decelerating(panVelocity, scrollAnimation);
1003 totalDisplacementForPan = 0;
1005 readyToNotice = true;
1006 OnScrollAnimationStarted();
1010 internal override bool OnAccessibilityPan(PanGesture gestures)
1012 if (SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
1017 OnPanGesture(gestures);
1021 private float CustomScrollAlphaFunction(float progress)
1023 if (panAnimationDelta == 0)
1029 // Parameter "progress" is normalized value. We need to multiply target duration to calculate distance.
1030 // Can get real distance using equation of deceleration (check Decelerating function)
1031 // After get real distance, normalize it
1032 float realDuration = progress * panAnimationDuration;
1033 float realDistance = velocityOfLastPan * ((float)Math.Pow(decelerationRate, realDuration) - 1) / logValueOfDeceleration;
1034 float result = Math.Min(realDistance / Math.Abs(panAnimationDelta), 1.0f);
1040 /// you can override it to custom your decelerating
1042 /// <param name="velocity">Velocity of current pan.</param>
1043 /// <param name="animation">Scroll animation.</param>
1044 [EditorBrowsable(EditorBrowsableState.Never)]
1045 protected virtual void Decelerating(float velocity, Animation animation)
1047 // Decelerating using deceleration equation ===========
1049 // V : velocity (pixel per milisecond)
1050 // V0 : initial velocity
1051 // d : deceleration rate,
1053 // X : final position after decelerating
1054 // log : natural logarithm
1056 // V(t) = V0 * d pow t;
1057 // X(t) = V0 * (d pow t - 1) / log d; <-- Integrate the velocity function
1058 // X(∞) = V0 * d / (1 - d); <-- Result using inifit T can be final position because T is tending to infinity.
1060 // Because of final T is tending to inifity, we should use threshold value to finish.
1061 // Final T = log(-threshold * log d / |V0| ) / log d;
1063 velocityOfLastPan = Math.Abs(velocity);
1065 float currentScrollPosition = -(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
1066 panAnimationDelta = (velocityOfLastPan * decelerationRate) / (1 - decelerationRate);
1067 panAnimationDelta = velocity > 0 ? -panAnimationDelta : panAnimationDelta;
1069 float destination = -(panAnimationDelta + currentScrollPosition);
1070 float adjustDestination = AdjustTargetPositionOfScrollAnimation(destination);
1071 float maxPosition = ScrollAvailableArea != null ? ScrollAvailableArea.Y : maxScrollDistance;
1072 float minPosition = ScrollAvailableArea != null ? ScrollAvailableArea.X : 0;
1074 if (destination < -maxPosition || destination > minPosition)
1076 panAnimationDelta = velocity > 0 ? (currentScrollPosition - minPosition) : (maxPosition - currentScrollPosition);
1077 destination = velocity > 0 ? minPosition : -maxPosition;
1079 if (panAnimationDelta == 0)
1081 panAnimationDuration = 0.0f;
1085 panAnimationDuration = (float)Math.Log((panAnimationDelta * logValueOfDeceleration / velocityOfLastPan + 1), decelerationRate);
1088 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1089 "OverRange======================= \n" +
1090 "[decelerationRate] " + decelerationRate + "\n" +
1091 "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1092 "[Velocity] " + velocityOfLastPan + "\n" +
1093 "[CurrentPosition] " + currentScrollPosition + "\n" +
1094 "[CandidateDelta] " + panAnimationDelta + "\n" +
1095 "[Destination] " + destination + "\n" +
1096 "[Duration] " + panAnimationDuration + "\n" +
1097 "================================ \n"
1102 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1104 if (adjustDestination != destination)
1106 destination = adjustDestination;
1107 panAnimationDelta = destination + currentScrollPosition;
1108 velocityOfLastPan = Math.Abs(panAnimationDelta * logValueOfDeceleration / ((float)Math.Pow(decelerationRate, panAnimationDuration) - 1));
1109 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1112 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1113 "================================ \n" +
1114 "[decelerationRate] " + decelerationRate + "\n" +
1115 "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1116 "[Velocity] " + velocityOfLastPan + "\n" +
1117 "[CurrentPosition] " + currentScrollPosition + "\n" +
1118 "[CandidateDelta] " + panAnimationDelta + "\n" +
1119 "[Destination] " + destination + "\n" +
1120 "[Duration] " + panAnimationDuration + "\n" +
1121 "================================ \n"
1125 finalTargetPosition = destination;
1127 customScrollAlphaFunction = new UserAlphaFunctionDelegate(CustomScrollAlphaFunction);
1128 animation.DefaultAlphaFunction = new AlphaFunction(customScrollAlphaFunction);
1129 GC.KeepAlive(customScrollAlphaFunction);
1130 animation.Duration = (int)panAnimationDuration;
1131 animation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", destination);
1135 private void ScrollAnimationFinished(object sender, EventArgs e)
1137 OnScrollAnimationEnded();
1141 /// Adjust scrolling position by own scrolling rules.
1142 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1144 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
1145 [EditorBrowsable(EditorBrowsableState.Never)]
1146 protected virtual float AdjustTargetPositionOfScrollAnimation(float position)
1152 /// Scroll position given to ScrollTo.
1153 /// This is the position in the opposite direction to the position of ContentContainer.
1155 /// <since_tizen> 8 </since_tizen>
1156 public Position ScrollPosition
1160 return new Position(-ContentContainer.Position);
1165 /// Current scroll position in the middle of ScrollTo animation.
1166 /// This is the position in the opposite direction to the current position of ContentContainer.
1168 /// <since_tizen> 8 </since_tizen>
1169 public Position ScrollCurrentPosition
1173 return new Position(-ContentContainer.CurrentPosition);