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;
447 private bool hideScrollbar = true;
448 private float maxScrollDistance;
449 private float childTargetPosition = 0.0f;
450 private PanGestureDetector mPanGestureDetector;
451 private View mInterruptTouchingChild;
452 private ScrollbarBase scrollBar;
453 private bool scrolling = false;
454 private float ratioOfScreenWidthToCompleteScroll = 0.5f;
455 private float totalDisplacementForPan = 0.0f;
456 private Size previousContainerSize = new Size();
458 // If false then can only flick pages when the current animation/scroll as ended.
459 private bool flickWhenAnimating = false;
460 private PropertyNotification propertyNotification;
462 private float noticeAnimationEndBeforePosition = 0.0f;
463 private bool readyToNotice = false;
464 // Let's consider more whether this needs to be set as protected.
465 public float NoticeAnimationEndBeforePosition { get => noticeAnimationEndBeforePosition; set => noticeAnimationEndBeforePosition = value; }
468 // Let's consider more whether this needs to be set as protected.
469 private float finalTargetPosition;
471 private Animation scrollAnimation;
472 // Declare user alpha function delegate
473 [UnmanagedFunctionPointer(CallingConvention.StdCall)]
474 private delegate float UserAlphaFunctionDelegate(float progress);
475 private UserAlphaFunctionDelegate customScrollAlphaFunction;
476 private float velocityOfLastPan = 0.0f;
477 private float panAnimationDuration = 0.0f;
478 private float panAnimationDelta = 0.0f;
479 private float logValueOfDeceleration = 0.0f;
480 private float decelerationRate = 0.0f;
484 /// [Draft] Constructor
486 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
487 [EditorBrowsable(EditorBrowsableState.Never)]
488 public ScrollableBase() : base()
490 DecelerationRate = 0.998f;
492 base.Layout = new ScrollableBaseCustomLayout();
493 mPanGestureDetector = new PanGestureDetector();
494 mPanGestureDetector.Attach(this);
495 mPanGestureDetector.AddDirection(PanGestureDetector.DirectionVertical);
496 mPanGestureDetector.Detected += OnPanGestureDetected;
498 ClippingMode = ClippingModeType.ClipChildren;
500 //Default Scrolling child
501 ContentContainer = new View()
503 WidthSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent,
504 HeightSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent,
505 Layout = new AbsoluteLayout() { SetPositionByLayout = false },
507 ContentContainer.Relayout += OnScrollingChildRelayout;
508 propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(1.0f));
509 propertyNotification.Notified += OnPropertyChanged;
510 base.Add(ContentContainer);
512 //Interrupt touching when panning is started
513 mInterruptTouchingChild = new View()
515 Size = new Size(Window.Instance.WindowSize),
516 BackgroundColor = Color.Transparent,
518 mInterruptTouchingChild.TouchEvent += OnIterruptTouchingChildTouched;
519 Scrollbar = new Scrollbar();
522 private bool OnIterruptTouchingChildTouched(object source, View.TouchEventArgs args)
524 if (args.Touch.GetState(0) == PointStateType.Down)
526 if (scrolling && !SnapToPage)
534 private void OnPropertyChanged(object source, PropertyNotification.NotifyEventArgs args)
540 /// Called after a child has been added to the owning view.
542 /// <param name="view">The child which has been added.</param>
543 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
544 [EditorBrowsable(EditorBrowsableState.Never)]
545 public override void Add(View view)
547 ContentContainer.Add(view);
551 /// Called after a child has been removed from the owning view.
553 /// <param name="view">The child which has been removed.</param>
554 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
555 [EditorBrowsable(EditorBrowsableState.Never)]
556 public override void Remove(View view)
558 if (SnapToPage && CurrentPage == Children.IndexOf(view) && CurrentPage == Children.Count - 1)
560 // Target View is current page and also last child.
561 // CurrentPage should be changed to previous page.
562 CurrentPage = Math.Max(0, CurrentPage - 1);
563 ScrollToIndex(CurrentPage);
566 ContentContainer.Remove(view);
569 private void OnScrollingChildRelayout(object source, EventArgs args)
571 // Size is changed. Calculate maxScrollDistance.
572 bool isSizeChanged = previousContainerSize.Width != ContentContainer.Size.Width || previousContainerSize.Height != ContentContainer.Size.Height;
576 maxScrollDistance = CalculateMaximumScrollDistance();
580 previousContainerSize = ContentContainer.Size;
584 /// The composition of a Scrollbar can vary depending on how you use ScrollableBase.
585 /// Set the composition that will go into the ScrollableBase according to your ScrollableBase.
587 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
588 [EditorBrowsable(EditorBrowsableState.Never)]
589 protected virtual void SetScrollbar()
593 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
594 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
595 float viewportLength = isHorizontal ? Size.Width : Size.Height;
596 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
597 Scrollbar.Initialize(contentLength, viewportLength, currentPosition, isHorizontal);
602 /// Scrolls to the item at the specified index.
604 /// <param name="index">Index of item.</param>
605 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
606 [EditorBrowsable(EditorBrowsableState.Never)]
607 public void ScrollToIndex(int index)
609 if (ContentContainer.ChildCount - 1 < index || index < 0)
619 float targetPosition = Math.Min(ScrollingDirection == Direction.Vertical ? Children[index].Position.Y : Children[index].Position.X, maxScrollDistance);
620 AnimateChildTo(ScrollDuration, -targetPosition);
623 private void OnScrollDragStarted()
625 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
626 ScrollDragStarted?.Invoke(this, eventArgs);
629 private void OnScrollDragEnded()
631 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
632 ScrollDragEnded?.Invoke(this, eventArgs);
635 private void OnScrollAnimationStarted()
637 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
638 ScrollAnimationStarted?.Invoke(this, eventArgs);
641 private void OnScrollAnimationEnded()
644 base.Remove(mInterruptTouchingChild);
646 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
647 ScrollAnimationEnded?.Invoke(this, eventArgs);
650 private void OnScroll()
652 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
653 Scrolling?.Invoke(this, eventArgs);
655 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
656 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
657 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
659 scrollBar.Update(contentLength, Math.Abs(currentPosition));
660 CheckPreReachedTargetPosition();
663 private void CheckPreReachedTargetPosition()
665 // Check whether we reached pre-reached target position
667 ContentContainer.CurrentPosition.Y <= finalTargetPosition + NoticeAnimationEndBeforePosition &&
668 ContentContainer.CurrentPosition.Y >= finalTargetPosition - NoticeAnimationEndBeforePosition)
671 readyToNotice = false;
672 OnPreReachedTargetPosition(finalTargetPosition);
677 /// This helps developer who wants to know before scroll is reaching target position.
679 /// <param name="targetPosition">Index of item.</param>
680 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
681 [EditorBrowsable(EditorBrowsableState.Never)]
682 protected virtual void OnPreReachedTargetPosition(float targetPosition)
687 private void StopScroll()
689 if (scrollAnimation != null)
691 if (scrollAnimation.State == Animation.States.Playing)
693 Debug.WriteLineIf(LayoutDebugScrollableBase, "StopScroll Animation Playing");
694 scrollAnimation.Stop(Animation.EndActions.Cancel);
695 OnScrollAnimationEnded();
697 scrollAnimation.Clear();
701 // static constructor registers the control type
702 static ScrollableBase()
704 // ViewRegistry registers control type with DALi type registry
705 // also uses introspection to find any properties that need to be registered with type registry
706 CustomViewRegistry.Instance.Register(CreateInstance, typeof(ScrollableBase));
709 internal static CustomView CreateInstance()
711 return new ScrollableBase();
714 private void AnimateChildTo(int duration, float axisPosition)
716 Debug.WriteLineIf(LayoutDebugScrollableBase, "AnimationTo Animation Duration:" + duration + " Destination:" + axisPosition);
717 finalTargetPosition = axisPosition;
719 StopScroll(); // Will replace previous animation so will stop existing one.
721 if (scrollAnimation == null)
723 scrollAnimation = new Animation();
724 scrollAnimation.Finished += ScrollAnimationFinished;
727 scrollAnimation.Duration = duration;
728 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSquare);
729 scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", axisPosition);
731 OnScrollAnimationStarted();
732 scrollAnimation.Play();
736 /// Scroll to specific position with or without animation.
738 /// <param name="position">Destination.</param>
739 /// <param name="animate">Scroll with or without animation</param>
740 [EditorBrowsable(EditorBrowsableState.Never)]
741 public void ScrollTo(float position, bool animate)
743 float currentPositionX = ContentContainer.CurrentPosition.X != 0 ? ContentContainer.CurrentPosition.X : ContentContainer.Position.X;
744 float currentPositionY = ContentContainer.CurrentPosition.Y != 0 ? ContentContainer.CurrentPosition.Y : ContentContainer.Position.Y;
745 float delta = ScrollingDirection == Direction.Horizontal ? currentPositionX : currentPositionY;
746 // The argument position is the new pan position. So the new position of ScrollableBase becomes (-position).
747 // To move ScrollableBase's position to (-position), it moves by (-position - currentPosition).
748 delta = -position - delta;
750 ScrollBy(delta, animate);
753 private float BoundScrollPosition(float targetPosition)
755 if (ScrollAvailableArea != null)
757 float minScrollPosition = ScrollAvailableArea.X;
758 float maxScrollPosition = ScrollAvailableArea.Y;
760 targetPosition = Math.Min(-minScrollPosition, targetPosition);
761 targetPosition = Math.Max(-maxScrollPosition, targetPosition);
765 targetPosition = Math.Min(0, targetPosition);
766 targetPosition = Math.Max(-maxScrollDistance, targetPosition);
769 return targetPosition;
772 private void ScrollBy(float displacement, bool animate)
774 if (GetChildCount() == 0 || maxScrollDistance < 0)
779 float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? ContentContainer.PositionX : ContentContainer.PositionY;
781 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy childCurrentPosition:" + childCurrentPosition +
782 " displacement:" + displacement,
783 " maxScrollDistance:" + maxScrollDistance);
785 childTargetPosition = childCurrentPosition + displacement; // child current position + gesture displacement
788 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition);
792 // Calculate scroll animaton duration
793 float scrollDistance = Math.Abs(displacement);
794 readyToNotice = true;
796 AnimateChildTo(ScrollDuration, BoundScrollPosition(AdjustTargetPositionOfScrollAnimation(BoundScrollPosition(childTargetPosition))));
800 finalTargetPosition = BoundScrollPosition(childTargetPosition);
802 // Set position of scrolling child without an animation
803 if (ScrollingDirection == Direction.Horizontal)
805 ContentContainer.PositionX = finalTargetPosition;
809 ContentContainer.PositionY = finalTargetPosition;
816 /// you can override it to clean-up your own resources.
818 /// <param name="type">DisposeTypes</param>
819 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
820 [EditorBrowsable(EditorBrowsableState.Never)]
821 protected override void Dispose(DisposeTypes type)
828 if (type == DisposeTypes.Explicit)
832 if (mPanGestureDetector != null)
834 mPanGestureDetector.Detected -= OnPanGestureDetected;
835 mPanGestureDetector.Dispose();
836 mPanGestureDetector = null;
842 private float CalculateMaximumScrollDistance()
844 float scrollingChildLength = 0;
845 float scrollerLength = 0;
846 if (ScrollingDirection == Direction.Horizontal)
848 Debug.WriteLineIf(LayoutDebugScrollableBase, "Horizontal");
850 scrollingChildLength = ContentContainer.Size.Width;
851 scrollerLength = Size.Width;
855 Debug.WriteLineIf(LayoutDebugScrollableBase, "Vertical");
856 scrollingChildLength = ContentContainer.Size.Height;
857 scrollerLength = Size.Height;
860 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy maxScrollDistance:" + (scrollingChildLength - scrollerLength) +
861 " parent length:" + scrollerLength +
862 " scrolling child length:" + scrollingChildLength);
864 return Math.Max(scrollingChildLength - scrollerLength, 0);
867 private void PageSnap()
869 Debug.WriteLineIf(LayoutDebugScrollableBase, "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan +
870 " currentPage[" + CurrentPage + "]");
872 //Increment current page if total displacement enough to warrant a page change.
873 if (Math.Abs(totalDisplacementForPan) > (mPageWidth * ratioOfScreenWidthToCompleteScroll))
875 if (totalDisplacementForPan < 0)
877 CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
881 CurrentPage = Math.Max(0, --CurrentPage);
885 // Animate to new page or reposition to current page
886 float destinationX = -(Children[CurrentPage].Position.X + Children[CurrentPage].CurrentSize.Width / 2 - CurrentSize.Width / 2); // set to middle of current page
887 Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to page[" + CurrentPage + "] to:" + destinationX + " from:" + ContentContainer.PositionX);
888 AnimateChildTo(ScrollDuration, destinationX);
891 private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
893 if (e.PanGesture.State == Gesture.StateType.Started)
895 readyToNotice = false;
896 base.Add(mInterruptTouchingChild);
897 Debug.WriteLineIf(LayoutDebugScrollableBase, "Gesture Start");
898 if (scrolling && !SnapToPage)
902 totalDisplacementForPan = 0.0f;
903 OnScrollDragStarted();
905 else if (e.PanGesture.State == Gesture.StateType.Continuing)
907 if (ScrollingDirection == Direction.Horizontal)
909 ScrollBy(e.PanGesture.Displacement.X, false);
910 totalDisplacementForPan += e.PanGesture.Displacement.X;
914 ScrollBy(e.PanGesture.Displacement.Y, false);
915 totalDisplacementForPan += e.PanGesture.Displacement.Y;
917 Debug.WriteLineIf(LayoutDebugScrollableBase, "OnPanGestureDetected Continue totalDisplacementForPan:" + totalDisplacementForPan);
919 else if (e.PanGesture.State == Gesture.StateType.Finished)
922 StopScroll(); // Will replace previous animation so will stop existing one.
924 if (scrollAnimation == null)
926 scrollAnimation = new Animation();
927 scrollAnimation.Finished += ScrollAnimationFinished;
936 Decelerating((ScrollingDirection == Direction.Horizontal) ? e.PanGesture.Velocity.X : e.PanGesture.Velocity.Y);
939 totalDisplacementForPan = 0;
941 readyToNotice = true;
942 OnScrollAnimationStarted();
946 private float CustomScrollAlphaFunction(float progress)
948 if (panAnimationDelta == 0)
954 // Parameter "progress" is normalized value. We need to multiply target duration to calculate distance.
955 // Can get real distance using equation of deceleration (check Decelerating function)
956 // After get real distance, normalize it
957 float realDuration = progress * panAnimationDuration;
958 float realDistance = velocityOfLastPan * ((float)Math.Pow(decelerationRate, realDuration) - 1) / logValueOfDeceleration;
959 float result = Math.Min(realDistance / Math.Abs(panAnimationDelta), 1.0f);
964 private void Decelerating(float velocity)
966 // Decelerating using deceleration equation ===========
968 // V : velocity (pixel per milisecond)
969 // V0 : initial velocity
970 // d : deceleration rate,
972 // X : final position after decelerating
973 // log : natural logarithm
975 // V(t) = V0 * d pow t;
976 // X(t) = V0 * (d pow t - 1) / log d; <-- Integrate the velocity function
977 // X(∞) = V0 * d / (1 - d); <-- Result using inifit T can be final position because T is tending to infinity.
979 // Because of final T is tending to inifity, we should use threshold value to finish.
980 // Final T = log(-threshold * log d / |V0| ) / log d;
982 velocityOfLastPan = Math.Abs(velocity);
984 float currentScrollPosition = -(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
985 panAnimationDelta = (velocityOfLastPan * decelerationRate) / (1 - decelerationRate);
986 panAnimationDelta = velocity > 0 ? -panAnimationDelta : panAnimationDelta;
988 float destination = -(panAnimationDelta + currentScrollPosition);
989 float adjustDestination = AdjustTargetPositionOfScrollAnimation(destination);
990 float maxPosition = ScrollAvailableArea != null ? ScrollAvailableArea.Y : maxScrollDistance;
991 float minPosition = ScrollAvailableArea != null ? ScrollAvailableArea.X : 0;
993 if (destination < -maxPosition || destination > minPosition)
995 panAnimationDelta = velocity > 0 ? (currentScrollPosition - minPosition) : (maxPosition - currentScrollPosition);
996 destination = velocity > 0 ? minPosition : -maxPosition;
998 if (panAnimationDelta == 0)
1000 panAnimationDuration = 0.0f;
1004 panAnimationDuration = (float)Math.Log((panAnimationDelta * logValueOfDeceleration / velocityOfLastPan + 1), decelerationRate);
1007 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1008 "OverRange======================= \n" +
1009 "[decelerationRate] " + decelerationRate + "\n" +
1010 "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1011 "[Velocity] " + velocityOfLastPan + "\n" +
1012 "[CurrentPosition] " + currentScrollPosition + "\n" +
1013 "[CandidateDelta] " + panAnimationDelta + "\n" +
1014 "[Destination] " + destination + "\n" +
1015 "[Duration] " + panAnimationDuration + "\n" +
1016 "================================ \n"
1021 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1023 if (adjustDestination != destination)
1025 destination = adjustDestination;
1026 panAnimationDelta = destination + currentScrollPosition;
1027 velocityOfLastPan = Math.Abs(panAnimationDelta * logValueOfDeceleration / ((float)Math.Pow(decelerationRate, panAnimationDuration) - 1));
1028 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1031 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1032 "================================ \n" +
1033 "[decelerationRate] " + decelerationRate + "\n" +
1034 "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1035 "[Velocity] " + velocityOfLastPan + "\n" +
1036 "[CurrentPosition] " + currentScrollPosition + "\n" +
1037 "[CandidateDelta] " + panAnimationDelta + "\n" +
1038 "[Destination] " + destination + "\n" +
1039 "[Duration] " + panAnimationDuration + "\n" +
1040 "================================ \n"
1044 finalTargetPosition = destination;
1046 customScrollAlphaFunction = new UserAlphaFunctionDelegate(CustomScrollAlphaFunction);
1047 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(customScrollAlphaFunction);
1048 GC.KeepAlive(customScrollAlphaFunction);
1049 scrollAnimation.Duration = (int)panAnimationDuration;
1050 scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", destination);
1051 scrollAnimation.Play();
1054 protected void OnTapGestureDetected(object source, TapGestureDetector.DetectedEventArgs e)
1059 private void ScrollAnimationFinished(object sender, EventArgs e)
1061 OnScrollAnimationEnded();
1065 /// Adjust scrolling position by own scrolling rules.
1066 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1068 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
1069 [EditorBrowsable(EditorBrowsableState.Never)]
1070 protected virtual float AdjustTargetPositionOfScrollAnimation(float position)