1 /* Copyright (c) 2020 Samsung Electronics Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
17 using Tizen.NUI.BaseComponents;
18 using System.Collections.Generic;
19 using System.ComponentModel;
20 using System.Diagnostics;
22 namespace Tizen.NUI.Components
25 /// [Draft] This class provides a View that can scroll a single View with a layout. This View can be a nest of Views.
27 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
28 [EditorBrowsable(EditorBrowsableState.Never)]
29 public class ScrollableBase : Control
31 static bool LayoutDebugScrollableBase = false; // Debug flag
32 private Direction mScrollingDirection = Direction.Vertical;
33 private bool mScrollEnabled = true;
34 private int mPageWidth = 0;
36 private class ScrollableBaseCustomLayout : LayoutGroup
38 protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
40 Extents padding = Padding;
41 float totalHeight = padding.Top + padding.Bottom;
42 float totalWidth = padding.Start + padding.End;
44 MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK;
45 MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK;
47 Direction scrollingDirection = Direction.Vertical;
48 ScrollableBase scrollableBase = this.Owner as ScrollableBase;
51 scrollingDirection = scrollableBase.ScrollingDirection;
54 // measure child, should be a single scrolling child
55 foreach (LayoutItem childLayout in LayoutChildren)
57 if (childLayout != null)
60 // Use an Unspecified MeasureSpecification mode so scrolling child is not restricted to it's parents size in Height (for vertical scrolling)
61 // or Width for horizontal scrolling
62 MeasureSpecification unrestrictedMeasureSpec = new MeasureSpecification(heightMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
64 if (scrollingDirection == Direction.Vertical)
66 MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), unrestrictedMeasureSpec, new LayoutLength(0)); // Height unrestricted by parent
70 MeasureChildWithMargins(childLayout, unrestrictedMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0)); // Width unrestricted by parent
73 float childWidth = childLayout.MeasuredWidth.Size.AsDecimal();
74 float childHeight = childLayout.MeasuredHeight.Size.AsDecimal();
76 // Determine the width and height needed by the children using their given position and size.
77 // Children could overlap so find the left most and right most child.
78 Position2D childPosition = childLayout.Owner.Position2D;
79 float childLeft = childPosition.X;
80 float childTop = childPosition.Y;
82 // Store current width and height needed to contain all children.
83 Extents childMargin = childLayout.Margin;
84 totalWidth = childWidth + childMargin.Start + childMargin.End;
85 totalHeight = childHeight + childMargin.Top + childMargin.Bottom;
87 if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
89 childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
91 if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
93 childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
99 MeasuredSize widthSizeAndState = ResolveSizeAndState(new LayoutLength(totalWidth + Padding.Start + Padding.End), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
100 MeasuredSize heightSizeAndState = ResolveSizeAndState(new LayoutLength(totalHeight + Padding.Top + Padding.Bottom), heightMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
101 totalWidth = widthSizeAndState.Size.AsDecimal();
102 totalHeight = heightSizeAndState.Size.AsDecimal();
104 // Ensure layout respects it's given minimum size
105 totalWidth = Math.Max(totalWidth, SuggestedMinimumWidth.AsDecimal());
106 totalHeight = Math.Max(totalHeight, SuggestedMinimumHeight.AsDecimal());
108 widthSizeAndState.State = childWidthState;
109 heightSizeAndState.State = childHeightState;
111 SetMeasuredDimensions(ResolveSizeAndState(new LayoutLength(totalWidth + Padding.Start + Padding.End), widthMeasureSpec, childWidthState),
112 ResolveSizeAndState(new LayoutLength(totalHeight + Padding.Top + Padding.Bottom), heightMeasureSpec, childHeightState));
114 // Size of ScrollableBase is changed. Change Page width too.
115 scrollableBase.mPageWidth = (int)MeasuredWidth.Size.AsRoundedValue();
118 protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
120 foreach (LayoutItem childLayout in LayoutChildren)
122 if (childLayout != null)
124 LayoutLength childWidth = childLayout.MeasuredWidth.Size;
125 LayoutLength childHeight = childLayout.MeasuredHeight.Size;
127 Position2D childPosition = childLayout.Owner.Position2D;
128 Extents padding = Padding;
129 Extents childMargin = childLayout.Margin;
131 LayoutLength childLeft = new LayoutLength(childPosition.X + childMargin.Start + padding.Start);
132 LayoutLength childTop = new LayoutLength(childPosition.Y + childMargin.Top + padding.Top);
134 childLayout.Layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
138 } // ScrollableBaseCustomLayout
141 /// The direction axis to scroll.
143 /// <since_tizen> 6 </since_tizen>
144 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
145 [EditorBrowsable(EditorBrowsableState.Never)]
146 public enum Direction
151 /// <since_tizen> 6 </since_tizen>
157 /// <since_tizen> 6 </since_tizen>
162 /// [Draft] Configurable speed threshold that register the gestures as a flick.
163 /// If the flick speed less than the threshold then will not be considered a flick.
165 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
166 [EditorBrowsable(EditorBrowsableState.Never)]
167 public float FlickThreshold { get; set; } = 0.2f;
170 /// [Draft] Configurable duration modifer for the flick animation.
171 /// Determines the speed of the scroll, large value results in a longer flick animation. Range (0.1 - 1.0)
173 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
174 [EditorBrowsable(EditorBrowsableState.Never)]
175 public float FlickAnimationSpeed { get; set; } = 0.4f;
178 /// [Draft] Configurable modifer for the distance to be scrolled when flicked detected.
179 /// It a ratio of the ScrollableBase's length. (not child's length).
180 /// First value is the ratio of the distance to scroll with the weakest flick.
181 /// Second value is the ratio of the distance to scroll with the strongest flick.
184 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
185 [EditorBrowsable(EditorBrowsableState.Never)]
186 public Vector2 FlickDistanceMultiplierRange { get; set; } = new Vector2(0.6f, 1.8f);
189 /// [Draft] Scrolling direction mode.
190 /// Default is Vertical scrolling.
192 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
193 [EditorBrowsable(EditorBrowsableState.Never)]
194 public Direction ScrollingDirection
198 return mScrollingDirection;
202 if (value != mScrollingDirection)
204 mScrollingDirection = value;
205 mPanGestureDetector.RemoveDirection(value == Direction.Horizontal ?
206 PanGestureDetector.DirectionVertical : PanGestureDetector.DirectionHorizontal);
207 mPanGestureDetector.AddDirection(value == Direction.Horizontal ?
208 PanGestureDetector.DirectionHorizontal : PanGestureDetector.DirectionVertical);
210 ContentContainer.WidthSpecification = mScrollingDirection == Direction.Vertical ?
211 LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent;
212 ContentContainer.HeightSpecification = mScrollingDirection == Direction.Vertical ?
213 LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent;
219 /// [Draft] Enable or disable scrolling.
221 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
222 [EditorBrowsable(EditorBrowsableState.Never)]
223 public bool ScrollEnabled
227 return mScrollEnabled;
231 if (value != mScrollEnabled)
233 mScrollEnabled = value;
236 mPanGestureDetector.Detected += OnPanGestureDetected;
237 mTapGestureDetector.Detected += OnTapGestureDetected;
241 mPanGestureDetector.Detected -= OnPanGestureDetected;
242 mTapGestureDetector.Detected -= OnTapGestureDetected;
249 /// [Draft] Pages mode, enables moving to the next or return to current page depending on pan displacement.
250 /// Default is false.
252 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
253 [EditorBrowsable(EditorBrowsableState.Never)]
254 public bool SnapToPage { set; get; } = false;
257 /// [Draft] Get current page.
258 /// Working propery with SnapToPage property.
260 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
261 [EditorBrowsable(EditorBrowsableState.Never)]
262 public int CurrentPage { get; private set; } = 0;
265 /// [Draft] Duration of scroll animation.
267 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
268 [EditorBrowsable(EditorBrowsableState.Never)]
270 public int ScrollDuration { set; get; } = 125;
272 /// [Draft] Scroll Available area.
274 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
275 [EditorBrowsable(EditorBrowsableState.Never)]
276 public Vector2 ScrollAvailableArea { set; get; }
279 /// ScrollEventArgs is a class to record scroll event arguments which will sent to user.
281 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
282 [EditorBrowsable(EditorBrowsableState.Never)]
283 public class ScrollEventArgs : EventArgs
288 /// Default constructor.
290 /// <param name="position">Current scroll position</param>
291 /// <since_tizen> 6 </since_tizen>
292 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
293 public ScrollEventArgs(Position position)
295 this.position = position;
299 /// [Draft] Current scroll position.
301 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
302 [EditorBrowsable(EditorBrowsableState.Never)]
303 public Position Position
313 /// An event emitted when user starts dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
315 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
316 [EditorBrowsable(EditorBrowsableState.Never)]
317 public event EventHandler<ScrollEventArgs> ScrollDragStartEvent;
320 /// An event emitted when user stops dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
322 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
323 [EditorBrowsable(EditorBrowsableState.Never)]
324 public event EventHandler<ScrollEventArgs> ScrollDragEndEvent;
328 /// An event emitted when the scrolling slide animation starts, user can subscribe or unsubscribe to this event handler.<br />
330 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
331 [EditorBrowsable(EditorBrowsableState.Never)]
332 public event EventHandler<ScrollEventArgs> ScrollAnimationStartEvent;
335 /// An event emitted when the scrolling slide animation ends, user can subscribe or unsubscribe to this event handler.<br />
337 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
338 [EditorBrowsable(EditorBrowsableState.Never)]
339 public event EventHandler<ScrollEventArgs> ScrollAnimationEndEvent;
343 /// An event emitted when scrolling, user can subscribe or unsubscribe to this event handler.<br />
345 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
346 [EditorBrowsable(EditorBrowsableState.Never)]
347 public event EventHandler<ScrollEventArgs> ScrollEvent;
351 /// Scrollbar for ScrollableBase.<br />
353 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
354 [EditorBrowsable(EditorBrowsableState.Never)]
355 public ScrollbarBase Scrollbar
365 scrollBar.Unparent();
369 scrollBar.Name = "ScrollBar";
386 /// [Draft] Always hide Scrollbar.
388 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
389 [EditorBrowsable(EditorBrowsableState.Never)]
390 public bool HideScrollBar
394 return hideScrollbar;
398 hideScrollbar = value;
414 private bool hideScrollbar = true;
415 private Animation scrollAnimation;
416 private float maxScrollDistance;
417 private float childTargetPosition = 0.0f;
418 private PanGestureDetector mPanGestureDetector;
419 private TapGestureDetector mTapGestureDetector;
420 private View mInterruptTouchingChild;
421 private ScrollbarBase scrollBar;
422 private float multiplier = 1.0f;
423 private bool scrolling = false;
424 private float ratioOfScreenWidthToCompleteScroll = 0.5f;
425 private float totalDisplacementForPan = 0.0f;
426 private Size previousContainerSize = new Size();
428 // If false then can only flick pages when the current animation/scroll as ended.
429 private bool flickWhenAnimating = false;
430 private PropertyNotification propertyNotification;
432 // Let's consider more whether this needs to be set as protected.
433 private float finalTargetPosition;
436 /// [Draft] Constructor
438 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
439 [EditorBrowsable(EditorBrowsableState.Never)]
440 public ScrollableBase() : base()
442 base.Layout = new ScrollableBaseCustomLayout();
443 mPanGestureDetector = new PanGestureDetector();
444 mPanGestureDetector.Attach(this);
445 mPanGestureDetector.AddDirection(PanGestureDetector.DirectionVertical);
446 mPanGestureDetector.Detected += OnPanGestureDetected;
448 mTapGestureDetector = new TapGestureDetector();
449 mTapGestureDetector.Attach(this);
450 mTapGestureDetector.Detected += OnTapGestureDetected;
452 ClippingMode = ClippingModeType.ClipChildren;
454 //Default Scrolling child
455 ContentContainer = new View()
457 WidthSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent,
458 HeightSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent,
459 Layout = new AbsoluteLayout(){SetPositionByLayout = false},
461 ContentContainer.Relayout += OnScrollingChildRelayout;
462 propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(1.0f));
463 propertyNotification.Notified += OnPropertyChanged;
464 base.Add(ContentContainer);
466 //Interrupt touching when panning is started
467 mInterruptTouchingChild = new View()
469 Size = new Size(Window.Instance.WindowSize),
470 BackgroundColor = Color.Transparent,
472 mInterruptTouchingChild.TouchEvent += OnIterruptTouchingChildTouched;
474 Scrollbar = new Scrollbar();
478 /// Container which has content of ScrollableBase.
480 [EditorBrowsable(EditorBrowsableState.Never)]
481 public View ContentContainer { get; private set; }
484 /// Set the layout on this View. Replaces any existing Layout.
486 public new LayoutItem Layout
490 return ContentContainer.Layout;
494 ContentContainer.Layout = value;
495 if(ContentContainer.Layout != null)
497 ContentContainer.Layout.SetPositionByLayout = false;
503 /// List of children of Container.
505 public new List<View> Children
509 return ContentContainer.Children;
513 private bool OnIterruptTouchingChildTouched(object source, View.TouchEventArgs args)
518 private void OnPropertyChanged(object source, PropertyNotification.NotifyEventArgs args)
524 /// Called after a child has been added to the owning view.
526 /// <param name="view">The child which has been added.</param>
527 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
528 [EditorBrowsable(EditorBrowsableState.Never)]
529 public override void Add(View view)
531 ContentContainer.Add(view);
535 /// Called after a child has been removed from the owning view.
537 /// <param name="view">The child which has been removed.</param>
538 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
539 [EditorBrowsable(EditorBrowsableState.Never)]
540 public override void Remove(View view)
542 if(SnapToPage && CurrentPage == Children.IndexOf(view) && CurrentPage == Children.Count -1)
544 // Target View is current page and also last child.
545 // CurrentPage should be changed to previous page.
546 CurrentPage = Math.Max(0, CurrentPage-1);
547 ScrollToIndex(CurrentPage);
550 ContentContainer.Remove(view);
553 private void OnScrollingChildRelayout(object source, EventArgs args)
555 // Size is changed. Calculate maxScrollDistance.
556 bool isSizeChanged = previousContainerSize.Width != ContentContainer.Size.Width || previousContainerSize.Height != ContentContainer.Size.Height;
560 maxScrollDistance = CalculateMaximumScrollDistance();
564 previousContainerSize = ContentContainer.Size;
568 /// The composition of a Scrollbar can vary depending on how you use ScrollableBase.
569 /// Set the composition that will go into the ScrollableBase according to your ScrollableBase.
571 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
572 [EditorBrowsable(EditorBrowsableState.Never)]
573 protected virtual void SetScrollbar()
577 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
578 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
579 float viewportLength = isHorizontal ? Size.Width : Size.Height;
580 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
581 Scrollbar.Initialize(contentLength, viewportLength, currentPosition, isHorizontal);
586 /// Scrolls to the item at the specified index.
588 /// <param name="index">Index of item.</param>
589 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
590 [EditorBrowsable(EditorBrowsableState.Never)]
591 public void ScrollToIndex(int index)
593 if (ContentContainer.ChildCount - 1 < index || index < 0)
603 float targetPosition = Math.Min(ScrollingDirection == Direction.Vertical ? Children[index].Position.Y : Children[index].Position.X, maxScrollDistance);
604 AnimateChildTo(ScrollDuration, -targetPosition);
607 private void OnScrollDragStart()
609 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
610 ScrollDragStartEvent?.Invoke(this, eventArgs);
613 private void OnScrollDragEnd()
615 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
616 ScrollDragEndEvent?.Invoke(this, eventArgs);
619 private void OnScrollAnimationStart()
621 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
622 ScrollAnimationStartEvent?.Invoke(this, eventArgs);
625 private void OnScrollAnimationEnd()
627 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
628 ScrollAnimationEndEvent?.Invoke(this, eventArgs);
631 private bool readyToNotice = false;
633 private float noticeAnimationEndBeforePosition = 0.0f;
634 // Let's consider more whether this needs to be set as protected.
635 public float NoticeAnimationEndBeforePosition { get => noticeAnimationEndBeforePosition; set => noticeAnimationEndBeforePosition = value; }
637 private void OnScroll()
639 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
640 ScrollEvent?.Invoke(this, eventArgs);
642 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
643 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
644 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
646 scrollBar.Update(contentLength, Math.Abs(currentPosition));
647 CheckPreReachedTargetPosition();
650 private void CheckPreReachedTargetPosition()
652 // Check whether we reached pre-reached target position
654 ContentContainer.CurrentPosition.Y <= finalTargetPosition + NoticeAnimationEndBeforePosition &&
655 ContentContainer.CurrentPosition.Y >= finalTargetPosition - NoticeAnimationEndBeforePosition)
658 readyToNotice = false;
659 OnPreReachedTargetPosition(finalTargetPosition);
664 /// This helps developer who wants to know before scroll is reaching target position.
666 /// <param name="targetPosition">Index of item.</param>
667 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
668 [EditorBrowsable(EditorBrowsableState.Never)]
669 protected virtual void OnPreReachedTargetPosition(float targetPosition)
674 private void StopScroll()
676 if (scrollAnimation != null)
678 if (scrollAnimation.State == Animation.States.Playing)
680 Debug.WriteLineIf(LayoutDebugScrollableBase, "StopScroll Animation Playing");
681 scrollAnimation.Stop(Animation.EndActions.Cancel);
682 OnScrollAnimationEnd();
684 scrollAnimation.Clear();
688 // static constructor registers the control type
689 static ScrollableBase()
691 // ViewRegistry registers control type with DALi type registry
692 // also uses introspection to find any properties that need to be registered with type registry
693 CustomViewRegistry.Instance.Register(CreateInstance, typeof(ScrollableBase));
696 internal static CustomView CreateInstance()
698 return new ScrollableBase();
701 private void AnimateChildTo(int duration, float axisPosition)
703 Debug.WriteLineIf(LayoutDebugScrollableBase, "AnimationTo Animation Duration:" + duration + " Destination:" + axisPosition);
704 finalTargetPosition = axisPosition;
706 StopScroll(); // Will replace previous animation so will stop existing one.
708 if (scrollAnimation == null)
710 scrollAnimation = new Animation();
711 scrollAnimation.Finished += ScrollAnimationFinished;
714 scrollAnimation.Duration = duration;
715 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSine);
716 scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", axisPosition);
718 OnScrollAnimationStart();
719 scrollAnimation.Play();
723 /// Scroll to specific position with or without animation.
725 /// <param name="position">Destination.</param>
726 /// <param name="animate">Scroll with or without animation</param>
727 [EditorBrowsable(EditorBrowsableState.Never)]
728 public void ScrollTo(float position, bool animate)
730 float currentPositionX = ContentContainer.CurrentPosition.X != 0 ? ContentContainer.CurrentPosition.X : ContentContainer.Position.X;
731 float currentPositionY = ContentContainer.CurrentPosition.Y != 0 ? ContentContainer.CurrentPosition.Y : ContentContainer.Position.Y;
732 float delta = ScrollingDirection == Direction.Horizontal ? currentPositionX : currentPositionY;
733 // The argument position is the new pan position. So the new position of ScrollableBase becomes (-position).
734 // To move ScrollableBase's position to (-position), it moves by (-position - currentPosition).
735 delta = -position - delta;
737 ScrollBy(delta, animate);
740 private float BoundScrollPosition(float targetPosition)
742 if (ScrollAvailableArea != null)
744 float minScrollPosition = ScrollAvailableArea.X;
745 float maxScrollPosition = ScrollAvailableArea.Y;
747 targetPosition = Math.Min(-minScrollPosition, targetPosition);
748 targetPosition = Math.Max(-maxScrollPosition, targetPosition);
752 targetPosition = Math.Min(0, targetPosition);
753 targetPosition = Math.Max(-maxScrollDistance, targetPosition);
756 return targetPosition;
759 private void ScrollBy(float displacement, bool animate)
761 if (GetChildCount() == 0 || maxScrollDistance < 0)
766 float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? ContentContainer.PositionX : ContentContainer.PositionY;
768 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy childCurrentPosition:" + childCurrentPosition +
769 " displacement:" + displacement,
770 " maxScrollDistance:" + maxScrollDistance);
772 childTargetPosition = childCurrentPosition + displacement; // child current position + gesture displacement
775 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition);
779 // Calculate scroll animaton duration
780 float scrollDistance = Math.Abs(displacement);
781 int duration = (int)((320 * FlickAnimationSpeed) + (scrollDistance * FlickAnimationSpeed));
782 Debug.WriteLineIf(LayoutDebugScrollableBase, "Scroll Animation Duration:" + duration + " Distance:" + scrollDistance);
784 readyToNotice = true;
786 AnimateChildTo(duration, BoundScrollPosition(AdjustTargetPositionOfScrollAnimation(BoundScrollPosition(childTargetPosition))));
790 finalTargetPosition = BoundScrollPosition(childTargetPosition);
792 // Set position of scrolling child without an animation
793 if (ScrollingDirection == Direction.Horizontal)
795 ContentContainer.PositionX = finalTargetPosition;
799 ContentContainer.PositionY = finalTargetPosition;
806 /// you can override it to clean-up your own resources.
808 /// <param name="type">DisposeTypes</param>
809 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
810 [EditorBrowsable(EditorBrowsableState.Never)]
811 protected override void Dispose(DisposeTypes type)
818 if (type == DisposeTypes.Explicit)
822 if (mPanGestureDetector != null)
824 mPanGestureDetector.Detected -= OnPanGestureDetected;
825 mPanGestureDetector.Dispose();
826 mPanGestureDetector = null;
829 if (mTapGestureDetector != null)
831 mTapGestureDetector.Detected -= OnTapGestureDetected;
832 mTapGestureDetector.Dispose();
833 mTapGestureDetector = null;
839 private float CalculateDisplacementFromVelocity(float axisVelocity)
841 // Map: flick speed of range (2.0 - 6.0) to flick multiplier of range (0.7 - 1.6)
842 float speedMinimum = FlickThreshold;
843 float speedMaximum = FlickThreshold + 6.0f;
844 float multiplierMinimum = FlickDistanceMultiplierRange.X;
845 float multiplierMaximum = FlickDistanceMultiplierRange.Y;
847 float flickDisplacement = 0.0f;
849 float speed = Math.Min(4.0f, Math.Abs(axisVelocity));
851 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollableBase Candidate Flick speed:" + speed);
853 if (speed > FlickThreshold)
855 // Flick length is the length of the ScrollableBase.
856 float flickLength = (ScrollingDirection == Direction.Horizontal) ? CurrentSize.Width : CurrentSize.Height;
858 // Calculate multiplier by mapping speed between the multiplier minimum and maximum.
859 multiplier = ((speed - speedMinimum) / ((speedMaximum - speedMinimum) * (multiplierMaximum - multiplierMinimum))) + multiplierMinimum;
861 // flick displacement is the product of the flick length and multiplier
862 flickDisplacement = ((flickLength * multiplier) * speed) / axisVelocity; // *speed and /velocity to perserve sign.
864 Debug.WriteLineIf(LayoutDebugScrollableBase, "Calculated FlickDisplacement[" + flickDisplacement + "] from speed[" + speed + "] multiplier:"
867 return flickDisplacement;
870 private float CalculateMaximumScrollDistance()
872 float scrollingChildLength = 0;
873 float scrollerLength = 0;
874 if (ScrollingDirection == Direction.Horizontal)
876 Debug.WriteLineIf(LayoutDebugScrollableBase, "Horizontal");
878 scrollingChildLength = ContentContainer.Size.Width;
879 scrollerLength = Size.Width;
883 Debug.WriteLineIf(LayoutDebugScrollableBase, "Vertical");
884 scrollingChildLength = ContentContainer.Size.Height;
885 scrollerLength = Size.Height;
888 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy maxScrollDistance:" + (scrollingChildLength - scrollerLength) +
889 " parent length:" + scrollerLength +
890 " scrolling child length:" + scrollingChildLength);
892 return Math.Max(scrollingChildLength - scrollerLength, 0);
895 private void PageSnap()
897 Debug.WriteLineIf(LayoutDebugScrollableBase, "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan +
898 " currentPage[" + CurrentPage + "]");
900 //Increment current page if total displacement enough to warrant a page change.
901 if (Math.Abs(totalDisplacementForPan) > (mPageWidth * ratioOfScreenWidthToCompleteScroll))
903 if (totalDisplacementForPan < 0)
905 CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
909 CurrentPage = Math.Max(0, --CurrentPage);
913 // Animate to new page or reposition to current page
914 float destinationX = -(Children[CurrentPage].Position.X + Children[CurrentPage].CurrentSize.Width / 2 - CurrentSize.Width / 2); // set to middle of current page
915 Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to page[" + CurrentPage + "] to:" + destinationX + " from:" + ContentContainer.PositionX);
916 AnimateChildTo(ScrollDuration, destinationX);
919 private void Flick(float flickDisplacement)
921 if (SnapToPage && Children.Count > 0)
923 if ((flickWhenAnimating && scrolling == true) || (scrolling == false))
925 if (flickDisplacement < 0)
927 CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), CurrentPage + 1);
928 Debug.WriteLineIf(LayoutDebugScrollableBase, "Snap - to page:" + CurrentPage);
932 CurrentPage = Math.Max(0, CurrentPage - 1);
933 Debug.WriteLineIf(LayoutDebugScrollableBase, "Snap + to page:" + CurrentPage);
936 float destinationX = -(Children[CurrentPage].Position.X + Children[CurrentPage].CurrentSize.Width / 2.0f - CurrentSize.Width / 2.0f); // set to middle of current page
937 Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to :" + destinationX);
938 AnimateChildTo(ScrollDuration, destinationX);
943 ScrollBy(flickDisplacement, true); // Animate flickDisplacement.
947 private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
949 if (e.PanGesture.State == Gesture.StateType.Started)
951 base.Add(mInterruptTouchingChild);
952 Debug.WriteLineIf(LayoutDebugScrollableBase, "Gesture Start");
953 if (scrolling && !SnapToPage)
957 totalDisplacementForPan = 0.0f;
960 else if (e.PanGesture.State == Gesture.StateType.Continuing)
962 if (ScrollingDirection == Direction.Horizontal)
964 ScrollBy(e.PanGesture.Displacement.X, false);
965 totalDisplacementForPan += e.PanGesture.Displacement.X;
969 ScrollBy(e.PanGesture.Displacement.Y, false);
970 totalDisplacementForPan += e.PanGesture.Displacement.Y;
972 Debug.WriteLineIf(LayoutDebugScrollableBase, "OnPanGestureDetected Continue totalDisplacementForPan:" + totalDisplacementForPan);
974 else if (e.PanGesture.State == Gesture.StateType.Finished)
976 float axisVelocity = (ScrollingDirection == Direction.Horizontal) ? e.PanGesture.Velocity.X : e.PanGesture.Velocity.Y;
977 float flickDisplacement = CalculateDisplacementFromVelocity(axisVelocity);
979 Debug.WriteLineIf(LayoutDebugScrollableBase, "FlickDisplacement:" + flickDisplacement + "TotalDisplacementForPan:" + totalDisplacementForPan);
982 if (flickDisplacement > 0 | flickDisplacement < 0)// Flick detected
984 Flick(flickDisplacement);
988 // End of panning gesture but was not a flick
989 if (SnapToPage && Children.Count > 0)
998 totalDisplacementForPan = 0;
1000 base.Remove(mInterruptTouchingChild);
1004 private new void OnTapGestureDetected(object source, TapGestureDetector.DetectedEventArgs e)
1006 if (e.TapGesture.Type == Gesture.GestureType.Tap)
1008 // Stop scrolling if tap detected (press then relase).
1009 // Unless in Pages mode, do not want a page change to stop part way.
1010 if (scrolling && !SnapToPage)
1017 private void ScrollAnimationFinished(object sender, EventArgs e)
1020 CheckPreReachedTargetPosition();
1021 OnScrollAnimationEnd();
1025 /// Adjust scrolling position by own scrolling rules.
1026 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1028 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
1029 [EditorBrowsable(EditorBrowsableState.Never)]
1030 protected virtual float AdjustTargetPositionOfScrollAnimation(float position)