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 private void AnimateChildTo(int duration, float axisPosition)
703 Debug.WriteLineIf(LayoutDebugScrollableBase, "AnimationTo Animation Duration:" + duration + " Destination:" + axisPosition);
704 finalTargetPosition = axisPosition;
706 StopScroll(); // Will replace previous animation so will stop existing one.
708 if (scrollAnimation == null)
710 scrollAnimation = new Animation();
711 scrollAnimation.Finished += ScrollAnimationFinished;
714 scrollAnimation.Duration = duration;
715 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSquare);
716 scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", axisPosition);
718 OnScrollAnimationStarted();
719 scrollAnimation.Play();
723 /// Scroll to specific position with or without animation.
725 /// <param name="position">Destination.</param>
726 /// <param name="animate">Scroll with or without animation</param>
727 [EditorBrowsable(EditorBrowsableState.Never)]
728 public void ScrollTo(float position, bool animate)
730 float currentPositionX = ContentContainer.CurrentPosition.X != 0 ? ContentContainer.CurrentPosition.X : ContentContainer.Position.X;
731 float currentPositionY = ContentContainer.CurrentPosition.Y != 0 ? ContentContainer.CurrentPosition.Y : ContentContainer.Position.Y;
732 float delta = ScrollingDirection == Direction.Horizontal ? currentPositionX : currentPositionY;
733 // The argument position is the new pan position. So the new position of ScrollableBase becomes (-position).
734 // To move ScrollableBase's position to (-position), it moves by (-position - currentPosition).
735 delta = -position - delta;
737 ScrollBy(delta, animate);
740 private float BoundScrollPosition(float targetPosition)
742 if (ScrollAvailableArea != null)
744 float minScrollPosition = ScrollAvailableArea.X;
745 float maxScrollPosition = ScrollAvailableArea.Y;
747 targetPosition = Math.Min(-minScrollPosition, targetPosition);
748 targetPosition = Math.Max(-maxScrollPosition, targetPosition);
752 targetPosition = Math.Min(0, targetPosition);
753 targetPosition = Math.Max(-maxScrollDistance, targetPosition);
756 return targetPosition;
759 private void ScrollBy(float displacement, bool animate)
761 if (GetChildCount() == 0 || maxScrollDistance < 0)
766 float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? ContentContainer.PositionX : ContentContainer.PositionY;
768 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy childCurrentPosition:" + childCurrentPosition +
769 " displacement:" + displacement,
770 " maxScrollDistance:" + maxScrollDistance);
772 childTargetPosition = childCurrentPosition + displacement; // child current position + gesture displacement
775 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition);
779 // Calculate scroll animaton duration
780 float scrollDistance = Math.Abs(displacement);
781 readyToNotice = true;
783 AnimateChildTo(ScrollDuration, BoundScrollPosition(AdjustTargetPositionOfScrollAnimation(BoundScrollPosition(childTargetPosition))));
787 finalTargetPosition = BoundScrollPosition(childTargetPosition);
789 // Set position of scrolling child without an animation
790 if (ScrollingDirection == Direction.Horizontal)
792 ContentContainer.PositionX = finalTargetPosition;
796 ContentContainer.PositionY = finalTargetPosition;
803 /// you can override it to clean-up your own resources.
805 /// <param name="type">DisposeTypes</param>
806 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
807 [EditorBrowsable(EditorBrowsableState.Never)]
808 protected override void Dispose(DisposeTypes type)
815 if (type == DisposeTypes.Explicit)
819 if (mPanGestureDetector != null)
821 mPanGestureDetector.Detected -= OnPanGestureDetected;
822 mPanGestureDetector.Dispose();
823 mPanGestureDetector = null;
829 private float CalculateMaximumScrollDistance()
831 float scrollingChildLength = 0;
832 float scrollerLength = 0;
833 if (ScrollingDirection == Direction.Horizontal)
835 Debug.WriteLineIf(LayoutDebugScrollableBase, "Horizontal");
837 scrollingChildLength = ContentContainer.Size.Width;
838 scrollerLength = Size.Width;
842 Debug.WriteLineIf(LayoutDebugScrollableBase, "Vertical");
843 scrollingChildLength = ContentContainer.Size.Height;
844 scrollerLength = Size.Height;
847 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy maxScrollDistance:" + (scrollingChildLength - scrollerLength) +
848 " parent length:" + scrollerLength +
849 " scrolling child length:" + scrollingChildLength);
851 return Math.Max(scrollingChildLength - scrollerLength, 0);
854 private void PageSnap()
856 Debug.WriteLineIf(LayoutDebugScrollableBase, "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan +
857 " currentPage[" + CurrentPage + "]");
859 //Increment current page if total displacement enough to warrant a page change.
860 if (Math.Abs(totalDisplacementForPan) > (mPageWidth * ratioOfScreenWidthToCompleteScroll))
862 if (totalDisplacementForPan < 0)
864 CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
868 CurrentPage = Math.Max(0, --CurrentPage);
872 // Animate to new page or reposition to current page
873 float destinationX = -(Children[CurrentPage].Position.X + Children[CurrentPage].CurrentSize.Width / 2 - CurrentSize.Width / 2); // set to middle of current page
874 Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to page[" + CurrentPage + "] to:" + destinationX + " from:" + ContentContainer.PositionX);
875 AnimateChildTo(ScrollDuration, destinationX);
878 private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
880 if (e.PanGesture.State == Gesture.StateType.Started)
882 readyToNotice = false;
883 base.Add(mInterruptTouchingChild);
884 Debug.WriteLineIf(LayoutDebugScrollableBase, "Gesture Start");
885 if (scrolling && !SnapToPage)
889 totalDisplacementForPan = 0.0f;
890 OnScrollDragStarted();
892 else if (e.PanGesture.State == Gesture.StateType.Continuing)
894 if (ScrollingDirection == Direction.Horizontal)
896 ScrollBy(e.PanGesture.Displacement.X, false);
897 totalDisplacementForPan += e.PanGesture.Displacement.X;
901 ScrollBy(e.PanGesture.Displacement.Y, false);
902 totalDisplacementForPan += e.PanGesture.Displacement.Y;
904 Debug.WriteLineIf(LayoutDebugScrollableBase, "OnPanGestureDetected Continue totalDisplacementForPan:" + totalDisplacementForPan);
906 else if (e.PanGesture.State == Gesture.StateType.Finished)
909 StopScroll(); // Will replace previous animation so will stop existing one.
911 if (scrollAnimation == null)
913 scrollAnimation = new Animation();
914 scrollAnimation.Finished += ScrollAnimationFinished;
923 Decelerating((ScrollingDirection == Direction.Horizontal) ? e.PanGesture.Velocity.X : e.PanGesture.Velocity.Y);
926 totalDisplacementForPan = 0;
928 readyToNotice = true;
929 OnScrollAnimationStarted();
933 private float CustomScrollAlphaFunction(float progress)
935 if (panAnimationDelta == 0)
941 // Parameter "progress" is normalized value. We need to multiply target duration to calculate distance.
942 // Can get real distance using equation of deceleration (check Decelerating function)
943 // After get real distance, normalize it
944 float realDuration = progress * panAnimationDuration;
945 float realDistance = velocityOfLastPan * ((float)Math.Pow(decelerationRate, realDuration) - 1) / logValueOfDeceleration;
946 float result = Math.Min(realDistance / Math.Abs(panAnimationDelta), 1.0f);
951 private void Decelerating(float velocity)
953 // Decelerating using deceleration equation ===========
955 // V : velocity (pixel per milisecond)
956 // V0 : initial velocity
957 // d : deceleration rate,
959 // X : final position after decelerating
960 // log : natural logarithm
962 // V(t) = V0 * d pow t;
963 // X(t) = V0 * (d pow t - 1) / log d; <-- Integrate the velocity function
964 // X(∞) = V0 * d / (1 - d); <-- Result using inifit T can be final position because T is tending to infinity.
966 // Because of final T is tending to inifity, we should use threshold value to finish.
967 // Final T = log(-threshold * log d / |V0| ) / log d;
969 velocityOfLastPan = Math.Abs(velocity);
971 float currentScrollPosition = -(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
972 panAnimationDelta = (velocityOfLastPan * decelerationRate) / (1 - decelerationRate);
973 panAnimationDelta = velocity > 0 ? -panAnimationDelta : panAnimationDelta;
975 float destination = -(panAnimationDelta + currentScrollPosition);
976 float adjustDestination = AdjustTargetPositionOfScrollAnimation(destination);
977 float maxPosition = ScrollAvailableArea != null ? ScrollAvailableArea.Y : maxScrollDistance;
978 float minPosition = ScrollAvailableArea != null ? ScrollAvailableArea.X : 0;
980 if (destination < -maxPosition || destination > minPosition)
982 panAnimationDelta = velocity > 0 ? (currentScrollPosition - minPosition) : (maxPosition - currentScrollPosition);
983 destination = velocity > 0 ? minPosition : -maxPosition;
985 if (panAnimationDelta == 0)
987 panAnimationDuration = 0.0f;
991 panAnimationDuration = (float)Math.Log((panAnimationDelta * logValueOfDeceleration / velocityOfLastPan + 1), decelerationRate);
994 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
995 "OverRange======================= \n" +
996 "[decelerationRate] " + decelerationRate + "\n" +
997 "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
998 "[Velocity] " + velocityOfLastPan + "\n" +
999 "[CurrentPosition] " + currentScrollPosition + "\n" +
1000 "[CandidateDelta] " + panAnimationDelta + "\n" +
1001 "[Destination] " + destination + "\n" +
1002 "[Duration] " + panAnimationDuration + "\n" +
1003 "================================ \n"
1008 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1010 if (adjustDestination != destination)
1012 destination = adjustDestination;
1013 panAnimationDelta = destination + currentScrollPosition;
1014 velocityOfLastPan = Math.Abs(panAnimationDelta * logValueOfDeceleration / ((float)Math.Pow(decelerationRate, panAnimationDuration) - 1));
1015 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1018 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1019 "================================ \n" +
1020 "[decelerationRate] " + decelerationRate + "\n" +
1021 "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1022 "[Velocity] " + velocityOfLastPan + "\n" +
1023 "[CurrentPosition] " + currentScrollPosition + "\n" +
1024 "[CandidateDelta] " + panAnimationDelta + "\n" +
1025 "[Destination] " + destination + "\n" +
1026 "[Duration] " + panAnimationDuration + "\n" +
1027 "================================ \n"
1031 finalTargetPosition = destination;
1033 customScrollAlphaFunction = new UserAlphaFunctionDelegate(CustomScrollAlphaFunction);
1034 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(customScrollAlphaFunction);
1035 GC.KeepAlive(customScrollAlphaFunction);
1036 scrollAnimation.Duration = (int)panAnimationDuration;
1037 scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", destination);
1038 scrollAnimation.Play();
1041 protected void OnTapGestureDetected(object source, TapGestureDetector.DetectedEventArgs e)
1046 private void ScrollAnimationFinished(object sender, EventArgs e)
1048 OnScrollAnimationEnded();
1052 /// Adjust scrolling position by own scrolling rules.
1053 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1055 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
1056 [EditorBrowsable(EditorBrowsableState.Never)]
1057 protected virtual float AdjustTargetPositionOfScrollAnimation(float position)