1 /* Copyright (c) 2021 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;
33 private Position scrollPosition;
36 /// Default constructor.
38 /// <param name="position">Current container position</param>
39 /// <since_tizen> 8 </since_tizen>
40 public ScrollEventArgs(Position position)
42 this.position = position;
43 this.scrollPosition = new Position(-position);
47 /// Current position of ContentContainer.
49 /// <since_tizen> 8 </since_tizen>
50 public Position Position
58 /// Current scroll position of scrollableBase pan.
59 /// This is the position in the opposite direction to the current position of ContentContainer.
61 [EditorBrowsable(EditorBrowsableState.Never)]
62 public Position ScrollPosition
66 return scrollPosition;
72 /// ScrollOutofBoundEventArgs is to record scroll out-of-bound event arguments which will be sent to user.
74 [EditorBrowsable(EditorBrowsableState.Never)]
75 public class ScrollOutOfBoundEventArgs : EventArgs
78 /// The direction to be touched.
80 [EditorBrowsable(EditorBrowsableState.Never)]
86 [EditorBrowsable(EditorBrowsableState.Never)]
92 [EditorBrowsable(EditorBrowsableState.Never)]
98 [EditorBrowsable(EditorBrowsableState.Never)]
104 [EditorBrowsable(EditorBrowsableState.Never)]
111 /// <param name="direction">Current pan direction</param>
112 /// <param name="displacement">Current total displacement</param>
113 [EditorBrowsable(EditorBrowsableState.Never)]
114 public ScrollOutOfBoundEventArgs(Direction direction, float displacement)
116 PanDirection = direction;
117 Displacement = displacement;
121 /// Current pan direction of ContentContainer.
123 [EditorBrowsable(EditorBrowsableState.Never)]
124 public Direction PanDirection
130 /// Current total displacement of ContentContainer.
131 /// if its value is greater than 0, it is at the top/left;
132 /// if less than 0, it is at the bottom/right.
134 [EditorBrowsable(EditorBrowsableState.Never)]
135 public float Displacement
142 /// This class provides a View that can scroll a single View with a layout. This View can be a nest of Views.
144 /// <since_tizen> 8 </since_tizen>
145 public partial class ScrollableBase : Control
147 static bool LayoutDebugScrollableBase = false; // Debug flag
148 private Direction mScrollingDirection = Direction.Vertical;
149 private bool mScrollEnabled = true;
150 private int mScrollDuration = 125;
151 private int mPageWidth = 0;
152 private float mPageFlickThreshold = 0.4f;
153 private float mScrollingEventThreshold = 0.001f;
155 private class ScrollableBaseCustomLayout : AbsoluteLayout
157 protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
159 MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK;
160 MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK;
162 Direction scrollingDirection = Direction.Vertical;
163 ScrollableBase scrollableBase = this.Owner as ScrollableBase;
164 if (scrollableBase != null)
166 scrollingDirection = scrollableBase.ScrollingDirection;
169 float totalWidth = 0.0f;
170 float totalHeight = 0.0f;
172 // measure child, should be a single scrolling child
173 foreach (LayoutItem childLayout in LayoutChildren)
175 if (childLayout != null && childLayout.Owner.Name == "ContentContainer")
178 // Use an Unspecified MeasureSpecification mode so scrolling child is not restricted to it's parents size in Height (for vertical scrolling)
179 // or Width for horizontal scrolling
180 if (scrollingDirection == Direction.Vertical)
182 MeasureSpecification unrestrictedMeasureSpec = new MeasureSpecification(heightMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
183 MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), unrestrictedMeasureSpec, new LayoutLength(0)); // Height unrestricted by parent
187 MeasureSpecification unrestrictedMeasureSpec = new MeasureSpecification(widthMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
188 MeasureChildWithMargins(childLayout, unrestrictedMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0)); // Width unrestricted by parent
191 totalWidth = (childLayout.MeasuredWidth.Size + (childLayout.Padding.Start + childLayout.Padding.End)).AsDecimal();
192 totalHeight = (childLayout.MeasuredHeight.Size + (childLayout.Padding.Top + childLayout.Padding.Bottom)).AsDecimal();
194 if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
196 childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
198 if (childLayout.MeasuredHeight.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
200 childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
205 MeasuredSize widthSizeAndState = ResolveSizeAndState(new LayoutLength(totalWidth), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
206 MeasuredSize heightSizeAndState = ResolveSizeAndState(new LayoutLength(totalHeight), heightMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
207 totalWidth = widthSizeAndState.Size.AsDecimal();
208 totalHeight = heightSizeAndState.Size.AsDecimal();
210 // Ensure layout respects it's given minimum size
211 totalWidth = Math.Max(totalWidth, SuggestedMinimumWidth.AsDecimal());
212 totalHeight = Math.Max(totalHeight, SuggestedMinimumHeight.AsDecimal());
214 widthSizeAndState.State = childWidthState;
215 heightSizeAndState.State = childHeightState;
217 SetMeasuredDimensions(ResolveSizeAndState(new LayoutLength(totalWidth), widthMeasureSpec, childWidthState),
218 ResolveSizeAndState(new LayoutLength(totalHeight), heightMeasureSpec, childHeightState));
220 // Size of ScrollableBase is changed. Change Page width too.
221 if (scrollableBase != null)
223 scrollableBase.mPageWidth = (int)MeasuredWidth.Size.AsRoundedValue();
224 scrollableBase.OnScrollingChildRelayout(null, null);
227 } // ScrollableBaseCustomLayout
230 /// The direction axis to scroll.
232 /// <since_tizen> 8 </since_tizen>
233 public enum Direction
238 /// <since_tizen> 8 </since_tizen>
244 /// <since_tizen> 8 </since_tizen>
249 /// Scrolling direction mode.
250 /// Default is Vertical scrolling.
252 /// <since_tizen> 8 </since_tizen>
253 public Direction ScrollingDirection
257 return (Direction)GetValue(ScrollingDirectionProperty);
261 SetValue(ScrollingDirectionProperty, value);
262 NotifyPropertyChanged();
265 private Direction InternalScrollingDirection
269 return mScrollingDirection;
273 if (value != mScrollingDirection)
275 //Reset scroll position and stop scroll animation
278 mScrollingDirection = value;
279 mPanGestureDetector.ClearAngles();
280 mPanGestureDetector.AddDirection(value == Direction.Horizontal ?
281 PanGestureDetector.DirectionHorizontal : PanGestureDetector.DirectionVertical);
283 ContentContainer.WidthSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent;
284 ContentContainer.HeightSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent;
291 /// Enable or disable scrolling.
293 /// <since_tizen> 8 </since_tizen>
294 public bool ScrollEnabled
298 return (bool)GetValue(ScrollEnabledProperty);
302 SetValue(ScrollEnabledProperty, value);
303 NotifyPropertyChanged();
306 private bool InternalScrollEnabled
310 return mScrollEnabled;
314 if (value != mScrollEnabled)
316 mScrollEnabled = value;
319 mPanGestureDetector.Detected += OnPanGestureDetected;
323 mPanGestureDetector.Detected -= OnPanGestureDetected;
330 /// Gets scrollable status.
332 [EditorBrowsable(EditorBrowsableState.Never)]
333 protected override bool AccessibilityIsScrollable()
339 /// Pages mode, enables moving to the next or return to current page depending on pan displacement.
340 /// Default is false.
342 /// <since_tizen> 8 </since_tizen>
343 public bool SnapToPage
347 return (bool)GetValue(SnapToPageProperty);
351 SetValue(SnapToPageProperty, value);
352 NotifyPropertyChanged();
355 private bool InternalSnapToPage { set; get; } = false;
358 /// Get current page.
359 /// Working property with SnapToPage property.
361 /// <since_tizen> 8 </since_tizen>
362 public int CurrentPage { get; private set; } = 0;
365 /// Duration of scroll animation.
366 /// Default value is 125ms.
368 /// <since_tizen> 8 </since_tizen>
369 public int ScrollDuration
373 return (int)GetValue(ScrollDurationProperty);
377 SetValue(ScrollDurationProperty, value);
378 NotifyPropertyChanged();
381 private int InternalScrollDuration
385 mScrollDuration = value >= 0 ? value : mScrollDuration;
389 return mScrollDuration;
394 /// Scroll Available area.
396 /// <since_tizen> 8 </since_tizen>
397 public Vector2 ScrollAvailableArea
401 return GetValue(ScrollAvailableAreaProperty) as Vector2;
405 SetValue(ScrollAvailableAreaProperty, value);
406 NotifyPropertyChanged();
409 private Vector2 InternalScrollAvailableArea { set; get; }
412 /// An event emitted when user starts dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
414 /// <since_tizen> 8 </since_tizen>
415 public event EventHandler<ScrollEventArgs> ScrollDragStarted;
418 /// An event emitted when user stops dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
420 /// <since_tizen> 8 </since_tizen>
421 public event EventHandler<ScrollEventArgs> ScrollDragEnded;
424 /// An event emitted when the scrolling slide animation starts, user can subscribe or unsubscribe to this event handler.<br />
426 /// <since_tizen> 8 </since_tizen>
427 public event EventHandler<ScrollEventArgs> ScrollAnimationStarted;
430 /// An event emitted when the scrolling slide animation ends, user can subscribe or unsubscribe to this event handler.<br />
432 /// <since_tizen> 8 </since_tizen>
433 public event EventHandler<ScrollEventArgs> ScrollAnimationEnded;
436 /// An event emitted when scrolling, user can subscribe or unsubscribe to this event handler.<br />
438 /// <since_tizen> 8 </since_tizen>
439 public event EventHandler<ScrollEventArgs> Scrolling;
442 /// An event emitted when scrolling out of bound, user can subscribe or unsubscribe to this event handler.<br />
444 [EditorBrowsable(EditorBrowsableState.Never)]
445 public event EventHandler<ScrollOutOfBoundEventArgs> ScrollOutOfBound;
448 /// Scrollbar for ScrollableBase.
450 /// <since_tizen> 8 </since_tizen>
451 public ScrollbarBase Scrollbar
455 return GetValue(ScrollbarProperty) as ScrollbarBase;
459 SetValue(ScrollbarProperty, value);
460 NotifyPropertyChanged();
463 private ScrollbarBase InternalScrollbar
473 base.Remove(scrollBar);
477 if (scrollBar != null)
479 scrollBar.Name = "ScrollBar";
497 /// Always hide Scrollbar.
499 /// <since_tizen> 8 </since_tizen>
500 public bool HideScrollbar
504 return (bool)GetValue(HideScrollbarProperty);
508 SetValue(HideScrollbarProperty, value);
509 NotifyPropertyChanged();
512 private bool InternalHideScrollbar
516 return hideScrollbar;
520 hideScrollbar = value;
537 /// Container which has content of ScrollableBase.
539 /// <since_tizen> 8 </since_tizen>
540 public View ContentContainer { get; private set; }
543 /// Set the layout on this View. Replaces any existing Layout.
545 /// <since_tizen> 8 </since_tizen>
546 public new LayoutItem Layout
550 return GetValue(LayoutProperty) as LayoutItem;
554 SetValue(LayoutProperty, value);
555 NotifyPropertyChanged();
558 private LayoutItem InternalLayout
562 return ContentContainer.Layout;
566 ContentContainer.Layout = value;
571 /// List of children of Container.
573 /// <since_tizen> 8 </since_tizen>
574 public new List<View> Children
578 return ContentContainer.Children;
583 /// Deceleration rate of scrolling by finger.
584 /// Rate should be bigger than 0 and smaller than 1.
585 /// Default value is 0.998f;
587 /// <since_tizen> 8 </since_tizen>
588 public float DecelerationRate
592 return (float)GetValue(DecelerationRateProperty);
596 SetValue(DecelerationRateProperty, value);
597 NotifyPropertyChanged();
600 private float InternalDecelerationRate
604 return decelerationRate;
608 decelerationRate = (value < 1 && value > 0) ? value : decelerationRate;
609 logValueOfDeceleration = (float)Math.Log(value);
614 /// Threshold not to go infinite at the end of scrolling animation.
616 [EditorBrowsable(EditorBrowsableState.Never)]
617 public float DecelerationThreshold
621 return (float)GetValue(DecelerationThresholdProperty);
625 SetValue(DecelerationThresholdProperty, value);
626 NotifyPropertyChanged();
629 private float InternalDecelerationThreshold { get; set; } = 0.1f;
632 /// Scrolling event will be thrown when this amount of scroll position is changed.
633 /// If this threshold becomes smaller, the tracking detail increases but the scrolling range that can be tracked becomes smaller.
634 /// If large sized ContentContainer is required, please use larger threshold value.
635 /// Default ScrollingEventThreshold value is 0.001f.
637 [EditorBrowsable(EditorBrowsableState.Never)]
638 public float ScrollingEventThreshold
642 return (float)GetValue(ScrollingEventThresholdProperty);
646 SetValue(ScrollingEventThresholdProperty, value);
647 NotifyPropertyChanged();
650 private float InternalScrollingEventThreshold
654 return mScrollingEventThreshold;
658 if (mScrollingEventThreshold != value && value > 0)
660 ContentContainer.RemovePropertyNotification(propertyNotification);
661 propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(value));
662 propertyNotification.Notified += OnPropertyChanged;
663 mScrollingEventThreshold = value;
669 /// Page will be changed when velocity of panning is over threshold.
670 /// The unit of threshold is pixel per millisecond.
672 /// <since_tizen> 8 </since_tizen>
673 public float PageFlickThreshold
677 return (float)GetValue(PageFlickThresholdProperty);
681 SetValue(PageFlickThresholdProperty, value);
682 NotifyPropertyChanged();
685 private float InternalPageFlickThreshold
689 return mPageFlickThreshold;
693 mPageFlickThreshold = value >= 0f ? value : mPageFlickThreshold;
698 /// Padding for the ScrollableBase
700 [EditorBrowsable(EditorBrowsableState.Never)]
701 public new Extents Padding
705 return GetValue(PaddingProperty) as Extents;
709 SetValue(PaddingProperty, value);
710 NotifyPropertyChanged();
713 private Extents InternalPadding
717 return ContentContainer.Padding;
721 ContentContainer.Padding = value;
726 /// Alphafunction for scroll animation.
728 [EditorBrowsable(EditorBrowsableState.Never)]
729 public AlphaFunction ScrollAlphaFunction
733 return GetValue(ScrollAlphaFunctionProperty) as AlphaFunction;
737 SetValue(ScrollAlphaFunctionProperty, value);
738 NotifyPropertyChanged();
741 private AlphaFunction InternalScrollAlphaFunction { get; set; } = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
743 private bool hideScrollbar = true;
744 private float maxScrollDistance;
745 private float childTargetPosition = 0.0f;
746 private PanGestureDetector mPanGestureDetector;
747 private ScrollbarBase scrollBar;
748 private bool scrolling = false;
749 private float ratioOfScreenWidthToCompleteScroll = 0.5f;
750 private float totalDisplacementForPan = 0.0f;
751 private Size previousContainerSize = new Size();
752 private Size previousSize = new Size();
753 private PropertyNotification propertyNotification;
754 private float noticeAnimationEndBeforePosition = 0.0f;
755 private bool readyToNotice = false;
758 /// Notice before animation is finished.
760 [EditorBrowsable(EditorBrowsableState.Never)]
761 // Let's consider more whether this needs to be set as protected.
762 public float NoticeAnimationEndBeforePosition
766 return (float)GetValue(NoticeAnimationEndBeforePositionProperty);
770 SetValue(NoticeAnimationEndBeforePositionProperty, value);
771 NotifyPropertyChanged();
774 private float InternalNoticeAnimationEndBeforePosition
776 get => noticeAnimationEndBeforePosition;
777 set => noticeAnimationEndBeforePosition = value;
781 /// Step scroll move distance.
782 /// Key focus originally moves focusable objects, but in ScrollableBase,
783 /// if focusable object is too far or un-exist and ScrollableBase is focusable,
784 /// it can scroll move itself by key input.
785 /// this value decide how long distance will it moves in one step.
786 /// if any value is not set, step will be moved quater size of ScrollableBase length.
788 [EditorBrowsable(EditorBrowsableState.Never)]
789 public float StepScrollDistance
793 return (float)GetValue(StepScrollDistanceProperty);
797 SetValue(StepScrollDistanceProperty, value);
798 NotifyPropertyChanged();
801 private float stepScrollDistance = 0f;
804 // Let's consider more whether this needs to be set as protected.
805 private float finalTargetPosition;
807 private Animation scrollAnimation;
808 // Declare user alpha function delegate
809 [UnmanagedFunctionPointer(CallingConvention.StdCall)]
810 private delegate float UserAlphaFunctionDelegate(float progress);
811 private UserAlphaFunctionDelegate customScrollAlphaFunction;
812 private float velocityOfLastPan = 0.0f;
813 private float panAnimationDuration = 0.0f;
814 private float panAnimationDelta = 0.0f;
815 private float logValueOfDeceleration = 0.0f;
816 private float decelerationRate = 0.0f;
818 private View topOverShootingShadowView;
819 private View bottomOverShootingShadowView;
820 private View leftOverShootingShadowView;
821 private View rightOverShootingShadowView;
822 private const int overShootingShadowScaleHeightLimit = 64 * 3;
823 private const int overShootingShadowAnimationDuration = 300;
824 private Animation overShootingShadowAnimation;
825 private bool isOverShootingShadowShown = false;
826 private float startShowShadowDisplacement;
829 /// Default Constructor
831 /// <since_tizen> 8 </since_tizen>
832 public ScrollableBase() : base()
834 DecelerationRate = 0.998f;
836 base.Layout = new ScrollableBaseCustomLayout();
837 mPanGestureDetector = new PanGestureDetector();
838 mPanGestureDetector.Attach(this);
839 mPanGestureDetector.AddDirection(PanGestureDetector.DirectionVertical);
840 if (mPanGestureDetector.GetMaximumTouchesRequired() < 2) mPanGestureDetector.SetMaximumTouchesRequired(2);
841 mPanGestureDetector.Detected += OnPanGestureDetected;
843 ClippingMode = ClippingModeType.ClipToBoundingBox;
845 //Default Scrolling child
846 ContentContainer = new View()
848 Name = "ContentContainer",
849 WidthSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent,
850 HeightSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent,
852 // Check if children's sizes change to update Scrollbar
853 ContentContainer.Relayout += OnScrollingChildRelayout;
854 propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(mScrollingEventThreshold));
855 propertyNotification.Notified += OnPropertyChanged;
856 base.Add(ContentContainer);
857 // Check if ScrollableBase's size changes to update Scrollbar
858 base.Relayout += OnScrollingChildRelayout;
860 Scrollbar = new Scrollbar();
862 //Show vertical shadow on the top (or bottom) of the scrollable when panning down (or up).
863 topOverShootingShadowView = new View
865 BackgroundImage = FrameworkInformation.ResourcePath + "nui_component_default_scroll_over_shooting_top.png",
868 PositionUsesPivotPoint = true,
869 ParentOrigin = NUI.ParentOrigin.TopCenter,
870 PivotPoint = NUI.PivotPoint.TopCenter,
872 bottomOverShootingShadowView = new View
874 BackgroundImage = FrameworkInformation.ResourcePath + "nui_component_default_scroll_over_shooting_bottom.png",
877 PositionUsesPivotPoint = true,
878 ParentOrigin = NUI.ParentOrigin.BottomCenter,
879 PivotPoint = NUI.PivotPoint.BottomCenter,
881 //Show horizontal shadow on the left (or right) of the scrollable when panning down (or up).
882 leftOverShootingShadowView = new View
884 BackgroundImage = FrameworkInformation.ResourcePath + "nui_component_default_scroll_over_shooting_left.png",
887 PositionUsesPivotPoint = true,
888 ParentOrigin = NUI.ParentOrigin.CenterLeft,
889 PivotPoint = NUI.PivotPoint.CenterLeft,
891 rightOverShootingShadowView = new View
893 BackgroundImage = FrameworkInformation.ResourcePath + "nui_component_default_scroll_over_shooting_right.png",
896 PositionUsesPivotPoint = true,
897 ParentOrigin = NUI.ParentOrigin.CenterRight,
898 PivotPoint = NUI.PivotPoint.CenterRight,
901 AccessibilityManager.Instance.SetAccessibilityAttribute(this, AccessibilityManager.AccessibilityAttribute.Trait, "ScrollableBase");
903 SetKeyboardNavigationSupport(true);
906 private bool OnInterruptTouchingChildTouched(object source, View.TouchEventArgs args)
908 if (args.Touch.GetState(0) == PointStateType.Down)
910 if (scrolling && !SnapToPage)
918 private void OnPropertyChanged(object source, PropertyNotification.NotifyEventArgs args)
924 /// Called after a child has been added to the owning view.
926 /// <param name="view">The child which has been added.</param>
927 /// <since_tizen> 8 </since_tizen>
928 public override void Add(View view)
930 ContentContainer.Add(view);
934 /// Called after a child has been removed from the owning view.
936 /// <param name="view">The child which has been removed.</param>
937 /// <since_tizen> 8 </since_tizen>
938 public override void Remove(View view)
940 if (SnapToPage && CurrentPage == Children.IndexOf(view) && CurrentPage == Children.Count - 1 && Children.Count > 1)
942 // Target View is current page and also last child.
943 // CurrentPage should be changed to previous page.
944 ScrollToIndex(CurrentPage - 1);
947 ContentContainer.Remove(view);
950 private void OnScrollingChildRelayout(object source, EventArgs args)
952 // Size is changed. Calculate maxScrollDistance.
953 bool isSizeChanged = previousContainerSize.Width != ContentContainer.Size.Width || previousContainerSize.Height != ContentContainer.Size.Height ||
954 previousSize.Width != Size.Width || previousSize.Height != Size.Height;
958 maxScrollDistance = CalculateMaximumScrollDistance();
959 if (!ReviseContainerPositionIfNeed())
965 previousContainerSize = new Size(ContentContainer.Size);
966 previousSize = new Size(Size);
969 private bool ReviseContainerPositionIfNeed()
971 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
972 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
974 if (Math.Abs(currentPosition) > maxScrollDistance)
977 var targetPosition = BoundScrollPosition(-maxScrollDistance);
978 if (isHorizontal) ContentContainer.PositionX = targetPosition;
979 else ContentContainer.PositionY = targetPosition;
987 /// The composition of a Scrollbar can vary depending on how you use ScrollableBase.
988 /// Set the composition that will go into the ScrollableBase according to your ScrollableBase.
990 /// <since_tizen> 8 </since_tizen>
991 [EditorBrowsable(EditorBrowsableState.Never)]
992 protected virtual void SetScrollbar()
996 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
997 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
998 float viewportLength = isHorizontal ? Size.Width : Size.Height;
999 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
1000 Scrollbar.Initialize(contentLength, viewportLength, -currentPosition, isHorizontal);
1004 /// Update scrollbar position and size.
1005 [EditorBrowsable(EditorBrowsableState.Never)]
1006 protected virtual void UpdateScrollbar()
1010 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
1011 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
1012 float viewportLength = isHorizontal ? Size.Width : Size.Height;
1013 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
1014 Scrollbar.Update(contentLength, viewportLength, -currentPosition);
1019 /// Scrolls to the item at the specified index.
1021 /// <param name="index">Index of item.</param>
1022 /// <since_tizen> 8 </since_tizen>
1023 public void ScrollToIndex(int index)
1025 if (ContentContainer.ChildCount - 1 < index || index < 0)
1032 CurrentPage = index;
1035 float targetPosition = Math.Min(ScrollingDirection == Direction.Vertical ? Children[index].Position.Y : Children[index].Position.X, maxScrollDistance);
1036 AnimateChildTo(ScrollDuration, -targetPosition);
1039 internal void ScrollToChild(View child, bool anim = false)
1041 if (null == FindDescendantByID(child.ID)) return;
1043 bool isHorizontal = (ScrollingDirection == Direction.Horizontal);
1045 float viewScreenPosition = (isHorizontal? ScreenPosition.X : ScreenPosition.Y);
1046 float childScreenPosition = (isHorizontal? child.ScreenPosition.X : child.ScreenPosition.Y);
1047 float scrollPosition = (isHorizontal? ScrollPosition.X : ScrollPosition.Y);
1048 float viewSize = (isHorizontal? SizeWidth : SizeHeight);
1049 float childSize = (isHorizontal? child.SizeWidth : child.SizeHeight);
1051 if (viewScreenPosition > childScreenPosition ||
1052 viewScreenPosition + viewSize < childScreenPosition + childSize)
1053 {// if object is outside
1054 float targetPosition;
1055 float dist = viewScreenPosition - childScreenPosition;
1057 {// if object is upper side
1058 targetPosition = scrollPosition - dist;
1061 {// if object is down side
1062 targetPosition = scrollPosition - dist + childSize - viewSize;
1064 ScrollTo(targetPosition, anim);
1068 private void OnScrollDragStarted()
1070 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
1071 ScrollDragStarted?.Invoke(this, eventArgs);
1074 private void OnScrollDragEnded()
1076 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
1077 ScrollDragEnded?.Invoke(this, eventArgs);
1080 private void OnScrollAnimationStarted()
1082 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
1083 ScrollAnimationStarted?.Invoke(this, eventArgs);
1086 private void OnScrollAnimationEnded()
1089 this.InterceptTouchEvent -= OnInterruptTouchingChildTouched;
1091 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
1092 ScrollAnimationEnded?.Invoke(this, eventArgs);
1095 private void OnScroll()
1097 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
1098 Scrolling?.Invoke(this, eventArgs);
1100 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
1101 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
1102 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
1104 scrollBar?.Update(contentLength, Math.Abs(currentPosition));
1105 CheckPreReachedTargetPosition();
1108 private void CheckPreReachedTargetPosition()
1110 // Check whether we reached pre-reached target position
1111 if (readyToNotice &&
1112 ContentContainer.CurrentPosition.Y <= finalTargetPosition + NoticeAnimationEndBeforePosition &&
1113 ContentContainer.CurrentPosition.Y >= finalTargetPosition - NoticeAnimationEndBeforePosition)
1116 readyToNotice = false;
1117 OnPreReachedTargetPosition(finalTargetPosition);
1122 /// This helps developer who wants to know before scroll is reaching target position.
1124 /// <param name="targetPosition">Index of item.</param>
1125 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
1126 [EditorBrowsable(EditorBrowsableState.Never)]
1127 protected virtual void OnPreReachedTargetPosition(float targetPosition)
1132 private void StopScroll()
1134 if (scrollAnimation != null)
1136 if (scrollAnimation.State == Animation.States.Playing)
1138 Debug.WriteLineIf(LayoutDebugScrollableBase, "StopScroll Animation Playing");
1139 scrollAnimation.Stop(Animation.EndActions.Cancel);
1140 OnScrollAnimationEnded();
1142 scrollAnimation.Clear();
1146 private void AnimateChildTo(int duration, float axisPosition)
1148 Debug.WriteLineIf(LayoutDebugScrollableBase, "AnimationTo Animation Duration:" + duration + " Destination:" + axisPosition);
1149 finalTargetPosition = axisPosition;
1151 StopScroll(); // Will replace previous animation so will stop existing one.
1153 if (scrollAnimation == null)
1155 scrollAnimation = new Animation();
1156 scrollAnimation.Finished += ScrollAnimationFinished;
1159 scrollAnimation.Duration = duration;
1160 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSquare);
1161 scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", axisPosition, ScrollAlphaFunction);
1163 OnScrollAnimationStarted();
1164 scrollAnimation.Play();
1168 /// Scroll to specific position with or without animation.
1170 /// <param name="position">Destination.</param>
1171 /// <param name="animate">Scroll with or without animation</param>
1172 /// <since_tizen> 8 </since_tizen>
1173 public void ScrollTo(float position, bool animate)
1176 float currentPositionX = ContentContainer.CurrentPosition.X != 0 ? ContentContainer.CurrentPosition.X : ContentContainer.Position.X;
1177 float currentPositionY = ContentContainer.CurrentPosition.Y != 0 ? ContentContainer.CurrentPosition.Y : ContentContainer.Position.Y;
1178 float delta = ScrollingDirection == Direction.Horizontal ? currentPositionX : currentPositionY;
1179 // The argument position is the new pan position. So the new position of ScrollableBase becomes (-position).
1180 // To move ScrollableBase's position to (-position), it moves by (-position - currentPosition).
1181 delta = -position - delta;
1183 ScrollBy(delta, animate);
1186 private float BoundScrollPosition(float targetPosition)
1188 if (ScrollAvailableArea != null)
1190 float minScrollPosition = ScrollAvailableArea.X;
1191 float maxScrollPosition = ScrollAvailableArea.Y;
1193 targetPosition = Math.Min(-minScrollPosition, targetPosition);
1194 targetPosition = Math.Max(-maxScrollPosition, targetPosition);
1198 targetPosition = Math.Min(0, targetPosition);
1199 targetPosition = Math.Max(-maxScrollDistance, targetPosition);
1202 return targetPosition;
1205 private void ScrollBy(float displacement, bool animate)
1207 if (GetChildCount() == 0 || maxScrollDistance < 0)
1212 float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? ContentContainer.PositionX : ContentContainer.PositionY;
1214 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy childCurrentPosition:" + childCurrentPosition +
1215 " displacement:" + displacement,
1216 " maxScrollDistance:" + maxScrollDistance);
1218 childTargetPosition = childCurrentPosition + displacement; // child current position + gesture displacement
1220 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition);
1224 // Calculate scroll animation duration
1225 float scrollDistance = Math.Abs(displacement);
1226 readyToNotice = true;
1228 AnimateChildTo(ScrollDuration, BoundScrollPosition(AdjustTargetPositionOfScrollAnimation(BoundScrollPosition(childTargetPosition))));
1233 finalTargetPosition = BoundScrollPosition(childTargetPosition);
1235 // Set position of scrolling child without an animation
1236 if (ScrollingDirection == Direction.Horizontal)
1238 ContentContainer.PositionX = finalTargetPosition;
1242 ContentContainer.PositionY = finalTargetPosition;
1248 /// you can override it to clean-up your own resources.
1250 /// <param name="type">DisposeTypes</param>
1251 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
1252 [EditorBrowsable(EditorBrowsableState.Never)]
1253 protected override void Dispose(DisposeTypes type)
1260 if (type == DisposeTypes.Explicit)
1262 StopOverShootingShadowAnimation();
1265 if (mPanGestureDetector != null)
1267 mPanGestureDetector.Detected -= OnPanGestureDetected;
1268 mPanGestureDetector.Dispose();
1269 mPanGestureDetector = null;
1272 propertyNotification.Dispose();
1277 private float CalculateMaximumScrollDistance()
1279 float scrollingChildLength = 0;
1280 float scrollerLength = 0;
1281 if (ScrollingDirection == Direction.Horizontal)
1283 Debug.WriteLineIf(LayoutDebugScrollableBase, "Horizontal");
1285 scrollingChildLength = ContentContainer.Size.Width;
1286 scrollerLength = Size.Width;
1290 Debug.WriteLineIf(LayoutDebugScrollableBase, "Vertical");
1291 scrollingChildLength = ContentContainer.Size.Height;
1292 scrollerLength = Size.Height;
1295 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy maxScrollDistance:" + (scrollingChildLength - scrollerLength) +
1296 " parent length:" + scrollerLength +
1297 " scrolling child length:" + scrollingChildLength);
1299 return Math.Max(scrollingChildLength - scrollerLength, 0);
1302 private void PageSnap(float velocity)
1306 Debug.WriteLineIf(LayoutDebugScrollableBase, "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan +
1307 " currentPage[" + CurrentPage + "]");
1309 //Increment current page if total displacement enough to warrant a page change.
1310 if (Math.Abs(totalDisplacementForPan) > (mPageWidth * ratioOfScreenWidthToCompleteScroll))
1312 if (totalDisplacementForPan < 0)
1314 CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
1318 CurrentPage = Math.Max(0, --CurrentPage);
1321 else if (Math.Abs(velocity) > PageFlickThreshold)
1325 CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
1329 CurrentPage = Math.Max(0, --CurrentPage);
1333 // Animate to new page or reposition to current page
1334 if (ScrollingDirection == Direction.Horizontal)
1335 destination = -(Children[CurrentPage].Position.X + Children[CurrentPage].CurrentSize.Width / 2 - CurrentSize.Width / 2); // set to middle of current page
1337 destination = -(Children[CurrentPage].Position.Y + Children[CurrentPage].CurrentSize.Height / 2 - CurrentSize.Height / 2);
1339 AnimateChildTo(ScrollDuration, destination);
1343 /// Enable/Disable overshooting effect. default is disabled.
1345 [EditorBrowsable(EditorBrowsableState.Never)]
1346 public bool EnableOverShootingEffect
1350 return (bool)GetValue(EnableOverShootingEffectProperty);
1354 SetValue(EnableOverShootingEffectProperty, value);
1355 NotifyPropertyChanged();
1358 private bool InternalEnableOverShootingEffect { get; set; } = false;
1360 private void AttachOverShootingShadowView()
1362 if (!EnableOverShootingEffect)
1365 // stop animation if necessary.
1366 StopOverShootingShadowAnimation();
1368 if (ScrollingDirection == Direction.Horizontal)
1370 base.Add(leftOverShootingShadowView);
1371 base.Add(rightOverShootingShadowView);
1373 leftOverShootingShadowView.Size = new Size(0.0f, SizeHeight);
1374 leftOverShootingShadowView.Opacity = 1.0f;
1375 leftOverShootingShadowView.RaiseToTop();
1377 rightOverShootingShadowView.Size = new Size(0.0f, SizeHeight);
1378 rightOverShootingShadowView.Opacity = 1.0f;
1379 rightOverShootingShadowView.RaiseToTop();
1383 base.Add(topOverShootingShadowView);
1384 base.Add(bottomOverShootingShadowView);
1386 topOverShootingShadowView.Size = new Size(SizeWidth, 0.0f);
1387 topOverShootingShadowView.Opacity = 1.0f;
1388 topOverShootingShadowView.RaiseToTop();
1390 bottomOverShootingShadowView.Size = new Size(SizeWidth, 0.0f);
1391 bottomOverShootingShadowView.Opacity = 1.0f;
1392 bottomOverShootingShadowView.RaiseToTop();
1395 // at the beginning, height or width of overshooting shadow is 0, so it is invisible.
1396 isOverShootingShadowShown = false;
1399 private void DragOverShootingShadow(float totalPanDisplacement, float panDisplacement)
1401 if (!EnableOverShootingEffect)
1404 if (totalPanDisplacement > 0) // downwards
1406 // check if reaching at the top / left.
1407 if ((int)finalTargetPosition != 0)
1409 isOverShootingShadowShown = false;
1413 // save start displacement, and re-calculate displacement.
1414 if (!isOverShootingShadowShown)
1416 startShowShadowDisplacement = totalPanDisplacement;
1418 isOverShootingShadowShown = true;
1420 float newDisplacement = (int)totalPanDisplacement < (int)startShowShadowDisplacement ? 0 : totalPanDisplacement - startShowShadowDisplacement;
1422 if (ScrollingDirection == Direction.Horizontal)
1424 // scale limit of height is 60%.
1425 float heightScale = newDisplacement / overShootingShadowScaleHeightLimit;
1426 leftOverShootingShadowView.SizeHeight = heightScale > 0.6f ? SizeHeight * 0.4f : SizeHeight * (1.0f - heightScale);
1428 // scale limit of width is 300%.
1429 leftOverShootingShadowView.SizeWidth = newDisplacement > overShootingShadowScaleHeightLimit ? overShootingShadowScaleHeightLimit : newDisplacement;
1432 ScrollOutOfBoundEventArgs.Direction scrollDirection = panDisplacement > 0 ?
1433 ScrollOutOfBoundEventArgs.Direction.Right : ScrollOutOfBoundEventArgs.Direction.Left;
1434 OnScrollOutOfBound(scrollDirection, totalPanDisplacement);
1438 // scale limit of width is 60%.
1439 float widthScale = newDisplacement / overShootingShadowScaleHeightLimit;
1440 topOverShootingShadowView.SizeWidth = widthScale > 0.6f ? SizeWidth * 0.4f : SizeWidth * (1.0f - widthScale);
1442 // scale limit of height is 300%.
1443 topOverShootingShadowView.SizeHeight = newDisplacement > overShootingShadowScaleHeightLimit ? overShootingShadowScaleHeightLimit : newDisplacement;
1446 ScrollOutOfBoundEventArgs.Direction scrollDirection = panDisplacement > 0 ?
1447 ScrollOutOfBoundEventArgs.Direction.Down : ScrollOutOfBoundEventArgs.Direction.Up;
1448 OnScrollOutOfBound(scrollDirection, totalPanDisplacement);
1451 else if (totalPanDisplacement < 0) // upwards
1453 // check if reaching at the bottom.
1454 if (-(int)finalTargetPosition != (int)maxScrollDistance)
1456 isOverShootingShadowShown = false;
1460 // save start displacement, and re-calculate displacement.
1461 if (!isOverShootingShadowShown)
1463 startShowShadowDisplacement = totalPanDisplacement;
1465 isOverShootingShadowShown = true;
1467 float newDisplacement = (int)startShowShadowDisplacement < (int)totalPanDisplacement ? 0 : startShowShadowDisplacement - totalPanDisplacement;
1469 if (ScrollingDirection == Direction.Horizontal)
1471 // scale limit of height is 60%.
1472 float heightScale = newDisplacement / overShootingShadowScaleHeightLimit;
1473 rightOverShootingShadowView.SizeHeight = heightScale > 0.6f ? SizeHeight * 0.4f : SizeHeight * (1.0f - heightScale);
1475 // scale limit of width is 300%.
1476 rightOverShootingShadowView.SizeWidth = newDisplacement > overShootingShadowScaleHeightLimit ? overShootingShadowScaleHeightLimit : newDisplacement;
1479 ScrollOutOfBoundEventArgs.Direction scrollDirection = panDisplacement > 0 ?
1480 ScrollOutOfBoundEventArgs.Direction.Right : ScrollOutOfBoundEventArgs.Direction.Left;
1481 OnScrollOutOfBound(scrollDirection, totalPanDisplacement);
1485 // scale limit of width is 60%.
1486 float widthScale = newDisplacement / overShootingShadowScaleHeightLimit;
1487 bottomOverShootingShadowView.SizeWidth = widthScale > 0.6f ? SizeWidth * 0.4f : SizeWidth * (1.0f - widthScale);
1489 // scale limit of height is 300%.
1490 bottomOverShootingShadowView.SizeHeight = newDisplacement > overShootingShadowScaleHeightLimit ? overShootingShadowScaleHeightLimit : newDisplacement;
1493 ScrollOutOfBoundEventArgs.Direction scrollDirection = panDisplacement > 0 ?
1494 ScrollOutOfBoundEventArgs.Direction.Down : ScrollOutOfBoundEventArgs.Direction.Up;
1495 OnScrollOutOfBound(scrollDirection, totalPanDisplacement);
1500 // if total displacement is 0, shadow would become invisible.
1501 isOverShootingShadowShown = false;
1505 private void PlayOverShootingShadowAnimation()
1507 if (!EnableOverShootingEffect)
1510 // stop animation if necessary.
1511 StopOverShootingShadowAnimation();
1513 if (overShootingShadowAnimation == null)
1515 overShootingShadowAnimation = new Animation(overShootingShadowAnimationDuration);
1516 overShootingShadowAnimation.Finished += OnOverShootingShadowAnimationFinished;
1519 if (ScrollingDirection == Direction.Horizontal)
1521 View targetView = totalDisplacementForPan < 0 ? rightOverShootingShadowView : leftOverShootingShadowView;
1522 overShootingShadowAnimation.AnimateTo(targetView, "SizeHeight", SizeHeight);
1523 overShootingShadowAnimation.AnimateTo(targetView, "SizeWidth", 0.0f);
1524 overShootingShadowAnimation.AnimateTo(targetView, "Opacity", 0.0f);
1528 View targetView = totalDisplacementForPan < 0 ? bottomOverShootingShadowView : topOverShootingShadowView;
1529 overShootingShadowAnimation.AnimateTo(targetView, "SizeWidth", SizeWidth);
1530 overShootingShadowAnimation.AnimateTo(targetView, "SizeHeight", 0.0f);
1531 overShootingShadowAnimation.AnimateTo(targetView, "Opacity", 0.0f);
1533 overShootingShadowAnimation.Play();
1536 private void StopOverShootingShadowAnimation()
1538 if (overShootingShadowAnimation == null || overShootingShadowAnimation.State != Animation.States.Playing)
1541 overShootingShadowAnimation.Stop(Animation.EndActions.Cancel);
1542 OnOverShootingShadowAnimationFinished(null, null);
1543 overShootingShadowAnimation.Clear();
1546 private void OnOverShootingShadowAnimationFinished(object sender, EventArgs e)
1548 if (ScrollingDirection == Direction.Horizontal)
1550 base.Remove(leftOverShootingShadowView);
1551 base.Remove(rightOverShootingShadowView);
1553 leftOverShootingShadowView.Size = new Size(0.0f, SizeHeight);
1554 rightOverShootingShadowView.Size = new Size(0.0f, SizeHeight);
1558 base.Remove(topOverShootingShadowView);
1559 base.Remove(bottomOverShootingShadowView);
1561 topOverShootingShadowView.Size = new Size(SizeWidth, 0.0f);
1562 bottomOverShootingShadowView.Size = new Size(SizeWidth, 0.0f);
1565 // after animation finished, height/width & opacity of vertical shadow both are 0, so it is invisible.
1566 isOverShootingShadowShown = false;
1569 private void OnScrollOutOfBound(ScrollOutOfBoundEventArgs.Direction direction, float displacement)
1571 ScrollOutOfBoundEventArgs args = new ScrollOutOfBoundEventArgs(direction, displacement);
1572 ScrollOutOfBound?.Invoke(this, args);
1575 private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
1577 e.Handled = OnPanGesture(e.PanGesture);
1580 private bool OnPanGesture(PanGesture panGesture)
1582 bool handled = true;
1583 if (SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
1587 if (panGesture.State == Gesture.StateType.Started)
1589 readyToNotice = false;
1590 AttachOverShootingShadowView();
1591 Debug.WriteLineIf(LayoutDebugScrollableBase, "Gesture Start");
1592 if (scrolling && !SnapToPage)
1596 totalDisplacementForPan = 0.0f;
1598 // check if gesture need to propagation
1599 var checkDisplacement = (ScrollingDirection == Direction.Horizontal) ? panGesture.Displacement.X : panGesture.Displacement.Y;
1600 var checkChildCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? ContentContainer.PositionX : ContentContainer.PositionY;
1601 var checkChildTargetPosition = checkChildCurrentPosition + checkDisplacement;
1602 var checkFinalTargetPosition = BoundScrollPosition(checkChildTargetPosition);
1603 handled = !((int)checkFinalTargetPosition == 0 || -(int)checkFinalTargetPosition == (int)maxScrollDistance);
1604 // If you propagate a gesture event, return;
1610 //Interrupt touching when panning is started
1611 this.InterceptTouchEvent += OnInterruptTouchingChildTouched;
1612 OnScrollDragStarted();
1614 else if (panGesture.State == Gesture.StateType.Continuing)
1616 if (ScrollingDirection == Direction.Horizontal)
1618 // if vertical shadow is shown, does not scroll.
1619 if (!isOverShootingShadowShown)
1621 ScrollBy(panGesture.Displacement.X, false);
1623 totalDisplacementForPan += panGesture.Displacement.X;
1624 DragOverShootingShadow(totalDisplacementForPan, panGesture.Displacement.X);
1628 // if vertical shadow is shown, does not scroll.
1629 if (!isOverShootingShadowShown)
1631 ScrollBy(panGesture.Displacement.Y, false);
1633 totalDisplacementForPan += panGesture.Displacement.Y;
1634 DragOverShootingShadow(totalDisplacementForPan, panGesture.Displacement.Y);
1636 Debug.WriteLineIf(LayoutDebugScrollableBase, "OnPanGestureDetected Continue totalDisplacementForPan:" + totalDisplacementForPan);
1639 else if (panGesture.State == Gesture.StateType.Finished || panGesture.State == Gesture.StateType.Cancelled)
1641 PlayOverShootingShadowAnimation();
1642 OnScrollDragEnded();
1643 StopScroll(); // Will replace previous animation so will stop existing one.
1645 if (scrollAnimation == null)
1647 scrollAnimation = new Animation();
1648 scrollAnimation.Finished += ScrollAnimationFinished;
1651 float panVelocity = (ScrollingDirection == Direction.Horizontal) ? panGesture.Velocity.X : panGesture.Velocity.Y;
1655 PageSnap(panVelocity);
1659 if (panVelocity == 0)
1661 float currentScrollPosition = (ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
1662 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
1663 scrollAnimation.Duration = 0;
1664 scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", currentScrollPosition);
1665 scrollAnimation.Play();
1669 Decelerating(panVelocity, scrollAnimation);
1673 totalDisplacementForPan = 0;
1675 readyToNotice = true;
1676 OnScrollAnimationStarted();
1681 internal void BaseRemove(View view)
1686 internal override bool OnAccessibilityPan(PanGesture gestures)
1688 if (SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
1693 OnPanGesture(gestures);
1697 private float CustomScrollAlphaFunction(float progress)
1699 if (panAnimationDelta == 0)
1705 // Parameter "progress" is normalized value. We need to multiply target duration to calculate distance.
1706 // Can get real distance using equation of deceleration (check Decelerating function)
1707 // After get real distance, normalize it
1708 float realDuration = progress * panAnimationDuration;
1709 float realDistance = velocityOfLastPan * ((float)Math.Pow(decelerationRate, realDuration) - 1) / logValueOfDeceleration;
1710 float result = Math.Min(realDistance / Math.Abs(panAnimationDelta), 1.0f);
1716 /// you can override it to custom your decelerating
1718 /// <param name="velocity">Velocity of current pan.</param>
1719 /// <param name="animation">Scroll animation.</param>
1720 [EditorBrowsable(EditorBrowsableState.Never)]
1721 protected virtual void Decelerating(float velocity, Animation animation)
1723 // Decelerating using deceleration equation ===========
1725 // V : velocity (pixel per millisecond)
1726 // V0 : initial velocity
1727 // d : deceleration rate,
1729 // X : final position after decelerating
1730 // log : natural logarithm
1732 // V(t) = V0 * d pow t;
1733 // X(t) = V0 * (d pow t - 1) / log d; <-- Integrate the velocity function
1734 // X(∞) = V0 * d / (1 - d); <-- Result using infinite T can be final position because T is tending to infinity.
1736 // Because of final T is tending to infinity, we should use threshold value to finish.
1737 // Final T = log(-threshold * log d / |V0| ) / log d;
1739 velocityOfLastPan = Math.Abs(velocity);
1741 float currentScrollPosition = -(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
1742 panAnimationDelta = (velocityOfLastPan * decelerationRate) / (1 - decelerationRate);
1743 panAnimationDelta = velocity > 0 ? -panAnimationDelta : panAnimationDelta;
1745 float destination = -(panAnimationDelta + currentScrollPosition);
1746 float adjustDestination = AdjustTargetPositionOfScrollAnimation(destination);
1747 float maxPosition = ScrollAvailableArea != null ? ScrollAvailableArea.Y : maxScrollDistance;
1748 float minPosition = ScrollAvailableArea != null ? ScrollAvailableArea.X : 0;
1750 if (destination < -maxPosition || destination > minPosition)
1752 panAnimationDelta = velocity > 0 ? (currentScrollPosition - minPosition) : (maxPosition - currentScrollPosition);
1753 destination = velocity > 0 ? minPosition : -maxPosition;
1755 if (panAnimationDelta == 0)
1757 panAnimationDuration = 0.0f;
1761 panAnimationDuration = (float)Math.Log((panAnimationDelta * logValueOfDeceleration / velocityOfLastPan + 1), decelerationRate);
1764 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1765 "OverRange======================= \n" +
1766 "[decelerationRate] " + decelerationRate + "\n" +
1767 "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1768 "[Velocity] " + velocityOfLastPan + "\n" +
1769 "[CurrentPosition] " + currentScrollPosition + "\n" +
1770 "[CandidateDelta] " + panAnimationDelta + "\n" +
1771 "[Destination] " + destination + "\n" +
1772 "[Duration] " + panAnimationDuration + "\n" +
1773 "================================ \n"
1778 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1780 if (adjustDestination != destination)
1782 destination = adjustDestination;
1783 panAnimationDelta = destination + currentScrollPosition;
1784 velocityOfLastPan = Math.Abs(panAnimationDelta * logValueOfDeceleration / ((float)Math.Pow(decelerationRate, panAnimationDuration) - 1));
1785 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1788 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1789 "================================ \n" +
1790 "[decelerationRate] " + decelerationRate + "\n" +
1791 "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1792 "[Velocity] " + velocityOfLastPan + "\n" +
1793 "[CurrentPosition] " + currentScrollPosition + "\n" +
1794 "[CandidateDelta] " + panAnimationDelta + "\n" +
1795 "[Destination] " + destination + "\n" +
1796 "[Duration] " + panAnimationDuration + "\n" +
1797 "================================ \n"
1801 finalTargetPosition = destination;
1803 customScrollAlphaFunction = new UserAlphaFunctionDelegate(CustomScrollAlphaFunction);
1804 animation.DefaultAlphaFunction = new AlphaFunction(customScrollAlphaFunction);
1805 GC.KeepAlive(customScrollAlphaFunction);
1806 animation.Duration = (int)panAnimationDuration;
1807 animation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", destination);
1811 private void ScrollAnimationFinished(object sender, EventArgs e)
1813 OnScrollAnimationEnded();
1817 /// Adjust scrolling position by own scrolling rules.
1818 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1820 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
1821 [EditorBrowsable(EditorBrowsableState.Never)]
1822 protected virtual float AdjustTargetPositionOfScrollAnimation(float position)
1828 /// Scroll position given to ScrollTo.
1829 /// This is the position in the opposite direction to the position of ContentContainer.
1831 /// <since_tizen> 8 </since_tizen>
1832 public Position ScrollPosition
1836 return new Position(-ContentContainer.Position);
1841 /// Current scroll position in the middle of ScrollTo animation.
1842 /// This is the position in the opposite direction to the current position of ContentContainer.
1844 /// <since_tizen> 8 </since_tizen>
1845 public Position ScrollCurrentPosition
1849 return new Position(-ContentContainer.CurrentPosition);
1854 /// Remove all children in ContentContainer.
1856 /// <param name="dispose">If true, removed child is disposed.</param>
1857 [EditorBrowsable(EditorBrowsableState.Never)]
1858 public void RemoveAllChildren(bool dispose = false)
1860 RecursiveRemoveChildren(ContentContainer, dispose);
1863 private void RecursiveRemoveChildren(View parent, bool dispose)
1869 int maxChild = (int)parent.GetChildCount();
1870 for (int i = maxChild - 1; i >= 0; --i)
1872 View child = parent.GetChildAt((uint)i);
1877 RecursiveRemoveChildren(child, dispose);
1878 parent.Remove(child);
1886 internal bool IsChildNearlyVisble(View child, float offset = 0)
1888 if (ScreenPosition.X - offset < child.ScreenPosition.X + child.SizeWidth &&
1889 ScreenPosition.X + SizeWidth + offset > child.ScreenPosition.X &&
1890 ScreenPosition.Y - offset < child.ScreenPosition.Y + child.SizeHeight &&
1891 ScreenPosition.Y + SizeHeight + offset > child.ScreenPosition.Y)
1903 [EditorBrowsable(EditorBrowsableState.Never)]
1904 public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
1906 bool isHorizontal = (ScrollingDirection == Direction.Horizontal);
1907 float targetPosition = -(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
1908 float stepDistance = (stepScrollDistance != 0? stepScrollDistance : (isHorizontal ? Size.Width * 0.25f : Size.Height * 0.25f));
1910 View nextFocusedView = FocusManager.Instance.GetNearestFocusableActor(this, currentFocusedView, direction);
1912 if (nextFocusedView != null)
1914 if (null != FindDescendantByID(nextFocusedView.ID))
1916 if (IsChildNearlyVisble(nextFocusedView, stepDistance) == true)
1918 ScrollToChild(nextFocusedView, true);
1922 if ((isHorizontal && direction == View.FocusDirection.Right) ||
1923 (!isHorizontal && direction == View.FocusDirection.Down))
1925 targetPosition += stepDistance;
1926 targetPosition = targetPosition > maxScrollDistance ? maxScrollDistance : targetPosition;
1929 else if ((isHorizontal && direction == View.FocusDirection.Left) ||
1930 (!isHorizontal && direction == View.FocusDirection.Up))
1932 targetPosition -= stepDistance;
1933 targetPosition = targetPosition < 0 ? 0 : targetPosition;
1936 ScrollTo(targetPosition, true);
1942 if((isHorizontal && direction == View.FocusDirection.Right) ||
1943 (!isHorizontal && direction == View.FocusDirection.Down))
1945 targetPosition += stepDistance;
1946 targetPosition = targetPosition > maxScrollDistance ? maxScrollDistance : targetPosition;
1949 else if((isHorizontal && direction == View.FocusDirection.Left) ||
1950 (!isHorizontal && direction == View.FocusDirection.Up))
1952 targetPosition -= stepDistance;
1953 targetPosition = targetPosition < 0 ? 0 : targetPosition;
1956 ScrollTo(targetPosition, true);
1958 // End of scroll. escape.
1959 if ((targetPosition == 0 || targetPosition == maxScrollDistance) == false)
1965 return nextFocusedView;
1969 [EditorBrowsable(EditorBrowsableState.Never)]
1970 protected override bool AccessibilityScrollToChild(View child)
1977 if (ScrollingDirection == Direction.Horizontal)
1979 if (child.ScreenPosition.X + child.Size.Width <= this.ScreenPosition.X)
1983 PageSnap(PageFlickThreshold + 1);
1987 ScrollTo((float)(child.ScreenPosition.X - ContentContainer.ScreenPosition.X), false);
1990 else if (child.ScreenPosition.X >= this.ScreenPosition.X + this.Size.Width)
1994 PageSnap(-(PageFlickThreshold + 1));
1998 ScrollTo((float)(child.ScreenPosition.X + child.Size.Width - ContentContainer.ScreenPosition.X - this.Size.Width), false);
2004 if (child.ScreenPosition.Y + child.Size.Height <= this.ScreenPosition.Y)
2008 PageSnap(PageFlickThreshold + 1);
2012 ScrollTo((float)(child.ScreenPosition.Y - ContentContainer.ScreenPosition.Y), false);
2015 else if (child.ScreenPosition.Y >= this.ScreenPosition.Y + this.Size.Height)
2019 PageSnap(-(PageFlickThreshold + 1));
2023 ScrollTo((float)(child.ScreenPosition.Y + child.Size.Height - ContentContainer.ScreenPosition.Y - this.Size.Height), false);