1 /* Copyright (c) 2020 Samsung Electronics Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
17 using Tizen.NUI.BaseComponents;
18 using System.Collections.Generic;
19 using System.ComponentModel;
20 using System.Diagnostics;
21 using System.Runtime.InteropServices;
22 using Tizen.NUI.Accessibility;
24 namespace Tizen.NUI.Components
27 /// ScrollEventArgs is a class to record scroll event arguments which will sent to user.
29 /// <since_tizen> 8 </since_tizen>
30 public class ScrollEventArgs : EventArgs
32 private Position position;
35 /// Default constructor.
37 /// <param name="position">Current scroll position</param>
38 /// <since_tizen> 8 </since_tizen>
39 public ScrollEventArgs(Position position)
41 this.position = position;
45 /// Current position of ContentContainer.
47 /// <since_tizen> 8 </since_tizen>
48 public Position Position
58 /// This class provides a View that can scroll a single View with a layout. This View can be a nest of Views.
60 /// <since_tizen> 8 </since_tizen>
61 public class ScrollableBase : Control
63 static bool LayoutDebugScrollableBase = false; // Debug flag
64 private Direction mScrollingDirection = Direction.Vertical;
65 private bool mScrollEnabled = true;
66 private int mScrollDuration = 125;
67 private int mPageWidth = 0;
68 private float mPageFlickThreshold = 0.4f;
69 private float mScrollingEventThreshold = 0.00001f;
71 private class ScrollableBaseCustomLayout : LayoutGroup
73 protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
75 Extents padding = Padding;
76 float totalHeight = padding.Top + padding.Bottom;
77 float totalWidth = padding.Start + padding.End;
79 MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK;
80 MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK;
82 Direction scrollingDirection = Direction.Vertical;
83 ScrollableBase scrollableBase = this.Owner as ScrollableBase;
86 scrollingDirection = scrollableBase.ScrollingDirection;
89 // measure child, should be a single scrolling child
90 foreach (LayoutItem childLayout in LayoutChildren)
92 if (childLayout != null)
95 // Use an Unspecified MeasureSpecification mode so scrolling child is not restricted to it's parents size in Height (for vertical scrolling)
96 // or Width for horizontal scrolling
97 MeasureSpecification unrestrictedMeasureSpec = new MeasureSpecification(heightMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
99 if (scrollingDirection == Direction.Vertical)
101 MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), unrestrictedMeasureSpec, new LayoutLength(0)); // Height unrestricted by parent
105 MeasureChildWithMargins(childLayout, unrestrictedMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0)); // Width unrestricted by parent
108 float childWidth = childLayout.MeasuredWidth.Size.AsDecimal();
109 float childHeight = childLayout.MeasuredHeight.Size.AsDecimal();
111 // Determine the width and height needed by the children using their given position and size.
112 // Children could overlap so find the left most and right most child.
113 Position2D childPosition = childLayout.Owner.Position2D;
114 float childLeft = childPosition.X;
115 float childTop = childPosition.Y;
117 // Store current width and height needed to contain all children.
118 Extents childMargin = childLayout.Margin;
119 totalWidth = childWidth + childMargin.Start + childMargin.End;
120 totalHeight = childHeight + childMargin.Top + childMargin.Bottom;
122 if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
124 childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
126 if (childLayout.MeasuredHeight.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
128 childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
134 MeasuredSize widthSizeAndState = ResolveSizeAndState(new LayoutLength(totalWidth + Padding.Start + Padding.End), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
135 MeasuredSize heightSizeAndState = ResolveSizeAndState(new LayoutLength(totalHeight + Padding.Top + Padding.Bottom), heightMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
136 totalWidth = widthSizeAndState.Size.AsDecimal();
137 totalHeight = heightSizeAndState.Size.AsDecimal();
139 // Ensure layout respects it's given minimum size
140 totalWidth = Math.Max(totalWidth, SuggestedMinimumWidth.AsDecimal());
141 totalHeight = Math.Max(totalHeight, SuggestedMinimumHeight.AsDecimal());
143 widthSizeAndState.State = childWidthState;
144 heightSizeAndState.State = childHeightState;
146 SetMeasuredDimensions(ResolveSizeAndState(new LayoutLength(totalWidth + Padding.Start + Padding.End), widthMeasureSpec, childWidthState),
147 ResolveSizeAndState(new LayoutLength(totalHeight + Padding.Top + Padding.Bottom), heightMeasureSpec, childHeightState));
149 // Size of ScrollableBase is changed. Change Page width too.
150 scrollableBase.mPageWidth = (int)MeasuredWidth.Size.AsRoundedValue();
151 scrollableBase.OnScrollingChildRelayout(null , null);
154 protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
156 foreach (LayoutItem childLayout in LayoutChildren)
158 if (childLayout != null)
160 LayoutLength childWidth = childLayout.MeasuredWidth.Size;
161 LayoutLength childHeight = childLayout.MeasuredHeight.Size;
163 Position2D childPosition = childLayout.Owner.Position2D;
164 Extents padding = Padding;
165 Extents childMargin = childLayout.Margin;
167 LayoutLength childLeft = new LayoutLength(childPosition.X + childMargin.Start + padding.Start);
168 LayoutLength childTop = new LayoutLength(childPosition.Y + childMargin.Top + padding.Top);
170 childLayout.Layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
174 } // ScrollableBaseCustomLayout
177 /// The direction axis to scroll.
179 /// <since_tizen> 8 </since_tizen>
180 public enum Direction
185 /// <since_tizen> 8 </since_tizen>
191 /// <since_tizen> 8 </since_tizen>
196 /// Scrolling direction mode.
197 /// Default is Vertical scrolling.
199 /// <since_tizen> 8 </since_tizen>
200 public Direction ScrollingDirection
204 return mScrollingDirection;
208 if (value != mScrollingDirection)
210 mScrollingDirection = value;
211 mPanGestureDetector.RemoveDirection(value == Direction.Horizontal ?
212 PanGestureDetector.DirectionVertical : PanGestureDetector.DirectionHorizontal);
213 mPanGestureDetector.AddDirection(value == Direction.Horizontal ?
214 PanGestureDetector.DirectionHorizontal : PanGestureDetector.DirectionVertical);
216 ContentContainer.WidthSpecification = mScrollingDirection == Direction.Vertical ?
217 LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent;
218 ContentContainer.HeightSpecification = mScrollingDirection == Direction.Vertical ?
219 LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent;
225 /// Enable or disable scrolling.
227 /// <since_tizen> 8 </since_tizen>
228 public bool ScrollEnabled
232 return mScrollEnabled;
236 if (value != mScrollEnabled)
238 mScrollEnabled = value;
241 mPanGestureDetector.Detected += OnPanGestureDetected;
245 mPanGestureDetector.Detected -= OnPanGestureDetected;
252 /// Pages mode, enables moving to the next or return to current page depending on pan displacement.
253 /// Default is false.
255 /// <since_tizen> 8 </since_tizen>
256 public bool SnapToPage { set; get; } = false;
259 /// Get current page.
260 /// Working property with SnapToPage property.
262 /// <since_tizen> 8 </since_tizen>
263 public int CurrentPage { get; private set; } = 0;
266 /// Duration of scroll animation.
267 /// Default value is 125ms.
269 /// <since_tizen> 8 </since_tizen>
270 public int ScrollDuration
274 mScrollDuration = value >= 0 ? value : mScrollDuration;
278 return mScrollDuration;
283 /// Scroll Available area.
285 /// <since_tizen> 8 </since_tizen>
286 public Vector2 ScrollAvailableArea { set; get; }
289 /// An event emitted when user starts dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
291 /// <since_tizen> 8 </since_tizen>
292 public event EventHandler<ScrollEventArgs> ScrollDragStarted;
295 /// An event emitted when user stops dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
297 /// <since_tizen> 8 </since_tizen>
298 public event EventHandler<ScrollEventArgs> ScrollDragEnded;
302 /// An event emitted when the scrolling slide animation starts, user can subscribe or unsubscribe to this event handler.<br />
304 /// <since_tizen> 8 </since_tizen>
305 public event EventHandler<ScrollEventArgs> ScrollAnimationStarted;
308 /// An event emitted when the scrolling slide animation ends, user can subscribe or unsubscribe to this event handler.<br />
310 /// <since_tizen> 8 </since_tizen>
311 public event EventHandler<ScrollEventArgs> ScrollAnimationEnded;
315 /// An event emitted when scrolling, user can subscribe or unsubscribe to this event handler.<br />
317 /// <since_tizen> 8 </since_tizen>
318 public event EventHandler<ScrollEventArgs> Scrolling;
322 /// Scrollbar for ScrollableBase.
324 /// <since_tizen> 8 </since_tizen>
325 public ScrollbarBase Scrollbar
335 scrollBar.Unparent();
339 if (scrollBar != null)
341 scrollBar.Name = "ScrollBar";
359 /// Always hide Scrollbar.
361 /// <since_tizen> 8 </since_tizen>
362 public bool HideScrollbar
366 return hideScrollbar;
370 hideScrollbar = value;
387 /// Container which has content of ScrollableBase.
389 /// <since_tizen> 8 </since_tizen>
390 public View ContentContainer { get; private set; }
393 /// Set the layout on this View. Replaces any existing Layout.
395 /// <since_tizen> 8 </since_tizen>
396 public new LayoutItem Layout
400 return ContentContainer.Layout;
404 ContentContainer.Layout = value;
405 if (ContentContainer.Layout != null)
407 ContentContainer.Layout.SetPositionByLayout = false;
413 /// List of children of Container.
415 /// <since_tizen> 8 </since_tizen>
416 public new List<View> Children
420 return ContentContainer.Children;
425 /// Deceleration rate of scrolling by finger.
426 /// Rate should be bigger than 0 and smaller than 1.
427 /// Default value is 0.998f;
429 /// <since_tizen> 8 </since_tizen>
430 public float DecelerationRate
434 return decelerationRate;
438 decelerationRate = (value < 1 && value > 0) ? value : decelerationRate;
439 logValueOfDeceleration = (float)Math.Log(value);
444 /// Threashold not to go infinit at the end of scrolling animation.
446 [EditorBrowsable(EditorBrowsableState.Never)]
447 public float DecelerationThreshold { get; set; } = 0.1f;
450 /// Scrolling event will be thrown when this amount of scroll positino is changed.
452 [EditorBrowsable(EditorBrowsableState.Never)]
453 public float ScrollingEventThreshold
457 return mScrollingEventThreshold;
461 if (mScrollingEventThreshold != value && value > 0)
463 ContentContainer.RemovePropertyNotification(propertyNotification);
464 propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(value));
465 propertyNotification.Notified += OnPropertyChanged;
466 mScrollingEventThreshold = value;
473 /// Page will be changed when velocity of panning is over threshold.
474 /// The unit of threshold is pixel per milisec.
476 /// <since_tizen> 8 </since_tizen>
477 public float PageFlickThreshold
481 return mPageFlickThreshold;
485 mPageFlickThreshold = value >= 0f ? value : mPageFlickThreshold;
490 /// Alphafunction for scroll animation.
492 [EditorBrowsable(EditorBrowsableState.Never)]
493 public AlphaFunction ScrollAlphaFunction { get; set; } = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
495 private bool hideScrollbar = true;
496 private float maxScrollDistance;
497 private float childTargetPosition = 0.0f;
498 private PanGestureDetector mPanGestureDetector;
499 private View mInterruptTouchingChild;
500 private ScrollbarBase scrollBar;
501 private bool scrolling = false;
502 private float ratioOfScreenWidthToCompleteScroll = 0.5f;
503 private float totalDisplacementForPan = 0.0f;
504 private Size previousContainerSize = new Size();
505 private Size previousSize = new Size();
506 private PropertyNotification propertyNotification;
507 private float noticeAnimationEndBeforePosition = 0.0f;
508 private bool readyToNotice = false;
511 /// Notice before animation is finished.
513 [EditorBrowsable(EditorBrowsableState.Never)]
514 // Let's consider more whether this needs to be set as protected.
515 public float NoticeAnimationEndBeforePosition { get => noticeAnimationEndBeforePosition; set => noticeAnimationEndBeforePosition = value; }
517 // Let's consider more whether this needs to be set as protected.
518 private float finalTargetPosition;
520 private Animation scrollAnimation;
521 // Declare user alpha function delegate
522 [UnmanagedFunctionPointer(CallingConvention.StdCall)]
523 private delegate float UserAlphaFunctionDelegate(float progress);
524 private UserAlphaFunctionDelegate customScrollAlphaFunction;
525 private float velocityOfLastPan = 0.0f;
526 private float panAnimationDuration = 0.0f;
527 private float panAnimationDelta = 0.0f;
528 private float logValueOfDeceleration = 0.0f;
529 private float decelerationRate = 0.0f;
531 private View mVerticalTopShadowView;
532 private View mVerticalBottomShadowView;
533 private const int mVerticalShadowScaleHeightLimit = 64 * 3;
534 private const int mVerticalShadowAnimationDuration = 300;
535 private Animation mVerticalShadowAnimation;
536 private bool isVerticalShadowShown = false;
537 private float mStartShowShadowDisplacement;
540 /// Default Constructor
542 /// <since_tizen> 8 </since_tizen>
543 public ScrollableBase() : base()
545 DecelerationRate = 0.998f;
547 base.Layout = new ScrollableBaseCustomLayout();
548 mPanGestureDetector = new PanGestureDetector();
549 mPanGestureDetector.Attach(this);
550 mPanGestureDetector.AddDirection(PanGestureDetector.DirectionVertical);
551 mPanGestureDetector.Detected += OnPanGestureDetected;
553 ClippingMode = ClippingModeType.ClipChildren;
555 //Default Scrolling child
556 ContentContainer = new View()
558 WidthSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent,
559 HeightSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent,
560 Layout = new AbsoluteLayout() { SetPositionByLayout = false },
562 ContentContainer.Relayout += OnScrollingChildRelayout;
563 propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(mScrollingEventThreshold));
564 propertyNotification.Notified += OnPropertyChanged;
565 base.Add(ContentContainer);
567 //Interrupt touching when panning is started
568 mInterruptTouchingChild = new View()
570 Size = new Size(Window.Instance.WindowSize),
571 BackgroundColor = Color.Transparent,
573 mInterruptTouchingChild.TouchEvent += OnIterruptTouchingChildTouched;
574 Scrollbar = new Scrollbar();
576 //Show vertical shadow when panning down (or up) on the scroll top (or end).
577 mVerticalTopShadowView = new View
579 BackgroundImage = StyleManager.GetFrameworkResourcePath("nui_component_default_scroll_over_shooting_top.png"),
582 PositionUsesPivotPoint = true,
583 ParentOrigin = NUI.ParentOrigin.TopCenter,
584 PivotPoint = NUI.PivotPoint.TopCenter,
586 mVerticalBottomShadowView = new View
588 BackgroundImage = StyleManager.GetFrameworkResourcePath("nui_component_default_scroll_over_shooting_bottom.png"),
591 PositionUsesPivotPoint = true,
592 ParentOrigin = NUI.ParentOrigin.BottomCenter,
593 PivotPoint = NUI.PivotPoint.BottomCenter,
596 AccessibilityManager.Instance.SetAccessibilityAttribute(this, AccessibilityManager.AccessibilityAttribute.Trait, "ScrollableBase");
599 private bool OnIterruptTouchingChildTouched(object source, View.TouchEventArgs args)
601 if (args.Touch.GetState(0) == PointStateType.Down)
603 if (scrolling && !SnapToPage)
611 private void OnPropertyChanged(object source, PropertyNotification.NotifyEventArgs args)
617 /// Called after a child has been added to the owning view.
619 /// <param name="view">The child which has been added.</param>
620 /// <since_tizen> 8 </since_tizen>
621 public override void Add(View view)
623 ContentContainer.Add(view);
627 /// Called after a child has been removed from the owning view.
629 /// <param name="view">The child which has been removed.</param>
630 /// <since_tizen> 8 </since_tizen>
631 public override void Remove(View view)
633 if (SnapToPage && CurrentPage == Children.IndexOf(view) && CurrentPage == Children.Count - 1)
635 // Target View is current page and also last child.
636 // CurrentPage should be changed to previous page.
637 CurrentPage = Math.Max(0, CurrentPage - 1);
638 ScrollToIndex(CurrentPage);
641 ContentContainer.Remove(view);
644 private void OnScrollingChildRelayout(object source, EventArgs args)
646 // Size is changed. Calculate maxScrollDistance.
647 bool isSizeChanged = previousContainerSize.Width != ContentContainer.Size.Width || previousContainerSize.Height != ContentContainer.Size.Height
648 || previousSize.Width != Size.Width || previousSize.Height != Size.Height;
652 maxScrollDistance = CalculateMaximumScrollDistance();
656 previousContainerSize = ContentContainer.Size;
661 /// The composition of a Scrollbar can vary depending on how you use ScrollableBase.
662 /// Set the composition that will go into the ScrollableBase according to your ScrollableBase.
664 /// <since_tizen> 8 </since_tizen>
665 [EditorBrowsable(EditorBrowsableState.Never)]
666 protected virtual void SetScrollbar()
670 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
671 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
672 float viewportLength = isHorizontal ? Size.Width : Size.Height;
673 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
674 Scrollbar.Initialize(contentLength, viewportLength, currentPosition, isHorizontal);
679 /// Scrolls to the item at the specified index.
681 /// <param name="index">Index of item.</param>
682 /// <since_tizen> 8 </since_tizen>
683 public void ScrollToIndex(int index)
685 if (ContentContainer.ChildCount - 1 < index || index < 0)
695 float targetPosition = Math.Min(ScrollingDirection == Direction.Vertical ? Children[index].Position.Y : Children[index].Position.X, maxScrollDistance);
696 AnimateChildTo(ScrollDuration, -targetPosition);
699 private void OnScrollDragStarted()
701 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
702 ScrollDragStarted?.Invoke(this, eventArgs);
705 private void OnScrollDragEnded()
707 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
708 ScrollDragEnded?.Invoke(this, eventArgs);
711 private void OnScrollAnimationStarted()
713 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
714 ScrollAnimationStarted?.Invoke(this, eventArgs);
717 private void OnScrollAnimationEnded()
720 base.Remove(mInterruptTouchingChild);
722 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
723 ScrollAnimationEnded?.Invoke(this, eventArgs);
726 private void OnScroll()
728 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
729 Scrolling?.Invoke(this, eventArgs);
731 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
732 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
733 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
735 scrollBar?.Update(contentLength, Math.Abs(currentPosition));
736 CheckPreReachedTargetPosition();
739 private void CheckPreReachedTargetPosition()
741 // Check whether we reached pre-reached target position
743 ContentContainer.CurrentPosition.Y <= finalTargetPosition + NoticeAnimationEndBeforePosition &&
744 ContentContainer.CurrentPosition.Y >= finalTargetPosition - NoticeAnimationEndBeforePosition)
747 readyToNotice = false;
748 OnPreReachedTargetPosition(finalTargetPosition);
753 /// This helps developer who wants to know before scroll is reaching target position.
755 /// <param name="targetPosition">Index of item.</param>
756 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
757 [EditorBrowsable(EditorBrowsableState.Never)]
758 protected virtual void OnPreReachedTargetPosition(float targetPosition)
763 private void StopScroll()
765 if (scrollAnimation != null)
767 if (scrollAnimation.State == Animation.States.Playing)
769 Debug.WriteLineIf(LayoutDebugScrollableBase, "StopScroll Animation Playing");
770 scrollAnimation.Stop(Animation.EndActions.Cancel);
771 OnScrollAnimationEnded();
773 scrollAnimation.Clear();
777 private void AnimateChildTo(int duration, float axisPosition)
779 Debug.WriteLineIf(LayoutDebugScrollableBase, "AnimationTo Animation Duration:" + duration + " Destination:" + axisPosition);
780 finalTargetPosition = axisPosition;
782 StopScroll(); // Will replace previous animation so will stop existing one.
784 if (scrollAnimation == null)
786 scrollAnimation = new Animation();
787 scrollAnimation.Finished += ScrollAnimationFinished;
790 scrollAnimation.Duration = duration;
791 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSquare);
792 scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", axisPosition, ScrollAlphaFunction);
794 OnScrollAnimationStarted();
795 scrollAnimation.Play();
799 /// Scroll to specific position with or without animation.
801 /// <param name="position">Destination.</param>
802 /// <param name="animate">Scroll with or without animation</param>
803 /// <since_tizen> 8 </since_tizen>
804 public void ScrollTo(float position, bool animate)
806 float currentPositionX = ContentContainer.CurrentPosition.X != 0 ? ContentContainer.CurrentPosition.X : ContentContainer.Position.X;
807 float currentPositionY = ContentContainer.CurrentPosition.Y != 0 ? ContentContainer.CurrentPosition.Y : ContentContainer.Position.Y;
808 float delta = ScrollingDirection == Direction.Horizontal ? currentPositionX : currentPositionY;
809 // The argument position is the new pan position. So the new position of ScrollableBase becomes (-position).
810 // To move ScrollableBase's position to (-position), it moves by (-position - currentPosition).
811 delta = -position - delta;
813 ScrollBy(delta, animate);
816 private float BoundScrollPosition(float targetPosition)
818 if (ScrollAvailableArea != null)
820 float minScrollPosition = ScrollAvailableArea.X;
821 float maxScrollPosition = ScrollAvailableArea.Y;
823 targetPosition = Math.Min(-minScrollPosition, targetPosition);
824 targetPosition = Math.Max(-maxScrollPosition, targetPosition);
828 targetPosition = Math.Min(0, targetPosition);
829 targetPosition = Math.Max(-maxScrollDistance, targetPosition);
832 return targetPosition;
835 private void ScrollBy(float displacement, bool animate)
837 if (GetChildCount() == 0 || maxScrollDistance < 0)
842 float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? ContentContainer.PositionX : ContentContainer.PositionY;
844 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy childCurrentPosition:" + childCurrentPosition +
845 " displacement:" + displacement,
846 " maxScrollDistance:" + maxScrollDistance);
848 childTargetPosition = childCurrentPosition + displacement; // child current position + gesture displacement
851 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition);
855 // Calculate scroll animaton duration
856 float scrollDistance = Math.Abs(displacement);
857 readyToNotice = true;
859 AnimateChildTo(ScrollDuration, BoundScrollPosition(AdjustTargetPositionOfScrollAnimation(BoundScrollPosition(childTargetPosition))));
863 finalTargetPosition = BoundScrollPosition(childTargetPosition);
865 // Set position of scrolling child without an animation
866 if (ScrollingDirection == Direction.Horizontal)
868 ContentContainer.PositionX = finalTargetPosition;
872 ContentContainer.PositionY = finalTargetPosition;
878 /// you can override it to clean-up your own resources.
880 /// <param name="type">DisposeTypes</param>
881 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
882 [EditorBrowsable(EditorBrowsableState.Never)]
883 protected override void Dispose(DisposeTypes type)
890 if (type == DisposeTypes.Explicit)
892 StopVerticalShadowAnimation();
895 if (mPanGestureDetector != null)
897 mPanGestureDetector.Detected -= OnPanGestureDetected;
898 mPanGestureDetector.Dispose();
899 mPanGestureDetector = null;
902 propertyNotification.Dispose();
907 private float CalculateMaximumScrollDistance()
909 float scrollingChildLength = 0;
910 float scrollerLength = 0;
911 if (ScrollingDirection == Direction.Horizontal)
913 Debug.WriteLineIf(LayoutDebugScrollableBase, "Horizontal");
915 scrollingChildLength = ContentContainer.Size.Width;
916 scrollerLength = Size.Width;
920 Debug.WriteLineIf(LayoutDebugScrollableBase, "Vertical");
921 scrollingChildLength = ContentContainer.Size.Height;
922 scrollerLength = Size.Height;
925 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy maxScrollDistance:" + (scrollingChildLength - scrollerLength) +
926 " parent length:" + scrollerLength +
927 " scrolling child length:" + scrollingChildLength);
929 return Math.Max(scrollingChildLength - scrollerLength, 0);
932 private void PageSnap(float velocity)
934 Debug.WriteLineIf(LayoutDebugScrollableBase, "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan +
935 " currentPage[" + CurrentPage + "]");
937 //Increment current page if total displacement enough to warrant a page change.
938 if (Math.Abs(totalDisplacementForPan) > (mPageWidth * ratioOfScreenWidthToCompleteScroll))
940 if (totalDisplacementForPan < 0)
942 CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
946 CurrentPage = Math.Max(0, --CurrentPage);
949 else if (Math.Abs(velocity) > PageFlickThreshold)
953 CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
957 CurrentPage = Math.Max(0, --CurrentPage);
961 // Animate to new page or reposition to current page
962 float destinationX = -(Children[CurrentPage].Position.X + Children[CurrentPage].CurrentSize.Width / 2 - CurrentSize.Width / 2); // set to middle of current page
963 Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to page[" + CurrentPage + "] to:" + destinationX + " from:" + ContentContainer.PositionX);
964 AnimateChildTo(ScrollDuration, destinationX);
967 private void AttachShadowView()
969 // stop animation if necessary.
970 StopVerticalShadowAnimation();
972 base.Add(mVerticalTopShadowView);
973 base.Add(mVerticalBottomShadowView);
975 mVerticalTopShadowView.Size = new Size(SizeWidth, 0.0f);
976 mVerticalTopShadowView.Opacity = 1.0f;
978 mVerticalBottomShadowView.Size = new Size(SizeWidth, 0.0f);
979 mVerticalBottomShadowView.Opacity = 1.0f;
981 // at the beginning, height of vertical shadow is 0, so it is invisible.
982 isVerticalShadowShown = false;
985 private void DragVerticalShadow(float displacement)
987 if ((int)displacement > 0) // downwards
989 // check if reaching at the top.
990 if ((int)finalTargetPosition != 0)
993 // save start displacement, and re-calculate displacement.
994 if (!isVerticalShadowShown)
996 mStartShowShadowDisplacement = displacement;
998 isVerticalShadowShown = true;
1000 float newDisplacement = displacement < mStartShowShadowDisplacement ? 0 : displacement - mStartShowShadowDisplacement;
1002 // scale limit of width is 60%.
1003 float widthScale = newDisplacement / mVerticalShadowScaleHeightLimit;
1004 mVerticalTopShadowView.SizeWidth = widthScale > 0.6f ? SizeWidth * 0.4f : SizeWidth * (1.0f - widthScale);
1006 // scale limit of height is 300%.
1007 mVerticalTopShadowView.SizeHeight = newDisplacement > mVerticalShadowScaleHeightLimit ? mVerticalShadowScaleHeightLimit : newDisplacement;
1009 else if ((int)displacement < 0) // upwards
1011 // check if reaching at the bottom.
1012 if (-(int)finalTargetPosition != (int)maxScrollDistance)
1015 // save start displacement, and re-calculate displacement.
1016 if (!isVerticalShadowShown)
1018 mStartShowShadowDisplacement = displacement;
1020 isVerticalShadowShown = true;
1022 float newDisplacement = mStartShowShadowDisplacement < displacement ? 0 : mStartShowShadowDisplacement - displacement;
1024 // scale limit of width is 60%.
1025 float widthScale = newDisplacement / mVerticalShadowScaleHeightLimit;
1026 mVerticalBottomShadowView.SizeWidth = widthScale > 0.6f ? SizeWidth * 0.4f : SizeWidth * (1.0f - widthScale);
1028 // scale limit of height is 300%.
1029 mVerticalBottomShadowView.SizeHeight = newDisplacement > mVerticalShadowScaleHeightLimit ? mVerticalShadowScaleHeightLimit : newDisplacement;
1033 // if total displacement is 0, shadow would become invisible.
1034 isVerticalShadowShown = false;
1038 private void PlayVerticalShadowAnimation()
1040 // stop animation if necessary.
1041 StopVerticalShadowAnimation();
1043 if (mVerticalShadowAnimation == null)
1045 mVerticalShadowAnimation = new Animation(mVerticalShadowAnimationDuration);
1046 mVerticalShadowAnimation.Finished += OnVerticalShadowAnimationFinished;
1049 View targetView = totalDisplacementForPan < 0 ? mVerticalBottomShadowView : mVerticalTopShadowView;
1050 mVerticalShadowAnimation.AnimateTo(targetView, "SizeWidth", SizeWidth);
1051 mVerticalShadowAnimation.AnimateTo(targetView, "SizeHeight", 0.0f);
1052 mVerticalShadowAnimation.AnimateTo(targetView, "Opacity", 0.0f);
1053 mVerticalShadowAnimation.Play();
1056 private void StopVerticalShadowAnimation()
1058 if (mVerticalShadowAnimation == null || mVerticalShadowAnimation.State != Animation.States.Playing)
1061 Debug.WriteLineIf(LayoutDebugScrollableBase, "gesture finished. Stop Vertical Shadow Animation Playing.");
1062 mVerticalShadowAnimation.Stop(Animation.EndActions.Cancel);
1063 OnVerticalShadowAnimationFinished(null, null);
1064 mVerticalShadowAnimation.Clear();
1067 private void OnVerticalShadowAnimationFinished(object sender, EventArgs e)
1069 base.Remove(mVerticalTopShadowView);
1070 base.Remove(mVerticalBottomShadowView);
1072 mVerticalTopShadowView.Size = new Size(SizeWidth, 0.0f);
1073 mVerticalBottomShadowView.Size = new Size(SizeWidth, 0.0f);
1075 // after animation finished, height & opacity of vertical shadow both are 0, so it is invisible.
1076 isVerticalShadowShown = false;
1079 private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
1081 OnPanGesture(e.PanGesture);
1084 private void OnPanGesture(PanGesture panGesture)
1086 if (SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
1091 if (panGesture.State == Gesture.StateType.Started)
1093 readyToNotice = false;
1094 base.Add(mInterruptTouchingChild);
1096 Debug.WriteLineIf(LayoutDebugScrollableBase, "Gesture Start");
1097 if (scrolling && !SnapToPage)
1101 totalDisplacementForPan = 0.0f;
1102 OnScrollDragStarted();
1104 else if (panGesture.State == Gesture.StateType.Continuing)
1106 if (ScrollingDirection == Direction.Horizontal)
1108 ScrollBy(panGesture.Displacement.X, false);
1109 totalDisplacementForPan += panGesture.Displacement.X;
1113 // if vertical shadow is shown, does not scroll.
1114 if (!isVerticalShadowShown)
1116 ScrollBy(panGesture.Displacement.Y, false);
1118 totalDisplacementForPan += panGesture.Displacement.Y;
1119 DragVerticalShadow(totalDisplacementForPan);
1121 Debug.WriteLineIf(LayoutDebugScrollableBase, "OnPanGestureDetected Continue totalDisplacementForPan:" + totalDisplacementForPan);
1123 else if (panGesture.State == Gesture.StateType.Finished || panGesture.State == Gesture.StateType.Cancelled)
1125 PlayVerticalShadowAnimation();
1126 OnScrollDragEnded();
1127 StopScroll(); // Will replace previous animation so will stop existing one.
1129 if (scrollAnimation == null)
1131 scrollAnimation = new Animation();
1132 scrollAnimation.Finished += ScrollAnimationFinished;
1135 float panVelocity = (ScrollingDirection == Direction.Horizontal) ? panGesture.Velocity.X : panGesture.Velocity.Y;
1139 PageSnap(panVelocity);
1143 if (panVelocity == 0)
1145 float currentScrollPosition = (ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
1146 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
1147 scrollAnimation.Duration = 0;
1148 scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", currentScrollPosition);
1149 scrollAnimation.Play();
1153 Decelerating(panVelocity, scrollAnimation);
1157 totalDisplacementForPan = 0;
1159 readyToNotice = true;
1160 OnScrollAnimationStarted();
1164 internal override bool OnAccessibilityPan(PanGesture gestures)
1166 if (SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
1171 OnPanGesture(gestures);
1175 private float CustomScrollAlphaFunction(float progress)
1177 if (panAnimationDelta == 0)
1183 // Parameter "progress" is normalized value. We need to multiply target duration to calculate distance.
1184 // Can get real distance using equation of deceleration (check Decelerating function)
1185 // After get real distance, normalize it
1186 float realDuration = progress * panAnimationDuration;
1187 float realDistance = velocityOfLastPan * ((float)Math.Pow(decelerationRate, realDuration) - 1) / logValueOfDeceleration;
1188 float result = Math.Min(realDistance / Math.Abs(panAnimationDelta), 1.0f);
1194 /// you can override it to custom your decelerating
1196 /// <param name="velocity">Velocity of current pan.</param>
1197 /// <param name="animation">Scroll animation.</param>
1198 [EditorBrowsable(EditorBrowsableState.Never)]
1199 protected virtual void Decelerating(float velocity, Animation animation)
1201 // Decelerating using deceleration equation ===========
1203 // V : velocity (pixel per milisecond)
1204 // V0 : initial velocity
1205 // d : deceleration rate,
1207 // X : final position after decelerating
1208 // log : natural logarithm
1210 // V(t) = V0 * d pow t;
1211 // X(t) = V0 * (d pow t - 1) / log d; <-- Integrate the velocity function
1212 // X(∞) = V0 * d / (1 - d); <-- Result using inifit T can be final position because T is tending to infinity.
1214 // Because of final T is tending to inifity, we should use threshold value to finish.
1215 // Final T = log(-threshold * log d / |V0| ) / log d;
1217 velocityOfLastPan = Math.Abs(velocity);
1219 float currentScrollPosition = -(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
1220 panAnimationDelta = (velocityOfLastPan * decelerationRate) / (1 - decelerationRate);
1221 panAnimationDelta = velocity > 0 ? -panAnimationDelta : panAnimationDelta;
1223 float destination = -(panAnimationDelta + currentScrollPosition);
1224 float adjustDestination = AdjustTargetPositionOfScrollAnimation(destination);
1225 float maxPosition = ScrollAvailableArea != null ? ScrollAvailableArea.Y : maxScrollDistance;
1226 float minPosition = ScrollAvailableArea != null ? ScrollAvailableArea.X : 0;
1228 if (destination < -maxPosition || destination > minPosition)
1230 panAnimationDelta = velocity > 0 ? (currentScrollPosition - minPosition) : (maxPosition - currentScrollPosition);
1231 destination = velocity > 0 ? minPosition : -maxPosition;
1233 if (panAnimationDelta == 0)
1235 panAnimationDuration = 0.0f;
1239 panAnimationDuration = (float)Math.Log((panAnimationDelta * logValueOfDeceleration / velocityOfLastPan + 1), decelerationRate);
1242 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1243 "OverRange======================= \n" +
1244 "[decelerationRate] " + decelerationRate + "\n" +
1245 "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1246 "[Velocity] " + velocityOfLastPan + "\n" +
1247 "[CurrentPosition] " + currentScrollPosition + "\n" +
1248 "[CandidateDelta] " + panAnimationDelta + "\n" +
1249 "[Destination] " + destination + "\n" +
1250 "[Duration] " + panAnimationDuration + "\n" +
1251 "================================ \n"
1256 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1258 if (adjustDestination != destination)
1260 destination = adjustDestination;
1261 panAnimationDelta = destination + currentScrollPosition;
1262 velocityOfLastPan = Math.Abs(panAnimationDelta * logValueOfDeceleration / ((float)Math.Pow(decelerationRate, panAnimationDuration) - 1));
1263 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1266 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1267 "================================ \n" +
1268 "[decelerationRate] " + decelerationRate + "\n" +
1269 "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1270 "[Velocity] " + velocityOfLastPan + "\n" +
1271 "[CurrentPosition] " + currentScrollPosition + "\n" +
1272 "[CandidateDelta] " + panAnimationDelta + "\n" +
1273 "[Destination] " + destination + "\n" +
1274 "[Duration] " + panAnimationDuration + "\n" +
1275 "================================ \n"
1279 finalTargetPosition = destination;
1281 customScrollAlphaFunction = new UserAlphaFunctionDelegate(CustomScrollAlphaFunction);
1282 animation.DefaultAlphaFunction = new AlphaFunction(customScrollAlphaFunction);
1283 GC.KeepAlive(customScrollAlphaFunction);
1284 animation.Duration = (int)panAnimationDuration;
1285 animation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", destination);
1289 private void ScrollAnimationFinished(object sender, EventArgs e)
1291 OnScrollAnimationEnded();
1295 /// Adjust scrolling position by own scrolling rules.
1296 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1298 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
1299 [EditorBrowsable(EditorBrowsableState.Never)]
1300 protected virtual float AdjustTargetPositionOfScrollAnimation(float position)
1306 /// Scroll position given to ScrollTo.
1307 /// This is the position in the opposite direction to the position of ContentContainer.
1309 /// <since_tizen> 8 </since_tizen>
1310 public Position ScrollPosition
1314 return new Position(-ContentContainer.Position);
1319 /// Current scroll position in the middle of ScrollTo animation.
1320 /// This is the position in the opposite direction to the current position of ContentContainer.
1322 /// <since_tizen> 8 </since_tizen>
1323 public Position ScrollCurrentPosition
1327 return new Position(-ContentContainer.CurrentPosition);
1332 /// Remove all children in ContentContainer.
1334 /// <param name="dispose">If true, removed child is disposed.</param>
1335 [EditorBrowsable(EditorBrowsableState.Never)]
1336 public void RemoveAllChildren(bool dispose = false)
1338 RecursiveRemoveChildren(ContentContainer, dispose);
1341 private void RecursiveRemoveChildren(View parent, bool dispose)
1347 int maxChild = (int)parent.GetChildCount();
1348 for (int i = maxChild - 1; i >= 0; --i)
1350 View child = parent.GetChildAt((uint)i);
1355 RecursiveRemoveChildren(child, dispose);
1356 parent.Remove(child);