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 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
29 [EditorBrowsable(EditorBrowsableState.Never)]
30 public class ScrollEventArgs : EventArgs
35 /// Default constructor.
37 /// <param name="position">Current scroll position</param>
38 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
39 public ScrollEventArgs(Position position)
41 this.position = position;
45 /// [Draft] Current scroll position.
47 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
48 [EditorBrowsable(EditorBrowsableState.Never)]
49 public Position Position
59 /// [Draft] This class provides a View that can scroll a single View with a layout. This View can be a nest of Views.
61 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
62 [EditorBrowsable(EditorBrowsableState.Never)]
63 public class ScrollableBase : Control
65 static bool LayoutDebugScrollableBase = false; // Debug flag
66 private Direction mScrollingDirection = Direction.Vertical;
67 private bool mScrollEnabled = true;
68 private int mPageWidth = 0;
70 private class ScrollableBaseCustomLayout : LayoutGroup
72 protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
74 Extents padding = Padding;
75 float totalHeight = padding.Top + padding.Bottom;
76 float totalWidth = padding.Start + padding.End;
78 MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK;
79 MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK;
81 Direction scrollingDirection = Direction.Vertical;
82 ScrollableBase scrollableBase = this.Owner as ScrollableBase;
85 scrollingDirection = scrollableBase.ScrollingDirection;
88 // measure child, should be a single scrolling child
89 foreach (LayoutItem childLayout in LayoutChildren)
91 if (childLayout != null)
94 // Use an Unspecified MeasureSpecification mode so scrolling child is not restricted to it's parents size in Height (for vertical scrolling)
95 // or Width for horizontal scrolling
96 MeasureSpecification unrestrictedMeasureSpec = new MeasureSpecification(heightMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
98 if (scrollingDirection == Direction.Vertical)
100 MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), unrestrictedMeasureSpec, new LayoutLength(0)); // Height unrestricted by parent
104 MeasureChildWithMargins(childLayout, unrestrictedMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0)); // Width unrestricted by parent
107 float childWidth = childLayout.MeasuredWidth.Size.AsDecimal();
108 float childHeight = childLayout.MeasuredHeight.Size.AsDecimal();
110 // Determine the width and height needed by the children using their given position and size.
111 // Children could overlap so find the left most and right most child.
112 Position2D childPosition = childLayout.Owner.Position2D;
113 float childLeft = childPosition.X;
114 float childTop = childPosition.Y;
116 // Store current width and height needed to contain all children.
117 Extents childMargin = childLayout.Margin;
118 totalWidth = childWidth + childMargin.Start + childMargin.End;
119 totalHeight = childHeight + childMargin.Top + childMargin.Bottom;
121 if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
123 childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
125 if (childLayout.MeasuredHeight.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
127 childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
133 MeasuredSize widthSizeAndState = ResolveSizeAndState(new LayoutLength(totalWidth + Padding.Start + Padding.End), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
134 MeasuredSize heightSizeAndState = ResolveSizeAndState(new LayoutLength(totalHeight + Padding.Top + Padding.Bottom), heightMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
135 totalWidth = widthSizeAndState.Size.AsDecimal();
136 totalHeight = heightSizeAndState.Size.AsDecimal();
138 // Ensure layout respects it's given minimum size
139 totalWidth = Math.Max(totalWidth, SuggestedMinimumWidth.AsDecimal());
140 totalHeight = Math.Max(totalHeight, SuggestedMinimumHeight.AsDecimal());
142 widthSizeAndState.State = childWidthState;
143 heightSizeAndState.State = childHeightState;
145 SetMeasuredDimensions(ResolveSizeAndState(new LayoutLength(totalWidth + Padding.Start + Padding.End), widthMeasureSpec, childWidthState),
146 ResolveSizeAndState(new LayoutLength(totalHeight + Padding.Top + Padding.Bottom), heightMeasureSpec, childHeightState));
148 // Size of ScrollableBase is changed. Change Page width too.
149 scrollableBase.mPageWidth = (int)MeasuredWidth.Size.AsRoundedValue();
152 protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
154 foreach (LayoutItem childLayout in LayoutChildren)
156 if (childLayout != null)
158 LayoutLength childWidth = childLayout.MeasuredWidth.Size;
159 LayoutLength childHeight = childLayout.MeasuredHeight.Size;
161 Position2D childPosition = childLayout.Owner.Position2D;
162 Extents padding = Padding;
163 Extents childMargin = childLayout.Margin;
165 LayoutLength childLeft = new LayoutLength(childPosition.X + childMargin.Start + padding.Start);
166 LayoutLength childTop = new LayoutLength(childPosition.Y + childMargin.Top + padding.Top);
168 childLayout.Layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
172 } // ScrollableBaseCustomLayout
175 /// The direction axis to scroll.
177 /// <since_tizen> 6 </since_tizen>
178 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
179 [EditorBrowsable(EditorBrowsableState.Never)]
180 public enum Direction
185 /// <since_tizen> 6 </since_tizen>
191 /// <since_tizen> 6 </since_tizen>
196 /// [Draft] Scrolling direction mode.
197 /// Default is Vertical scrolling.
199 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
200 [EditorBrowsable(EditorBrowsableState.Never)]
201 public Direction ScrollingDirection
205 return mScrollingDirection;
209 if (value != mScrollingDirection)
211 mScrollingDirection = value;
212 mPanGestureDetector.RemoveDirection(value == Direction.Horizontal ?
213 PanGestureDetector.DirectionVertical : PanGestureDetector.DirectionHorizontal);
214 mPanGestureDetector.AddDirection(value == Direction.Horizontal ?
215 PanGestureDetector.DirectionHorizontal : PanGestureDetector.DirectionVertical);
217 ContentContainer.WidthSpecification = mScrollingDirection == Direction.Vertical ?
218 LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent;
219 ContentContainer.HeightSpecification = mScrollingDirection == Direction.Vertical ?
220 LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent;
226 /// [Draft] Enable or disable scrolling.
228 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
229 [EditorBrowsable(EditorBrowsableState.Never)]
230 public bool ScrollEnabled
234 return mScrollEnabled;
238 if (value != mScrollEnabled)
240 mScrollEnabled = value;
243 mPanGestureDetector.Detected += OnPanGestureDetected;
247 mPanGestureDetector.Detected -= OnPanGestureDetected;
254 /// [Draft] Pages mode, enables moving to the next or return to current page depending on pan displacement.
255 /// Default is false.
257 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
258 [EditorBrowsable(EditorBrowsableState.Never)]
259 public bool SnapToPage { set; get; } = false;
262 /// [Draft] Get current page.
263 /// Working propery with SnapToPage property.
265 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
266 [EditorBrowsable(EditorBrowsableState.Never)]
267 public int CurrentPage { get; private set; } = 0;
270 /// [Draft] Duration of scroll animation.
272 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
273 [EditorBrowsable(EditorBrowsableState.Never)]
275 public int ScrollDuration { set; get; } = 125;
277 /// [Draft] Scroll Available area.
279 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
280 [EditorBrowsable(EditorBrowsableState.Never)]
281 public Vector2 ScrollAvailableArea { set; get; }
284 /// An event emitted when user starts dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
286 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
287 [EditorBrowsable(EditorBrowsableState.Never)]
288 public event EventHandler<ScrollEventArgs> ScrollDragStarted;
291 /// An event emitted when user stops dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
293 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
294 [EditorBrowsable(EditorBrowsableState.Never)]
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 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
302 [EditorBrowsable(EditorBrowsableState.Never)]
303 public event EventHandler<ScrollEventArgs> ScrollAnimationStarted;
306 /// An event emitted when the scrolling slide animation ends, user can subscribe or unsubscribe to this event handler.<br />
308 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
309 [EditorBrowsable(EditorBrowsableState.Never)]
310 public event EventHandler<ScrollEventArgs> ScrollAnimationEnded;
314 /// An event emitted when scrolling, user can subscribe or unsubscribe to this event handler.<br />
316 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
317 [EditorBrowsable(EditorBrowsableState.Never)]
318 public event EventHandler<ScrollEventArgs> Scrolling;
322 /// Scrollbar for ScrollableBase.<br />
324 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
325 [EditorBrowsable(EditorBrowsableState.Never)]
326 public ScrollbarBase Scrollbar
336 scrollBar.Unparent();
340 scrollBar.Name = "ScrollBar";
357 /// [Draft] Always hide Scrollbar.
359 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
360 [EditorBrowsable(EditorBrowsableState.Never)]
361 public bool HideScrollBar
365 return hideScrollbar;
369 hideScrollbar = value;
386 /// Container which has content of ScrollableBase.
388 [EditorBrowsable(EditorBrowsableState.Never)]
389 public View ContentContainer { get; private set; }
392 /// Set the layout on this View. Replaces any existing Layout.
394 [EditorBrowsable(EditorBrowsableState.Never)]
395 public new LayoutItem Layout
399 return ContentContainer.Layout;
403 ContentContainer.Layout = value;
404 if (ContentContainer.Layout != null)
406 ContentContainer.Layout.SetPositionByLayout = false;
412 /// List of children of Container.
414 [EditorBrowsable(EditorBrowsableState.Never)]
415 public new List<View> Children
419 return ContentContainer.Children;
424 /// Deceleration rate of scrolling by finger.
425 /// Rate should be 0 < rate < 1.
427 [EditorBrowsable(EditorBrowsableState.Never)]
428 public float DecelerationRate
432 return decelerationRate;
436 decelerationRate = value;
437 logValueOfDeceleration = (float)Math.Log(value);
442 /// Threashold not to go infinit at the end of scrolling animation.
444 [EditorBrowsable(EditorBrowsableState.Never)]
445 public float DecelerationThreshold { get; set; } = 0.1f;
448 /// Page will be changed when velocity of panning is over threshold.
450 [EditorBrowsable(EditorBrowsableState.Never)]
451 public float PageFlickThreshold { get; set; } = 0.4f;
454 /// Alphafunction for scroll animation.
456 [EditorBrowsable(EditorBrowsableState.Never)]
457 public AlphaFunction ScrollAlphaFunction { get; set; } = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
459 private bool hideScrollbar = true;
460 private float maxScrollDistance;
461 private float childTargetPosition = 0.0f;
462 private PanGestureDetector mPanGestureDetector;
463 private View mInterruptTouchingChild;
464 private ScrollbarBase scrollBar;
465 private bool scrolling = false;
466 private float ratioOfScreenWidthToCompleteScroll = 0.5f;
467 private float totalDisplacementForPan = 0.0f;
468 private Size previousContainerSize = new Size();
469 private PropertyNotification propertyNotification;
470 private float noticeAnimationEndBeforePosition = 0.0f;
471 private bool readyToNotice = false;
472 // Let's consider more whether this needs to be set as protected.
473 public float NoticeAnimationEndBeforePosition { get => noticeAnimationEndBeforePosition; set => noticeAnimationEndBeforePosition = value; }
476 // Let's consider more whether this needs to be set as protected.
477 private float finalTargetPosition;
479 private Animation scrollAnimation;
480 // Declare user alpha function delegate
481 [UnmanagedFunctionPointer(CallingConvention.StdCall)]
482 private delegate float UserAlphaFunctionDelegate(float progress);
483 private UserAlphaFunctionDelegate customScrollAlphaFunction;
484 private float velocityOfLastPan = 0.0f;
485 private float panAnimationDuration = 0.0f;
486 private float panAnimationDelta = 0.0f;
487 private float logValueOfDeceleration = 0.0f;
488 private float decelerationRate = 0.0f;
491 /// [Draft] Constructor
493 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
494 [EditorBrowsable(EditorBrowsableState.Never)]
495 public ScrollableBase() : base()
497 DecelerationRate = 0.998f;
499 base.Layout = new ScrollableBaseCustomLayout();
500 mPanGestureDetector = new PanGestureDetector();
501 mPanGestureDetector.Attach(this);
502 mPanGestureDetector.AddDirection(PanGestureDetector.DirectionVertical);
503 mPanGestureDetector.Detected += OnPanGestureDetected;
505 ClippingMode = ClippingModeType.ClipChildren;
507 //Default Scrolling child
508 ContentContainer = new View()
510 WidthSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent,
511 HeightSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent,
512 Layout = new AbsoluteLayout() { SetPositionByLayout = false },
514 ContentContainer.Relayout += OnScrollingChildRelayout;
515 propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(1.0f));
516 propertyNotification.Notified += OnPropertyChanged;
517 base.Add(ContentContainer);
519 //Interrupt touching when panning is started
520 mInterruptTouchingChild = new View()
522 Size = new Size(Window.Instance.WindowSize),
523 BackgroundColor = Color.Transparent,
525 mInterruptTouchingChild.TouchEvent += OnIterruptTouchingChildTouched;
526 Scrollbar = new Scrollbar();
529 private bool OnIterruptTouchingChildTouched(object source, View.TouchEventArgs args)
531 if (args.Touch.GetState(0) == PointStateType.Down)
533 if (scrolling && !SnapToPage)
541 private void OnPropertyChanged(object source, PropertyNotification.NotifyEventArgs args)
547 /// Called after a child has been added to the owning view.
549 /// <param name="view">The child which has been added.</param>
550 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
551 [EditorBrowsable(EditorBrowsableState.Never)]
552 public override void Add(View view)
554 ContentContainer.Add(view);
558 /// Called after a child has been removed from the owning view.
560 /// <param name="view">The child which has been removed.</param>
561 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
562 [EditorBrowsable(EditorBrowsableState.Never)]
563 public override void Remove(View view)
565 if (SnapToPage && CurrentPage == Children.IndexOf(view) && CurrentPage == Children.Count - 1)
567 // Target View is current page and also last child.
568 // CurrentPage should be changed to previous page.
569 CurrentPage = Math.Max(0, CurrentPage - 1);
570 ScrollToIndex(CurrentPage);
573 ContentContainer.Remove(view);
576 private void OnScrollingChildRelayout(object source, EventArgs args)
578 // Size is changed. Calculate maxScrollDistance.
579 bool isSizeChanged = previousContainerSize.Width != ContentContainer.Size.Width || previousContainerSize.Height != ContentContainer.Size.Height;
583 maxScrollDistance = CalculateMaximumScrollDistance();
587 previousContainerSize = ContentContainer.Size;
591 /// The composition of a Scrollbar can vary depending on how you use ScrollableBase.
592 /// Set the composition that will go into the ScrollableBase according to your ScrollableBase.
594 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
595 [EditorBrowsable(EditorBrowsableState.Never)]
596 protected virtual void SetScrollbar()
600 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
601 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
602 float viewportLength = isHorizontal ? Size.Width : Size.Height;
603 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
604 Scrollbar.Initialize(contentLength, viewportLength, currentPosition, isHorizontal);
609 /// Scrolls to the item at the specified index.
611 /// <param name="index">Index of item.</param>
612 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
613 [EditorBrowsable(EditorBrowsableState.Never)]
614 public void ScrollToIndex(int index)
616 if (ContentContainer.ChildCount - 1 < index || index < 0)
626 float targetPosition = Math.Min(ScrollingDirection == Direction.Vertical ? Children[index].Position.Y : Children[index].Position.X, maxScrollDistance);
627 AnimateChildTo(ScrollDuration, -targetPosition);
630 private void OnScrollDragStarted()
632 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
633 ScrollDragStarted?.Invoke(this, eventArgs);
636 private void OnScrollDragEnded()
638 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
639 ScrollDragEnded?.Invoke(this, eventArgs);
642 private void OnScrollAnimationStarted()
644 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
645 ScrollAnimationStarted?.Invoke(this, eventArgs);
648 private void OnScrollAnimationEnded()
651 base.Remove(mInterruptTouchingChild);
653 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
654 ScrollAnimationEnded?.Invoke(this, eventArgs);
657 private void OnScroll()
659 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
660 Scrolling?.Invoke(this, eventArgs);
662 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
663 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
664 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
666 scrollBar.Update(contentLength, Math.Abs(currentPosition));
667 CheckPreReachedTargetPosition();
670 private void CheckPreReachedTargetPosition()
672 // Check whether we reached pre-reached target position
674 ContentContainer.CurrentPosition.Y <= finalTargetPosition + NoticeAnimationEndBeforePosition &&
675 ContentContainer.CurrentPosition.Y >= finalTargetPosition - NoticeAnimationEndBeforePosition)
678 readyToNotice = false;
679 OnPreReachedTargetPosition(finalTargetPosition);
684 /// This helps developer who wants to know before scroll is reaching target position.
686 /// <param name="targetPosition">Index of item.</param>
687 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
688 [EditorBrowsable(EditorBrowsableState.Never)]
689 protected virtual void OnPreReachedTargetPosition(float targetPosition)
694 private void StopScroll()
696 if (scrollAnimation != null)
698 if (scrollAnimation.State == Animation.States.Playing)
700 Debug.WriteLineIf(LayoutDebugScrollableBase, "StopScroll Animation Playing");
701 scrollAnimation.Stop(Animation.EndActions.Cancel);
702 OnScrollAnimationEnded();
704 scrollAnimation.Clear();
708 private void AnimateChildTo(int duration, float axisPosition)
710 Debug.WriteLineIf(LayoutDebugScrollableBase, "AnimationTo Animation Duration:" + duration + " Destination:" + axisPosition);
711 finalTargetPosition = axisPosition;
713 StopScroll(); // Will replace previous animation so will stop existing one.
715 if (scrollAnimation == null)
717 scrollAnimation = new Animation();
718 scrollAnimation.Finished += ScrollAnimationFinished;
721 scrollAnimation.Duration = duration;
722 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSquare);
723 scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", axisPosition, ScrollAlphaFunction);
725 OnScrollAnimationStarted();
726 scrollAnimation.Play();
730 /// Scroll to specific position with or without animation.
732 /// <param name="position">Destination.</param>
733 /// <param name="animate">Scroll with or without animation</param>
734 [EditorBrowsable(EditorBrowsableState.Never)]
735 public void ScrollTo(float position, bool animate)
737 float currentPositionX = ContentContainer.CurrentPosition.X != 0 ? ContentContainer.CurrentPosition.X : ContentContainer.Position.X;
738 float currentPositionY = ContentContainer.CurrentPosition.Y != 0 ? ContentContainer.CurrentPosition.Y : ContentContainer.Position.Y;
739 float delta = ScrollingDirection == Direction.Horizontal ? currentPositionX : currentPositionY;
740 // The argument position is the new pan position. So the new position of ScrollableBase becomes (-position).
741 // To move ScrollableBase's position to (-position), it moves by (-position - currentPosition).
742 delta = -position - delta;
744 ScrollBy(delta, animate);
747 private float BoundScrollPosition(float targetPosition)
749 if (ScrollAvailableArea != null)
751 float minScrollPosition = ScrollAvailableArea.X;
752 float maxScrollPosition = ScrollAvailableArea.Y;
754 targetPosition = Math.Min(-minScrollPosition, targetPosition);
755 targetPosition = Math.Max(-maxScrollPosition, targetPosition);
759 targetPosition = Math.Min(0, targetPosition);
760 targetPosition = Math.Max(-maxScrollDistance, targetPosition);
763 return targetPosition;
766 private void ScrollBy(float displacement, bool animate)
768 if (GetChildCount() == 0 || maxScrollDistance < 0)
773 float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? ContentContainer.PositionX : ContentContainer.PositionY;
775 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy childCurrentPosition:" + childCurrentPosition +
776 " displacement:" + displacement,
777 " maxScrollDistance:" + maxScrollDistance);
779 childTargetPosition = childCurrentPosition + displacement; // child current position + gesture displacement
782 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition);
786 // Calculate scroll animaton duration
787 float scrollDistance = Math.Abs(displacement);
788 readyToNotice = true;
790 AnimateChildTo(ScrollDuration, BoundScrollPosition(AdjustTargetPositionOfScrollAnimation(BoundScrollPosition(childTargetPosition))));
794 finalTargetPosition = BoundScrollPosition(childTargetPosition);
796 // Set position of scrolling child without an animation
797 if (ScrollingDirection == Direction.Horizontal)
799 ContentContainer.PositionX = finalTargetPosition;
803 ContentContainer.PositionY = finalTargetPosition;
810 /// you can override it to clean-up your own resources.
812 /// <param name="type">DisposeTypes</param>
813 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
814 [EditorBrowsable(EditorBrowsableState.Never)]
815 protected override void Dispose(DisposeTypes type)
822 if (type == DisposeTypes.Explicit)
826 if (mPanGestureDetector != null)
828 mPanGestureDetector.Detected -= OnPanGestureDetected;
829 mPanGestureDetector.Dispose();
830 mPanGestureDetector = null;
836 private float CalculateMaximumScrollDistance()
838 float scrollingChildLength = 0;
839 float scrollerLength = 0;
840 if (ScrollingDirection == Direction.Horizontal)
842 Debug.WriteLineIf(LayoutDebugScrollableBase, "Horizontal");
844 scrollingChildLength = ContentContainer.Size.Width;
845 scrollerLength = Size.Width;
849 Debug.WriteLineIf(LayoutDebugScrollableBase, "Vertical");
850 scrollingChildLength = ContentContainer.Size.Height;
851 scrollerLength = Size.Height;
854 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy maxScrollDistance:" + (scrollingChildLength - scrollerLength) +
855 " parent length:" + scrollerLength +
856 " scrolling child length:" + scrollingChildLength);
858 return Math.Max(scrollingChildLength - scrollerLength, 0);
861 private void PageSnap(float velocity)
863 Debug.WriteLineIf(LayoutDebugScrollableBase, "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan +
864 " currentPage[" + CurrentPage + "]");
866 //Increment current page if total displacement enough to warrant a page change.
867 if (Math.Abs(totalDisplacementForPan) > (mPageWidth * ratioOfScreenWidthToCompleteScroll))
869 if (totalDisplacementForPan < 0)
871 CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
875 CurrentPage = Math.Max(0, --CurrentPage);
878 else if (Math.Abs(velocity) > PageFlickThreshold)
882 CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
886 CurrentPage = Math.Max(0, --CurrentPage);
890 // Animate to new page or reposition to current page
891 float destinationX = -(Children[CurrentPage].Position.X + Children[CurrentPage].CurrentSize.Width / 2 - CurrentSize.Width / 2); // set to middle of current page
892 Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to page[" + CurrentPage + "] to:" + destinationX + " from:" + ContentContainer.PositionX);
893 AnimateChildTo(ScrollDuration, destinationX);
896 private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
898 if (SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
903 if (e.PanGesture.State == Gesture.StateType.Started)
905 readyToNotice = false;
906 base.Add(mInterruptTouchingChild);
907 Debug.WriteLineIf(LayoutDebugScrollableBase, "Gesture Start");
908 if (scrolling && !SnapToPage)
912 totalDisplacementForPan = 0.0f;
913 OnScrollDragStarted();
915 else if (e.PanGesture.State == Gesture.StateType.Continuing)
917 if (ScrollingDirection == Direction.Horizontal)
919 ScrollBy(e.PanGesture.Displacement.X, false);
920 totalDisplacementForPan += e.PanGesture.Displacement.X;
924 ScrollBy(e.PanGesture.Displacement.Y, false);
925 totalDisplacementForPan += e.PanGesture.Displacement.Y;
927 Debug.WriteLineIf(LayoutDebugScrollableBase, "OnPanGestureDetected Continue totalDisplacementForPan:" + totalDisplacementForPan);
929 else if (e.PanGesture.State == Gesture.StateType.Finished)
932 StopScroll(); // Will replace previous animation so will stop existing one.
934 if (scrollAnimation == null)
936 scrollAnimation = new Animation();
937 scrollAnimation.Finished += ScrollAnimationFinished;
942 PageSnap((ScrollingDirection == Direction.Horizontal) ? e.PanGesture.Velocity.X : e.PanGesture.Velocity.Y);
946 Decelerating((ScrollingDirection == Direction.Horizontal) ? e.PanGesture.Velocity.X : e.PanGesture.Velocity.Y);
949 totalDisplacementForPan = 0;
951 readyToNotice = true;
952 OnScrollAnimationStarted();
956 private float CustomScrollAlphaFunction(float progress)
958 if (panAnimationDelta == 0)
964 // Parameter "progress" is normalized value. We need to multiply target duration to calculate distance.
965 // Can get real distance using equation of deceleration (check Decelerating function)
966 // After get real distance, normalize it
967 float realDuration = progress * panAnimationDuration;
968 float realDistance = velocityOfLastPan * ((float)Math.Pow(decelerationRate, realDuration) - 1) / logValueOfDeceleration;
969 float result = Math.Min(realDistance / Math.Abs(panAnimationDelta), 1.0f);
974 private void Decelerating(float velocity)
976 // Decelerating using deceleration equation ===========
978 // V : velocity (pixel per milisecond)
979 // V0 : initial velocity
980 // d : deceleration rate,
982 // X : final position after decelerating
983 // log : natural logarithm
985 // V(t) = V0 * d pow t;
986 // X(t) = V0 * (d pow t - 1) / log d; <-- Integrate the velocity function
987 // X(∞) = V0 * d / (1 - d); <-- Result using inifit T can be final position because T is tending to infinity.
989 // Because of final T is tending to inifity, we should use threshold value to finish.
990 // Final T = log(-threshold * log d / |V0| ) / log d;
992 velocityOfLastPan = Math.Abs(velocity);
994 float currentScrollPosition = -(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
995 panAnimationDelta = (velocityOfLastPan * decelerationRate) / (1 - decelerationRate);
996 panAnimationDelta = velocity > 0 ? -panAnimationDelta : panAnimationDelta;
998 float destination = -(panAnimationDelta + currentScrollPosition);
999 float adjustDestination = AdjustTargetPositionOfScrollAnimation(destination);
1000 float maxPosition = ScrollAvailableArea != null ? ScrollAvailableArea.Y : maxScrollDistance;
1001 float minPosition = ScrollAvailableArea != null ? ScrollAvailableArea.X : 0;
1003 if (destination < -maxPosition || destination > minPosition)
1005 panAnimationDelta = velocity > 0 ? (currentScrollPosition - minPosition) : (maxPosition - currentScrollPosition);
1006 destination = velocity > 0 ? minPosition : -maxPosition;
1008 if (panAnimationDelta == 0)
1010 panAnimationDuration = 0.0f;
1014 panAnimationDuration = (float)Math.Log((panAnimationDelta * logValueOfDeceleration / velocityOfLastPan + 1), decelerationRate);
1017 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1018 "OverRange======================= \n" +
1019 "[decelerationRate] " + decelerationRate + "\n" +
1020 "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1021 "[Velocity] " + velocityOfLastPan + "\n" +
1022 "[CurrentPosition] " + currentScrollPosition + "\n" +
1023 "[CandidateDelta] " + panAnimationDelta + "\n" +
1024 "[Destination] " + destination + "\n" +
1025 "[Duration] " + panAnimationDuration + "\n" +
1026 "================================ \n"
1031 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1033 if (adjustDestination != destination)
1035 destination = adjustDestination;
1036 panAnimationDelta = destination + currentScrollPosition;
1037 velocityOfLastPan = Math.Abs(panAnimationDelta * logValueOfDeceleration / ((float)Math.Pow(decelerationRate, panAnimationDuration) - 1));
1038 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1041 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1042 "================================ \n" +
1043 "[decelerationRate] " + decelerationRate + "\n" +
1044 "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1045 "[Velocity] " + velocityOfLastPan + "\n" +
1046 "[CurrentPosition] " + currentScrollPosition + "\n" +
1047 "[CandidateDelta] " + panAnimationDelta + "\n" +
1048 "[Destination] " + destination + "\n" +
1049 "[Duration] " + panAnimationDuration + "\n" +
1050 "================================ \n"
1054 finalTargetPosition = destination;
1056 customScrollAlphaFunction = new UserAlphaFunctionDelegate(CustomScrollAlphaFunction);
1057 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(customScrollAlphaFunction);
1058 GC.KeepAlive(customScrollAlphaFunction);
1059 scrollAnimation.Duration = (int)panAnimationDuration;
1060 scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", destination);
1061 scrollAnimation.Play();
1064 protected void OnTapGestureDetected(object source, TapGestureDetector.DetectedEventArgs e)
1069 private void ScrollAnimationFinished(object sender, EventArgs e)
1071 OnScrollAnimationEnded();
1075 /// Adjust scrolling position by own scrolling rules.
1076 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1078 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
1079 [EditorBrowsable(EditorBrowsableState.Never)]
1080 protected virtual float AdjustTargetPositionOfScrollAnimation(float position)