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 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 mScrollingDirection;
261 if (value != mScrollingDirection)
263 //Reset scroll position and stop scroll animation
266 mScrollingDirection = value;
267 mPanGestureDetector.ClearAngles();
268 mPanGestureDetector.AddDirection(value == Direction.Horizontal ?
269 PanGestureDetector.DirectionHorizontal : PanGestureDetector.DirectionVertical);
271 ContentContainer.WidthSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent;
272 ContentContainer.HeightSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent;
279 /// Enable or disable scrolling.
281 /// <since_tizen> 8 </since_tizen>
282 public bool ScrollEnabled
286 return mScrollEnabled;
290 if (value != mScrollEnabled)
292 mScrollEnabled = value;
295 mPanGestureDetector.Detected += OnPanGestureDetected;
299 mPanGestureDetector.Detected -= OnPanGestureDetected;
306 /// Gets scrollable status.
308 [EditorBrowsable(EditorBrowsableState.Never)]
309 protected override bool AccessibilityIsScrollable()
315 /// Pages mode, enables moving to the next or return to current page depending on pan displacement.
316 /// Default is false.
318 /// <since_tizen> 8 </since_tizen>
319 public bool SnapToPage { set; get; } = false;
322 /// Get current page.
323 /// Working property with SnapToPage property.
325 /// <since_tizen> 8 </since_tizen>
326 public int CurrentPage { get; private set; } = 0;
329 /// Duration of scroll animation.
330 /// Default value is 125ms.
332 /// <since_tizen> 8 </since_tizen>
333 public int ScrollDuration
337 mScrollDuration = value >= 0 ? value : mScrollDuration;
341 return mScrollDuration;
346 /// Scroll Available area.
348 /// <since_tizen> 8 </since_tizen>
349 public Vector2 ScrollAvailableArea { set; get; }
352 /// An event emitted when user starts dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
354 /// <since_tizen> 8 </since_tizen>
355 public event EventHandler<ScrollEventArgs> ScrollDragStarted;
358 /// An event emitted when user stops dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
360 /// <since_tizen> 8 </since_tizen>
361 public event EventHandler<ScrollEventArgs> ScrollDragEnded;
364 /// An event emitted when the scrolling slide animation starts, user can subscribe or unsubscribe to this event handler.<br />
366 /// <since_tizen> 8 </since_tizen>
367 public event EventHandler<ScrollEventArgs> ScrollAnimationStarted;
370 /// An event emitted when the scrolling slide animation ends, user can subscribe or unsubscribe to this event handler.<br />
372 /// <since_tizen> 8 </since_tizen>
373 public event EventHandler<ScrollEventArgs> ScrollAnimationEnded;
376 /// An event emitted when scrolling, user can subscribe or unsubscribe to this event handler.<br />
378 /// <since_tizen> 8 </since_tizen>
379 public event EventHandler<ScrollEventArgs> Scrolling;
382 /// An event emitted when scrolling out of bound, user can subscribe or unsubscribe to this event handler.<br />
384 [EditorBrowsable(EditorBrowsableState.Never)]
385 public event EventHandler<ScrollOutOfBoundEventArgs> ScrollOutOfBound;
388 /// Scrollbar for ScrollableBase.
390 /// <since_tizen> 8 </since_tizen>
391 public ScrollbarBase Scrollbar
401 base.Remove(scrollBar);
405 if (scrollBar != null)
407 scrollBar.Name = "ScrollBar";
425 /// Always hide Scrollbar.
427 /// <since_tizen> 8 </since_tizen>
428 public bool HideScrollbar
432 return hideScrollbar;
436 hideScrollbar = value;
453 /// Container which has content of ScrollableBase.
455 /// <since_tizen> 8 </since_tizen>
456 public View ContentContainer { get; private set; }
459 /// Set the layout on this View. Replaces any existing Layout.
461 /// <since_tizen> 8 </since_tizen>
462 public new LayoutItem Layout
466 return ContentContainer.Layout;
470 ContentContainer.Layout = value;
475 /// List of children of Container.
477 /// <since_tizen> 8 </since_tizen>
478 public new List<View> Children
482 return ContentContainer.Children;
487 /// Deceleration rate of scrolling by finger.
488 /// Rate should be bigger than 0 and smaller than 1.
489 /// Default value is 0.998f;
491 /// <since_tizen> 8 </since_tizen>
492 public float DecelerationRate
496 return decelerationRate;
500 decelerationRate = (value < 1 && value > 0) ? value : decelerationRate;
501 logValueOfDeceleration = (float)Math.Log(value);
506 /// Threshold not to go infinite at the end of scrolling animation.
508 [EditorBrowsable(EditorBrowsableState.Never)]
509 public float DecelerationThreshold { get; set; } = 0.1f;
512 /// Scrolling event will be thrown when this amount of scroll position is changed.
513 /// If this threshold becomes smaller, the tracking detail increases but the scrolling range that can be tracked becomes smaller.
514 /// If large sized ContentContainer is required, please use larger threshold value.
515 /// Default ScrollingEventThreshold value is 0.001f.
517 [EditorBrowsable(EditorBrowsableState.Never)]
518 public float ScrollingEventThreshold
522 return mScrollingEventThreshold;
526 if (mScrollingEventThreshold != value && value > 0)
528 ContentContainer.RemovePropertyNotification(propertyNotification);
529 propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(value));
530 propertyNotification.Notified += OnPropertyChanged;
531 mScrollingEventThreshold = value;
537 /// Page will be changed when velocity of panning is over threshold.
538 /// The unit of threshold is pixel per millisecond.
540 /// <since_tizen> 8 </since_tizen>
541 public float PageFlickThreshold
545 return mPageFlickThreshold;
549 mPageFlickThreshold = value >= 0f ? value : mPageFlickThreshold;
554 /// Padding for the ScrollableBase
556 [EditorBrowsable(EditorBrowsableState.Never)]
557 public new Extents Padding
561 return ContentContainer.Padding;
565 ContentContainer.Padding = value;
570 /// Alphafunction for scroll animation.
572 [EditorBrowsable(EditorBrowsableState.Never)]
573 public AlphaFunction ScrollAlphaFunction { get; set; } = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
575 private bool hideScrollbar = true;
576 private float maxScrollDistance;
577 private float childTargetPosition = 0.0f;
578 private PanGestureDetector mPanGestureDetector;
579 private ScrollbarBase scrollBar;
580 private bool scrolling = false;
581 private float ratioOfScreenWidthToCompleteScroll = 0.5f;
582 private float totalDisplacementForPan = 0.0f;
583 private Size previousContainerSize = new Size();
584 private Size previousSize = new Size();
585 private PropertyNotification propertyNotification;
586 private float noticeAnimationEndBeforePosition = 0.0f;
587 private bool readyToNotice = false;
590 /// Notice before animation is finished.
592 [EditorBrowsable(EditorBrowsableState.Never)]
593 // Let's consider more whether this needs to be set as protected.
594 public float NoticeAnimationEndBeforePosition
596 get => noticeAnimationEndBeforePosition;
597 set => noticeAnimationEndBeforePosition = value;
600 // Let's consider more whether this needs to be set as protected.
601 private float finalTargetPosition;
603 private Animation scrollAnimation;
604 // Declare user alpha function delegate
605 [UnmanagedFunctionPointer(CallingConvention.StdCall)]
606 private delegate float UserAlphaFunctionDelegate(float progress);
607 private UserAlphaFunctionDelegate customScrollAlphaFunction;
608 private float velocityOfLastPan = 0.0f;
609 private float panAnimationDuration = 0.0f;
610 private float panAnimationDelta = 0.0f;
611 private float logValueOfDeceleration = 0.0f;
612 private float decelerationRate = 0.0f;
614 private View topOverShootingShadowView;
615 private View bottomOverShootingShadowView;
616 private View leftOverShootingShadowView;
617 private View rightOverShootingShadowView;
618 private const int overShootingShadowScaleHeightLimit = 64 * 3;
619 private const int overShootingShadowAnimationDuration = 300;
620 private Animation overShootingShadowAnimation;
621 private bool isOverShootingShadowShown = false;
622 private float startShowShadowDisplacement;
625 /// Default Constructor
627 /// <since_tizen> 8 </since_tizen>
628 public ScrollableBase() : base()
630 DecelerationRate = 0.998f;
632 base.Layout = new ScrollableBaseCustomLayout();
633 mPanGestureDetector = new PanGestureDetector();
634 mPanGestureDetector.Attach(this);
635 mPanGestureDetector.AddDirection(PanGestureDetector.DirectionVertical);
636 if (mPanGestureDetector.GetMaximumTouchesRequired() < 2) mPanGestureDetector.SetMaximumTouchesRequired(2);
637 mPanGestureDetector.Detected += OnPanGestureDetected;
639 ClippingMode = ClippingModeType.ClipToBoundingBox;
641 //Default Scrolling child
642 ContentContainer = new View()
644 Name = "ContentContainer",
645 WidthSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent,
646 HeightSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent,
648 // Check if children's sizes change to update Scrollbar
649 ContentContainer.Relayout += OnScrollingChildRelayout;
650 propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(mScrollingEventThreshold));
651 propertyNotification.Notified += OnPropertyChanged;
652 base.Add(ContentContainer);
653 // Check if ScrollableBase's size changes to update Scrollbar
654 base.Relayout += OnScrollingChildRelayout;
656 Scrollbar = new Scrollbar();
658 //Show vertical shadow on the top (or bottom) of the scrollable when panning down (or up).
659 topOverShootingShadowView = new View
661 BackgroundImage = FrameworkInformation.ResourcePath + "nui_component_default_scroll_over_shooting_top.png",
664 PositionUsesPivotPoint = true,
665 ParentOrigin = NUI.ParentOrigin.TopCenter,
666 PivotPoint = NUI.PivotPoint.TopCenter,
668 bottomOverShootingShadowView = new View
670 BackgroundImage = FrameworkInformation.ResourcePath + "nui_component_default_scroll_over_shooting_bottom.png",
673 PositionUsesPivotPoint = true,
674 ParentOrigin = NUI.ParentOrigin.BottomCenter,
675 PivotPoint = NUI.PivotPoint.BottomCenter,
677 //Show horizontal shadow on the left (or right) of the scrollable when panning down (or up).
678 leftOverShootingShadowView = new View
680 BackgroundImage = FrameworkInformation.ResourcePath + "nui_component_default_scroll_over_shooting_left.png",
683 PositionUsesPivotPoint = true,
684 ParentOrigin = NUI.ParentOrigin.CenterLeft,
685 PivotPoint = NUI.PivotPoint.CenterLeft,
687 rightOverShootingShadowView = new View
689 BackgroundImage = FrameworkInformation.ResourcePath + "nui_component_default_scroll_over_shooting_right.png",
692 PositionUsesPivotPoint = true,
693 ParentOrigin = NUI.ParentOrigin.CenterRight,
694 PivotPoint = NUI.PivotPoint.CenterRight,
697 AccessibilityManager.Instance.SetAccessibilityAttribute(this, AccessibilityManager.AccessibilityAttribute.Trait, "ScrollableBase");
699 SetKeyboardNavigationSupport(true);
702 private bool OnInterruptTouchingChildTouched(object source, View.TouchEventArgs args)
704 if (args.Touch.GetState(0) == PointStateType.Down)
706 if (scrolling && !SnapToPage)
714 private void OnPropertyChanged(object source, PropertyNotification.NotifyEventArgs args)
720 /// Called after a child has been added to the owning view.
722 /// <param name="view">The child which has been added.</param>
723 /// <since_tizen> 8 </since_tizen>
724 public override void Add(View view)
726 ContentContainer.Add(view);
730 /// Called after a child has been removed from the owning view.
732 /// <param name="view">The child which has been removed.</param>
733 /// <since_tizen> 8 </since_tizen>
734 public override void Remove(View view)
736 if (SnapToPage && CurrentPage == Children.IndexOf(view) && CurrentPage == Children.Count - 1 && Children.Count > 1)
738 // Target View is current page and also last child.
739 // CurrentPage should be changed to previous page.
740 ScrollToIndex(CurrentPage - 1);
743 ContentContainer.Remove(view);
746 private void OnScrollingChildRelayout(object source, EventArgs args)
748 // Size is changed. Calculate maxScrollDistance.
749 bool isSizeChanged = previousContainerSize.Width != ContentContainer.Size.Width || previousContainerSize.Height != ContentContainer.Size.Height ||
750 previousSize.Width != Size.Width || previousSize.Height != Size.Height;
754 maxScrollDistance = CalculateMaximumScrollDistance();
755 if (!ReviseContainerPositionIfNeed())
761 previousContainerSize = ContentContainer.Size;
765 private bool ReviseContainerPositionIfNeed()
767 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
768 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
770 if (Math.Abs(currentPosition) > maxScrollDistance)
773 var targetPosition = BoundScrollPosition(-maxScrollDistance);
774 if (isHorizontal) ContentContainer.PositionX = targetPosition;
775 else ContentContainer.PositionY = targetPosition;
783 /// The composition of a Scrollbar can vary depending on how you use ScrollableBase.
784 /// Set the composition that will go into the ScrollableBase according to your ScrollableBase.
786 /// <since_tizen> 8 </since_tizen>
787 [EditorBrowsable(EditorBrowsableState.Never)]
788 protected virtual void SetScrollbar()
792 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
793 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
794 float viewportLength = isHorizontal ? Size.Width : Size.Height;
795 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
796 Scrollbar.Initialize(contentLength, viewportLength, -currentPosition, isHorizontal);
800 /// Update scrollbar position and size.
801 [EditorBrowsable(EditorBrowsableState.Never)]
802 protected virtual void UpdateScrollbar()
806 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
807 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
808 float viewportLength = isHorizontal ? Size.Width : Size.Height;
809 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
810 Scrollbar.Update(contentLength, viewportLength, -currentPosition);
815 /// Scrolls to the item at the specified index.
817 /// <param name="index">Index of item.</param>
818 /// <since_tizen> 8 </since_tizen>
819 public void ScrollToIndex(int index)
821 if (ContentContainer.ChildCount - 1 < index || index < 0)
831 float targetPosition = Math.Min(ScrollingDirection == Direction.Vertical ? Children[index].Position.Y : Children[index].Position.X, maxScrollDistance);
832 AnimateChildTo(ScrollDuration, -targetPosition);
835 private void OnScrollDragStarted()
837 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
838 ScrollDragStarted?.Invoke(this, eventArgs);
841 private void OnScrollDragEnded()
843 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
844 ScrollDragEnded?.Invoke(this, eventArgs);
847 private void OnScrollAnimationStarted()
849 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
850 ScrollAnimationStarted?.Invoke(this, eventArgs);
853 private void OnScrollAnimationEnded()
856 this.InterceptTouchEvent -= OnInterruptTouchingChildTouched;
858 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
859 ScrollAnimationEnded?.Invoke(this, eventArgs);
862 private void OnScroll()
864 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
865 Scrolling?.Invoke(this, eventArgs);
867 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
868 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
869 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
871 scrollBar?.Update(contentLength, Math.Abs(currentPosition));
872 CheckPreReachedTargetPosition();
875 private void CheckPreReachedTargetPosition()
877 // Check whether we reached pre-reached target position
879 ContentContainer.CurrentPosition.Y <= finalTargetPosition + NoticeAnimationEndBeforePosition &&
880 ContentContainer.CurrentPosition.Y >= finalTargetPosition - NoticeAnimationEndBeforePosition)
883 readyToNotice = false;
884 OnPreReachedTargetPosition(finalTargetPosition);
889 /// This helps developer who wants to know before scroll is reaching target position.
891 /// <param name="targetPosition">Index of item.</param>
892 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
893 [EditorBrowsable(EditorBrowsableState.Never)]
894 protected virtual void OnPreReachedTargetPosition(float targetPosition)
899 private void StopScroll()
901 if (scrollAnimation != null)
903 if (scrollAnimation.State == Animation.States.Playing)
905 Debug.WriteLineIf(LayoutDebugScrollableBase, "StopScroll Animation Playing");
906 scrollAnimation.Stop(Animation.EndActions.Cancel);
907 OnScrollAnimationEnded();
909 scrollAnimation.Clear();
913 private void AnimateChildTo(int duration, float axisPosition)
915 Debug.WriteLineIf(LayoutDebugScrollableBase, "AnimationTo Animation Duration:" + duration + " Destination:" + axisPosition);
916 finalTargetPosition = axisPosition;
918 StopScroll(); // Will replace previous animation so will stop existing one.
920 if (scrollAnimation == null)
922 scrollAnimation = new Animation();
923 scrollAnimation.Finished += ScrollAnimationFinished;
926 scrollAnimation.Duration = duration;
927 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSquare);
928 scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", axisPosition, ScrollAlphaFunction);
930 OnScrollAnimationStarted();
931 scrollAnimation.Play();
935 /// Scroll to specific position with or without animation.
937 /// <param name="position">Destination.</param>
938 /// <param name="animate">Scroll with or without animation</param>
939 /// <since_tizen> 8 </since_tizen>
940 public void ScrollTo(float position, bool animate)
943 float currentPositionX = ContentContainer.CurrentPosition.X != 0 ? ContentContainer.CurrentPosition.X : ContentContainer.Position.X;
944 float currentPositionY = ContentContainer.CurrentPosition.Y != 0 ? ContentContainer.CurrentPosition.Y : ContentContainer.Position.Y;
945 float delta = ScrollingDirection == Direction.Horizontal ? currentPositionX : currentPositionY;
946 // The argument position is the new pan position. So the new position of ScrollableBase becomes (-position).
947 // To move ScrollableBase's position to (-position), it moves by (-position - currentPosition).
948 delta = -position - delta;
950 ScrollBy(delta, animate);
953 private float BoundScrollPosition(float targetPosition)
955 if (ScrollAvailableArea != null)
957 float minScrollPosition = ScrollAvailableArea.X;
958 float maxScrollPosition = ScrollAvailableArea.Y;
960 targetPosition = Math.Min(-minScrollPosition, targetPosition);
961 targetPosition = Math.Max(-maxScrollPosition, targetPosition);
965 targetPosition = Math.Min(0, targetPosition);
966 targetPosition = Math.Max(-maxScrollDistance, targetPosition);
969 return targetPosition;
972 private void ScrollBy(float displacement, bool animate)
974 if (GetChildCount() == 0 || maxScrollDistance < 0)
979 float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? ContentContainer.PositionX : ContentContainer.PositionY;
981 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy childCurrentPosition:" + childCurrentPosition +
982 " displacement:" + displacement,
983 " maxScrollDistance:" + maxScrollDistance);
985 childTargetPosition = childCurrentPosition + displacement; // child current position + gesture displacement
987 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition);
991 // Calculate scroll animation duration
992 float scrollDistance = Math.Abs(displacement);
993 readyToNotice = true;
995 AnimateChildTo(ScrollDuration, BoundScrollPosition(AdjustTargetPositionOfScrollAnimation(BoundScrollPosition(childTargetPosition))));
1000 finalTargetPosition = BoundScrollPosition(childTargetPosition);
1002 // Set position of scrolling child without an animation
1003 if (ScrollingDirection == Direction.Horizontal)
1005 ContentContainer.PositionX = finalTargetPosition;
1009 ContentContainer.PositionY = finalTargetPosition;
1015 /// you can override it to clean-up your own resources.
1017 /// <param name="type">DisposeTypes</param>
1018 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
1019 [EditorBrowsable(EditorBrowsableState.Never)]
1020 protected override void Dispose(DisposeTypes type)
1027 if (type == DisposeTypes.Explicit)
1029 StopOverShootingShadowAnimation();
1032 if (mPanGestureDetector != null)
1034 mPanGestureDetector.Detected -= OnPanGestureDetected;
1035 mPanGestureDetector.Dispose();
1036 mPanGestureDetector = null;
1039 propertyNotification.Dispose();
1044 private float CalculateMaximumScrollDistance()
1046 float scrollingChildLength = 0;
1047 float scrollerLength = 0;
1048 if (ScrollingDirection == Direction.Horizontal)
1050 Debug.WriteLineIf(LayoutDebugScrollableBase, "Horizontal");
1052 scrollingChildLength = ContentContainer.Size.Width;
1053 scrollerLength = Size.Width;
1057 Debug.WriteLineIf(LayoutDebugScrollableBase, "Vertical");
1058 scrollingChildLength = ContentContainer.Size.Height;
1059 scrollerLength = Size.Height;
1062 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy maxScrollDistance:" + (scrollingChildLength - scrollerLength) +
1063 " parent length:" + scrollerLength +
1064 " scrolling child length:" + scrollingChildLength);
1066 return Math.Max(scrollingChildLength - scrollerLength, 0);
1069 private void PageSnap(float velocity)
1073 Debug.WriteLineIf(LayoutDebugScrollableBase, "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan +
1074 " currentPage[" + CurrentPage + "]");
1076 //Increment current page if total displacement enough to warrant a page change.
1077 if (Math.Abs(totalDisplacementForPan) > (mPageWidth * ratioOfScreenWidthToCompleteScroll))
1079 if (totalDisplacementForPan < 0)
1081 CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
1085 CurrentPage = Math.Max(0, --CurrentPage);
1088 else if (Math.Abs(velocity) > PageFlickThreshold)
1092 CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
1096 CurrentPage = Math.Max(0, --CurrentPage);
1100 // Animate to new page or reposition to current page
1101 if (ScrollingDirection == Direction.Horizontal)
1102 destination = -(Children[CurrentPage].Position.X + Children[CurrentPage].CurrentSize.Width / 2 - CurrentSize.Width / 2); // set to middle of current page
1104 destination = -(Children[CurrentPage].Position.Y + Children[CurrentPage].CurrentSize.Height / 2 - CurrentSize.Height / 2);
1106 AnimateChildTo(ScrollDuration, destination);
1110 /// Enable/Disable overshooting effect. default is disabled.
1112 [EditorBrowsable(EditorBrowsableState.Never)]
1113 public bool EnableOverShootingEffect { get; set; } = false;
1115 private void AttachOverShootingShadowView()
1117 if (!EnableOverShootingEffect)
1120 // stop animation if necessary.
1121 StopOverShootingShadowAnimation();
1123 if (ScrollingDirection == Direction.Horizontal)
1125 base.Add(leftOverShootingShadowView);
1126 base.Add(rightOverShootingShadowView);
1128 leftOverShootingShadowView.Size = new Size(0.0f, SizeHeight);
1129 leftOverShootingShadowView.Opacity = 1.0f;
1130 leftOverShootingShadowView.RaiseToTop();
1132 rightOverShootingShadowView.Size = new Size(0.0f, SizeHeight);
1133 rightOverShootingShadowView.Opacity = 1.0f;
1134 rightOverShootingShadowView.RaiseToTop();
1138 base.Add(topOverShootingShadowView);
1139 base.Add(bottomOverShootingShadowView);
1141 topOverShootingShadowView.Size = new Size(SizeWidth, 0.0f);
1142 topOverShootingShadowView.Opacity = 1.0f;
1143 topOverShootingShadowView.RaiseToTop();
1145 bottomOverShootingShadowView.Size = new Size(SizeWidth, 0.0f);
1146 bottomOverShootingShadowView.Opacity = 1.0f;
1147 bottomOverShootingShadowView.RaiseToTop();
1150 // at the beginning, height or width of overshooting shadow is 0, so it is invisible.
1151 isOverShootingShadowShown = false;
1154 private void DragOverShootingShadow(float totalPanDisplacement, float panDisplacement)
1156 if (!EnableOverShootingEffect)
1159 if (totalPanDisplacement > 0) // downwards
1161 // check if reaching at the top / left.
1162 if ((int)finalTargetPosition != 0)
1164 isOverShootingShadowShown = false;
1168 // save start displacement, and re-calculate displacement.
1169 if (!isOverShootingShadowShown)
1171 startShowShadowDisplacement = totalPanDisplacement;
1173 isOverShootingShadowShown = true;
1175 float newDisplacement = (int)totalPanDisplacement < (int)startShowShadowDisplacement ? 0 : totalPanDisplacement - startShowShadowDisplacement;
1177 if (ScrollingDirection == Direction.Horizontal)
1179 // scale limit of height is 60%.
1180 float heightScale = newDisplacement / overShootingShadowScaleHeightLimit;
1181 leftOverShootingShadowView.SizeHeight = heightScale > 0.6f ? SizeHeight * 0.4f : SizeHeight * (1.0f - heightScale);
1183 // scale limit of width is 300%.
1184 leftOverShootingShadowView.SizeWidth = newDisplacement > overShootingShadowScaleHeightLimit ? overShootingShadowScaleHeightLimit : newDisplacement;
1187 ScrollOutOfBoundEventArgs.Direction scrollDirection = panDisplacement > 0 ?
1188 ScrollOutOfBoundEventArgs.Direction.Right : ScrollOutOfBoundEventArgs.Direction.Left;
1189 OnScrollOutOfBound(scrollDirection, totalPanDisplacement);
1193 // scale limit of width is 60%.
1194 float widthScale = newDisplacement / overShootingShadowScaleHeightLimit;
1195 topOverShootingShadowView.SizeWidth = widthScale > 0.6f ? SizeWidth * 0.4f : SizeWidth * (1.0f - widthScale);
1197 // scale limit of height is 300%.
1198 topOverShootingShadowView.SizeHeight = newDisplacement > overShootingShadowScaleHeightLimit ? overShootingShadowScaleHeightLimit : newDisplacement;
1201 ScrollOutOfBoundEventArgs.Direction scrollDirection = panDisplacement > 0 ?
1202 ScrollOutOfBoundEventArgs.Direction.Down : ScrollOutOfBoundEventArgs.Direction.Up;
1203 OnScrollOutOfBound(scrollDirection, totalPanDisplacement);
1206 else if (totalPanDisplacement < 0) // upwards
1208 // check if reaching at the bottom.
1209 if (-(int)finalTargetPosition != (int)maxScrollDistance)
1211 isOverShootingShadowShown = false;
1215 // save start displacement, and re-calculate displacement.
1216 if (!isOverShootingShadowShown)
1218 startShowShadowDisplacement = totalPanDisplacement;
1220 isOverShootingShadowShown = true;
1222 float newDisplacement = (int)startShowShadowDisplacement < (int)totalPanDisplacement ? 0 : startShowShadowDisplacement - totalPanDisplacement;
1224 if (ScrollingDirection == Direction.Horizontal)
1226 // scale limit of height is 60%.
1227 float heightScale = newDisplacement / overShootingShadowScaleHeightLimit;
1228 rightOverShootingShadowView.SizeHeight = heightScale > 0.6f ? SizeHeight * 0.4f : SizeHeight * (1.0f - heightScale);
1230 // scale limit of width is 300%.
1231 rightOverShootingShadowView.SizeWidth = newDisplacement > overShootingShadowScaleHeightLimit ? overShootingShadowScaleHeightLimit : newDisplacement;
1234 ScrollOutOfBoundEventArgs.Direction scrollDirection = panDisplacement > 0 ?
1235 ScrollOutOfBoundEventArgs.Direction.Right : ScrollOutOfBoundEventArgs.Direction.Left;
1236 OnScrollOutOfBound(scrollDirection, totalPanDisplacement);
1240 // scale limit of width is 60%.
1241 float widthScale = newDisplacement / overShootingShadowScaleHeightLimit;
1242 bottomOverShootingShadowView.SizeWidth = widthScale > 0.6f ? SizeWidth * 0.4f : SizeWidth * (1.0f - widthScale);
1244 // scale limit of height is 300%.
1245 bottomOverShootingShadowView.SizeHeight = newDisplacement > overShootingShadowScaleHeightLimit ? overShootingShadowScaleHeightLimit : newDisplacement;
1248 ScrollOutOfBoundEventArgs.Direction scrollDirection = panDisplacement > 0 ?
1249 ScrollOutOfBoundEventArgs.Direction.Down : ScrollOutOfBoundEventArgs.Direction.Up;
1250 OnScrollOutOfBound(scrollDirection, totalPanDisplacement);
1255 // if total displacement is 0, shadow would become invisible.
1256 isOverShootingShadowShown = false;
1260 private void PlayOverShootingShadowAnimation()
1262 if (!EnableOverShootingEffect)
1265 // stop animation if necessary.
1266 StopOverShootingShadowAnimation();
1268 if (overShootingShadowAnimation == null)
1270 overShootingShadowAnimation = new Animation(overShootingShadowAnimationDuration);
1271 overShootingShadowAnimation.Finished += OnOverShootingShadowAnimationFinished;
1274 if (ScrollingDirection == Direction.Horizontal)
1276 View targetView = totalDisplacementForPan < 0 ? rightOverShootingShadowView : leftOverShootingShadowView;
1277 overShootingShadowAnimation.AnimateTo(targetView, "SizeHeight", SizeHeight);
1278 overShootingShadowAnimation.AnimateTo(targetView, "SizeWidth", 0.0f);
1279 overShootingShadowAnimation.AnimateTo(targetView, "Opacity", 0.0f);
1283 View targetView = totalDisplacementForPan < 0 ? bottomOverShootingShadowView : topOverShootingShadowView;
1284 overShootingShadowAnimation.AnimateTo(targetView, "SizeWidth", SizeWidth);
1285 overShootingShadowAnimation.AnimateTo(targetView, "SizeHeight", 0.0f);
1286 overShootingShadowAnimation.AnimateTo(targetView, "Opacity", 0.0f);
1288 overShootingShadowAnimation.Play();
1291 private void StopOverShootingShadowAnimation()
1293 if (overShootingShadowAnimation == null || overShootingShadowAnimation.State != Animation.States.Playing)
1296 overShootingShadowAnimation.Stop(Animation.EndActions.Cancel);
1297 OnOverShootingShadowAnimationFinished(null, null);
1298 overShootingShadowAnimation.Clear();
1301 private void OnOverShootingShadowAnimationFinished(object sender, EventArgs e)
1303 if (ScrollingDirection == Direction.Horizontal)
1305 base.Remove(leftOverShootingShadowView);
1306 base.Remove(rightOverShootingShadowView);
1308 leftOverShootingShadowView.Size = new Size(0.0f, SizeHeight);
1309 rightOverShootingShadowView.Size = new Size(0.0f, SizeHeight);
1313 base.Remove(topOverShootingShadowView);
1314 base.Remove(bottomOverShootingShadowView);
1316 topOverShootingShadowView.Size = new Size(SizeWidth, 0.0f);
1317 bottomOverShootingShadowView.Size = new Size(SizeWidth, 0.0f);
1320 // after animation finished, height/width & opacity of vertical shadow both are 0, so it is invisible.
1321 isOverShootingShadowShown = false;
1324 private void OnScrollOutOfBound(ScrollOutOfBoundEventArgs.Direction direction, float displacement)
1326 ScrollOutOfBoundEventArgs args = new ScrollOutOfBoundEventArgs(direction, displacement);
1327 ScrollOutOfBound?.Invoke(this, args);
1330 private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
1332 OnPanGesture(e.PanGesture);
1333 if(!((SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing) || e.PanGesture.State == Gesture.StateType.Started))
1335 e.Handled = !((int)finalTargetPosition == 0 || -(int)finalTargetPosition == (int)maxScrollDistance);
1339 private void OnPanGesture(PanGesture panGesture)
1341 if (SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
1346 if (panGesture.State == Gesture.StateType.Started)
1348 readyToNotice = false;
1349 //Interrupt touching when panning is started
1350 this.InterceptTouchEvent += OnInterruptTouchingChildTouched;
1351 AttachOverShootingShadowView();
1352 Debug.WriteLineIf(LayoutDebugScrollableBase, "Gesture Start");
1353 if (scrolling && !SnapToPage)
1357 totalDisplacementForPan = 0.0f;
1358 OnScrollDragStarted();
1360 else if (panGesture.State == Gesture.StateType.Continuing)
1362 if (ScrollingDirection == Direction.Horizontal)
1364 // if vertical shadow is shown, does not scroll.
1365 if (!isOverShootingShadowShown)
1367 ScrollBy(panGesture.Displacement.X, false);
1369 totalDisplacementForPan += panGesture.Displacement.X;
1370 DragOverShootingShadow(totalDisplacementForPan, panGesture.Displacement.X);
1374 // if vertical shadow is shown, does not scroll.
1375 if (!isOverShootingShadowShown)
1377 ScrollBy(panGesture.Displacement.Y, false);
1379 totalDisplacementForPan += panGesture.Displacement.Y;
1380 DragOverShootingShadow(totalDisplacementForPan, panGesture.Displacement.Y);
1382 Debug.WriteLineIf(LayoutDebugScrollableBase, "OnPanGestureDetected Continue totalDisplacementForPan:" + totalDisplacementForPan);
1385 else if (panGesture.State == Gesture.StateType.Finished || panGesture.State == Gesture.StateType.Cancelled)
1387 PlayOverShootingShadowAnimation();
1388 OnScrollDragEnded();
1389 StopScroll(); // Will replace previous animation so will stop existing one.
1391 if (scrollAnimation == null)
1393 scrollAnimation = new Animation();
1394 scrollAnimation.Finished += ScrollAnimationFinished;
1397 float panVelocity = (ScrollingDirection == Direction.Horizontal) ? panGesture.Velocity.X : panGesture.Velocity.Y;
1401 PageSnap(panVelocity);
1405 if (panVelocity == 0)
1407 float currentScrollPosition = (ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
1408 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Linear);
1409 scrollAnimation.Duration = 0;
1410 scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", currentScrollPosition);
1411 scrollAnimation.Play();
1415 Decelerating(panVelocity, scrollAnimation);
1419 totalDisplacementForPan = 0;
1421 readyToNotice = true;
1422 OnScrollAnimationStarted();
1426 internal void BaseRemove(View view)
1431 internal override bool OnAccessibilityPan(PanGesture gestures)
1433 if (SnapToPage && scrollAnimation != null && scrollAnimation.State == Animation.States.Playing)
1438 OnPanGesture(gestures);
1442 private float CustomScrollAlphaFunction(float progress)
1444 if (panAnimationDelta == 0)
1450 // Parameter "progress" is normalized value. We need to multiply target duration to calculate distance.
1451 // Can get real distance using equation of deceleration (check Decelerating function)
1452 // After get real distance, normalize it
1453 float realDuration = progress * panAnimationDuration;
1454 float realDistance = velocityOfLastPan * ((float)Math.Pow(decelerationRate, realDuration) - 1) / logValueOfDeceleration;
1455 float result = Math.Min(realDistance / Math.Abs(panAnimationDelta), 1.0f);
1461 /// you can override it to custom your decelerating
1463 /// <param name="velocity">Velocity of current pan.</param>
1464 /// <param name="animation">Scroll animation.</param>
1465 [EditorBrowsable(EditorBrowsableState.Never)]
1466 protected virtual void Decelerating(float velocity, Animation animation)
1468 // Decelerating using deceleration equation ===========
1470 // V : velocity (pixel per millisecond)
1471 // V0 : initial velocity
1472 // d : deceleration rate,
1474 // X : final position after decelerating
1475 // log : natural logarithm
1477 // V(t) = V0 * d pow t;
1478 // X(t) = V0 * (d pow t - 1) / log d; <-- Integrate the velocity function
1479 // X(∞) = V0 * d / (1 - d); <-- Result using infinite T can be final position because T is tending to infinity.
1481 // Because of final T is tending to infinity, we should use threshold value to finish.
1482 // Final T = log(-threshold * log d / |V0| ) / log d;
1484 velocityOfLastPan = Math.Abs(velocity);
1486 float currentScrollPosition = -(ScrollingDirection == Direction.Horizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y);
1487 panAnimationDelta = (velocityOfLastPan * decelerationRate) / (1 - decelerationRate);
1488 panAnimationDelta = velocity > 0 ? -panAnimationDelta : panAnimationDelta;
1490 float destination = -(panAnimationDelta + currentScrollPosition);
1491 float adjustDestination = AdjustTargetPositionOfScrollAnimation(destination);
1492 float maxPosition = ScrollAvailableArea != null ? ScrollAvailableArea.Y : maxScrollDistance;
1493 float minPosition = ScrollAvailableArea != null ? ScrollAvailableArea.X : 0;
1495 if (destination < -maxPosition || destination > minPosition)
1497 panAnimationDelta = velocity > 0 ? (currentScrollPosition - minPosition) : (maxPosition - currentScrollPosition);
1498 destination = velocity > 0 ? minPosition : -maxPosition;
1500 if (panAnimationDelta == 0)
1502 panAnimationDuration = 0.0f;
1506 panAnimationDuration = (float)Math.Log((panAnimationDelta * logValueOfDeceleration / velocityOfLastPan + 1), decelerationRate);
1509 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1510 "OverRange======================= \n" +
1511 "[decelerationRate] " + decelerationRate + "\n" +
1512 "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1513 "[Velocity] " + velocityOfLastPan + "\n" +
1514 "[CurrentPosition] " + currentScrollPosition + "\n" +
1515 "[CandidateDelta] " + panAnimationDelta + "\n" +
1516 "[Destination] " + destination + "\n" +
1517 "[Duration] " + panAnimationDuration + "\n" +
1518 "================================ \n"
1523 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1525 if (adjustDestination != destination)
1527 destination = adjustDestination;
1528 panAnimationDelta = destination + currentScrollPosition;
1529 velocityOfLastPan = Math.Abs(panAnimationDelta * logValueOfDeceleration / ((float)Math.Pow(decelerationRate, panAnimationDuration) - 1));
1530 panAnimationDuration = (float)Math.Log(-DecelerationThreshold * logValueOfDeceleration / velocityOfLastPan) / logValueOfDeceleration;
1533 Debug.WriteLineIf(LayoutDebugScrollableBase, "\n" +
1534 "================================ \n" +
1535 "[decelerationRate] " + decelerationRate + "\n" +
1536 "[logValueOfDeceleration] " + logValueOfDeceleration + "\n" +
1537 "[Velocity] " + velocityOfLastPan + "\n" +
1538 "[CurrentPosition] " + currentScrollPosition + "\n" +
1539 "[CandidateDelta] " + panAnimationDelta + "\n" +
1540 "[Destination] " + destination + "\n" +
1541 "[Duration] " + panAnimationDuration + "\n" +
1542 "================================ \n"
1546 finalTargetPosition = destination;
1548 customScrollAlphaFunction = new UserAlphaFunctionDelegate(CustomScrollAlphaFunction);
1549 animation.DefaultAlphaFunction = new AlphaFunction(customScrollAlphaFunction);
1550 GC.KeepAlive(customScrollAlphaFunction);
1551 animation.Duration = (int)panAnimationDuration;
1552 animation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", destination);
1556 private void ScrollAnimationFinished(object sender, EventArgs e)
1558 OnScrollAnimationEnded();
1562 /// Adjust scrolling position by own scrolling rules.
1563 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1565 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
1566 [EditorBrowsable(EditorBrowsableState.Never)]
1567 protected virtual float AdjustTargetPositionOfScrollAnimation(float position)
1573 /// Scroll position given to ScrollTo.
1574 /// This is the position in the opposite direction to the position of ContentContainer.
1576 /// <since_tizen> 8 </since_tizen>
1577 public Position ScrollPosition
1581 return new Position(-ContentContainer.Position);
1586 /// Current scroll position in the middle of ScrollTo animation.
1587 /// This is the position in the opposite direction to the current position of ContentContainer.
1589 /// <since_tizen> 8 </since_tizen>
1590 public Position ScrollCurrentPosition
1594 return new Position(-ContentContainer.CurrentPosition);
1599 /// Remove all children in ContentContainer.
1601 /// <param name="dispose">If true, removed child is disposed.</param>
1602 [EditorBrowsable(EditorBrowsableState.Never)]
1603 public void RemoveAllChildren(bool dispose = false)
1605 RecursiveRemoveChildren(ContentContainer, dispose);
1608 private void RecursiveRemoveChildren(View parent, bool dispose)
1614 int maxChild = (int)parent.GetChildCount();
1615 for (int i = maxChild - 1; i >= 0; --i)
1617 View child = parent.GetChildAt((uint)i);
1622 RecursiveRemoveChildren(child, dispose);
1623 parent.Remove(child);
1633 [EditorBrowsable(EditorBrowsableState.Never)]
1634 public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
1636 View nextFocusedView = null;
1638 int currentIndex = ContentContainer.Children.IndexOf(currentFocusedView);
1642 case View.FocusDirection.Left:
1643 case View.FocusDirection.Up:
1645 if (currentIndex > 0)
1647 nextFocusedView = ContentContainer.Children[--currentIndex];
1651 case View.FocusDirection.Right:
1652 case View.FocusDirection.Down:
1654 if (currentIndex < ContentContainer.Children.Count - 1)
1656 nextFocusedView = ContentContainer.Children[++currentIndex];
1662 if (nextFocusedView != null)
1664 // Check next focused view is inside of visible area.
1665 // If it is not, move scroll position to make it visible.
1666 Position scrollPosition = ContentContainer.CurrentPosition;
1667 float targetPosition = -(ScrollingDirection == Direction.Horizontal ? scrollPosition.X : scrollPosition.Y);
1669 float left = nextFocusedView.Position.X;
1670 float right = nextFocusedView.Position.X + nextFocusedView.Size.Width;
1671 float top = nextFocusedView.Position.Y;
1672 float bottom = nextFocusedView.Position.Y + nextFocusedView.Size.Height;
1674 float visibleRectangleLeft = -scrollPosition.X;
1675 float visibleRectangleRight = -scrollPosition.X + Size.Width;
1676 float visibleRectangleTop = -scrollPosition.Y;
1677 float visibleRectangleBottom = -scrollPosition.Y + Size.Height;
1679 if (ScrollingDirection == Direction.Horizontal)
1681 if (left < visibleRectangleLeft)
1683 targetPosition = left;
1685 else if (right > visibleRectangleRight)
1687 targetPosition = right - Size.Width;
1692 if (top < visibleRectangleTop)
1694 targetPosition = top;
1696 else if (bottom > visibleRectangleBottom)
1698 targetPosition = bottom - Size.Height;
1701 ScrollTo(targetPosition, true);
1704 return nextFocusedView;