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 /// ScrollEventArgs is a class to record scroll event arguments which will sent to user.
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 ScrollEventArgs : EventArgs
34 /// Default constructor.
36 /// <param name="position">Current scroll position</param>
37 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
38 public ScrollEventArgs(Position position)
40 this.position = position;
44 /// [Draft] Current scroll position.
46 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
47 [EditorBrowsable(EditorBrowsableState.Never)]
48 public Position Position
58 /// [Draft] This class provides a View that can scroll a single View with a layout. This View can be a nest of Views.
60 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
61 [EditorBrowsable(EditorBrowsableState.Never)]
62 public class ScrollableBase : Control
64 static bool LayoutDebugScrollableBase = false; // Debug flag
65 private Direction mScrollingDirection = Direction.Vertical;
66 private bool mScrollEnabled = true;
67 private int mPageWidth = 0;
69 private class ScrollableBaseCustomLayout : LayoutGroup
71 protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
73 Extents padding = Padding;
74 float totalHeight = padding.Top + padding.Bottom;
75 float totalWidth = padding.Start + padding.End;
77 MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK;
78 MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK;
80 Direction scrollingDirection = Direction.Vertical;
81 ScrollableBase scrollableBase = this.Owner as ScrollableBase;
84 scrollingDirection = scrollableBase.ScrollingDirection;
87 // measure child, should be a single scrolling child
88 foreach (LayoutItem childLayout in LayoutChildren)
90 if (childLayout != null)
93 // Use an Unspecified MeasureSpecification mode so scrolling child is not restricted to it's parents size in Height (for vertical scrolling)
94 // or Width for horizontal scrolling
95 MeasureSpecification unrestrictedMeasureSpec = new MeasureSpecification(heightMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
97 if (scrollingDirection == Direction.Vertical)
99 MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), unrestrictedMeasureSpec, new LayoutLength(0)); // Height unrestricted by parent
103 MeasureChildWithMargins(childLayout, unrestrictedMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0)); // Width unrestricted by parent
106 float childWidth = childLayout.MeasuredWidth.Size.AsDecimal();
107 float childHeight = childLayout.MeasuredHeight.Size.AsDecimal();
109 // Determine the width and height needed by the children using their given position and size.
110 // Children could overlap so find the left most and right most child.
111 Position2D childPosition = childLayout.Owner.Position2D;
112 float childLeft = childPosition.X;
113 float childTop = childPosition.Y;
115 // Store current width and height needed to contain all children.
116 Extents childMargin = childLayout.Margin;
117 totalWidth = childWidth + childMargin.Start + childMargin.End;
118 totalHeight = childHeight + childMargin.Top + childMargin.Bottom;
120 if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
122 childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
124 if (childLayout.MeasuredHeight.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
126 childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
132 MeasuredSize widthSizeAndState = ResolveSizeAndState(new LayoutLength(totalWidth + Padding.Start + Padding.End), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
133 MeasuredSize heightSizeAndState = ResolveSizeAndState(new LayoutLength(totalHeight + Padding.Top + Padding.Bottom), heightMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
134 totalWidth = widthSizeAndState.Size.AsDecimal();
135 totalHeight = heightSizeAndState.Size.AsDecimal();
137 // Ensure layout respects it's given minimum size
138 totalWidth = Math.Max(totalWidth, SuggestedMinimumWidth.AsDecimal());
139 totalHeight = Math.Max(totalHeight, SuggestedMinimumHeight.AsDecimal());
141 widthSizeAndState.State = childWidthState;
142 heightSizeAndState.State = childHeightState;
144 SetMeasuredDimensions(ResolveSizeAndState(new LayoutLength(totalWidth + Padding.Start + Padding.End), widthMeasureSpec, childWidthState),
145 ResolveSizeAndState(new LayoutLength(totalHeight + Padding.Top + Padding.Bottom), heightMeasureSpec, childHeightState));
147 // Size of ScrollableBase is changed. Change Page width too.
148 scrollableBase.mPageWidth = (int)MeasuredWidth.Size.AsRoundedValue();
151 protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
153 foreach (LayoutItem childLayout in LayoutChildren)
155 if (childLayout != null)
157 LayoutLength childWidth = childLayout.MeasuredWidth.Size;
158 LayoutLength childHeight = childLayout.MeasuredHeight.Size;
160 Position2D childPosition = childLayout.Owner.Position2D;
161 Extents padding = Padding;
162 Extents childMargin = childLayout.Margin;
164 LayoutLength childLeft = new LayoutLength(childPosition.X + childMargin.Start + padding.Start);
165 LayoutLength childTop = new LayoutLength(childPosition.Y + childMargin.Top + padding.Top);
167 childLayout.Layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
171 } // ScrollableBaseCustomLayout
174 /// The direction axis to scroll.
176 /// <since_tizen> 6 </since_tizen>
177 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
178 [EditorBrowsable(EditorBrowsableState.Never)]
179 public enum Direction
184 /// <since_tizen> 6 </since_tizen>
190 /// <since_tizen> 6 </since_tizen>
195 /// [Draft] Configurable speed threshold that register the gestures as a flick.
196 /// If the flick speed less than the threshold then will not be considered a flick.
198 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
199 [EditorBrowsable(EditorBrowsableState.Never)]
200 public float FlickThreshold { get; set; } = 0.2f;
203 /// [Draft] Configurable duration modifer for the flick animation.
204 /// Determines the speed of the scroll, large value results in a longer flick animation. Range (0.1 - 1.0)
206 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
207 [EditorBrowsable(EditorBrowsableState.Never)]
208 public float FlickAnimationSpeed { get; set; } = 0.4f;
211 /// [Draft] Configurable modifer for the distance to be scrolled when flicked detected.
212 /// It a ratio of the ScrollableBase's length. (not child's length).
213 /// First value is the ratio of the distance to scroll with the weakest flick.
214 /// Second value is the ratio of the distance to scroll with the strongest flick.
217 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
218 [EditorBrowsable(EditorBrowsableState.Never)]
219 public Vector2 FlickDistanceMultiplierRange { get; set; } = new Vector2(0.6f, 1.8f);
222 /// [Draft] Scrolling direction mode.
223 /// Default is Vertical scrolling.
225 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
226 [EditorBrowsable(EditorBrowsableState.Never)]
227 public Direction ScrollingDirection
231 return mScrollingDirection;
235 if (value != mScrollingDirection)
237 mScrollingDirection = value;
238 mPanGestureDetector.RemoveDirection(value == Direction.Horizontal ?
239 PanGestureDetector.DirectionVertical : PanGestureDetector.DirectionHorizontal);
240 mPanGestureDetector.AddDirection(value == Direction.Horizontal ?
241 PanGestureDetector.DirectionHorizontal : PanGestureDetector.DirectionVertical);
243 ContentContainer.WidthSpecification = mScrollingDirection == Direction.Vertical ?
244 LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent;
245 ContentContainer.HeightSpecification = mScrollingDirection == Direction.Vertical ?
246 LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent;
252 /// [Draft] Enable or disable scrolling.
254 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
255 [EditorBrowsable(EditorBrowsableState.Never)]
256 public bool ScrollEnabled
260 return mScrollEnabled;
264 if (value != mScrollEnabled)
266 mScrollEnabled = value;
269 mPanGestureDetector.Detected += OnPanGestureDetected;
270 mTapGestureDetector.Detected += OnTapGestureDetected;
274 mPanGestureDetector.Detected -= OnPanGestureDetected;
275 mTapGestureDetector.Detected -= OnTapGestureDetected;
282 /// [Draft] Pages mode, enables moving to the next or return to current page depending on pan displacement.
283 /// Default is false.
285 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
286 [EditorBrowsable(EditorBrowsableState.Never)]
287 public bool SnapToPage { set; get; } = false;
290 /// [Draft] Get current page.
291 /// Working propery with SnapToPage property.
293 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
294 [EditorBrowsable(EditorBrowsableState.Never)]
295 public int CurrentPage { get; private set; } = 0;
298 /// [Draft] Duration of scroll animation.
300 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
301 [EditorBrowsable(EditorBrowsableState.Never)]
303 public int ScrollDuration { set; get; } = 125;
305 /// [Draft] Scroll Available area.
307 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
308 [EditorBrowsable(EditorBrowsableState.Never)]
309 public Vector2 ScrollAvailableArea { set; get; }
312 /// An event emitted when user starts dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
314 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
315 [EditorBrowsable(EditorBrowsableState.Never)]
316 public event EventHandler<ScrollEventArgs> ScrollDragStarted;
319 /// An event emitted when user stops dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
321 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
322 [EditorBrowsable(EditorBrowsableState.Never)]
323 public event EventHandler<ScrollEventArgs> ScrollDragEnded;
327 /// An event emitted when the scrolling slide animation starts, user can subscribe or unsubscribe to this event handler.<br />
329 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
330 [EditorBrowsable(EditorBrowsableState.Never)]
331 public event EventHandler<ScrollEventArgs> ScrollAnimationStarted;
334 /// An event emitted when the scrolling slide animation ends, user can subscribe or unsubscribe to this event handler.<br />
336 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
337 [EditorBrowsable(EditorBrowsableState.Never)]
338 public event EventHandler<ScrollEventArgs> ScrollAnimationEnded;
342 /// An event emitted when scrolling, user can subscribe or unsubscribe to this event handler.<br />
344 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
345 [EditorBrowsable(EditorBrowsableState.Never)]
346 public event EventHandler<ScrollEventArgs> Scrolling;
350 /// Scrollbar for ScrollableBase.<br />
352 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
353 [EditorBrowsable(EditorBrowsableState.Never)]
354 public ScrollbarBase Scrollbar
364 scrollBar.Unparent();
368 scrollBar.Name = "ScrollBar";
385 /// [Draft] Always hide Scrollbar.
387 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
388 [EditorBrowsable(EditorBrowsableState.Never)]
389 public bool HideScrollBar
393 return hideScrollbar;
397 hideScrollbar = value;
413 private bool hideScrollbar = true;
414 private Animation scrollAnimation;
415 private float maxScrollDistance;
416 private float childTargetPosition = 0.0f;
417 private PanGestureDetector mPanGestureDetector;
418 private TapGestureDetector mTapGestureDetector;
419 private View mInterruptTouchingChild;
420 private ScrollbarBase scrollBar;
421 private float multiplier = 1.0f;
422 private bool scrolling = false;
423 private float ratioOfScreenWidthToCompleteScroll = 0.5f;
424 private float totalDisplacementForPan = 0.0f;
425 private Size previousContainerSize = new Size();
427 // If false then can only flick pages when the current animation/scroll as ended.
428 private bool flickWhenAnimating = false;
429 private PropertyNotification propertyNotification;
431 // Let's consider more whether this needs to be set as protected.
432 private float finalTargetPosition;
435 /// [Draft] Constructor
437 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
438 [EditorBrowsable(EditorBrowsableState.Never)]
439 public ScrollableBase() : base()
441 base.Layout = new ScrollableBaseCustomLayout();
442 mPanGestureDetector = new PanGestureDetector();
443 mPanGestureDetector.Attach(this);
444 mPanGestureDetector.AddDirection(PanGestureDetector.DirectionVertical);
445 mPanGestureDetector.Detected += OnPanGestureDetected;
447 mTapGestureDetector = new TapGestureDetector();
448 mTapGestureDetector.Attach(this);
449 mTapGestureDetector.Detected += OnTapGestureDetected;
451 ClippingMode = ClippingModeType.ClipChildren;
453 //Default Scrolling child
454 ContentContainer = new View()
456 WidthSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.MatchParent : LayoutParamPolicies.WrapContent,
457 HeightSpecification = ScrollingDirection == Direction.Vertical ? LayoutParamPolicies.WrapContent : LayoutParamPolicies.MatchParent,
458 Layout = new AbsoluteLayout(){SetPositionByLayout = false},
460 ContentContainer.Relayout += OnScrollingChildRelayout;
461 propertyNotification = ContentContainer.AddPropertyNotification("position", PropertyCondition.Step(1.0f));
462 propertyNotification.Notified += OnPropertyChanged;
463 base.Add(ContentContainer);
465 //Interrupt touching when panning is started
466 mInterruptTouchingChild = new View()
468 Size = new Size(Window.Instance.WindowSize),
469 BackgroundColor = Color.Transparent,
471 mInterruptTouchingChild.TouchEvent += OnIterruptTouchingChildTouched;
473 Scrollbar = new Scrollbar();
477 /// Container which has content of ScrollableBase.
479 [EditorBrowsable(EditorBrowsableState.Never)]
480 public View ContentContainer { get; private set; }
483 /// Set the layout on this View. Replaces any existing Layout.
485 public new LayoutItem Layout
489 return ContentContainer.Layout;
493 ContentContainer.Layout = value;
494 if(ContentContainer.Layout != null)
496 ContentContainer.Layout.SetPositionByLayout = false;
502 /// List of children of Container.
504 public new List<View> Children
508 return ContentContainer.Children;
512 private bool OnIterruptTouchingChildTouched(object source, View.TouchEventArgs args)
517 private void OnPropertyChanged(object source, PropertyNotification.NotifyEventArgs args)
523 /// Called after a child has been added to the owning view.
525 /// <param name="view">The child which has been added.</param>
526 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
527 [EditorBrowsable(EditorBrowsableState.Never)]
528 public override void Add(View view)
530 ContentContainer.Add(view);
534 /// Called after a child has been removed from the owning view.
536 /// <param name="view">The child which has been removed.</param>
537 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
538 [EditorBrowsable(EditorBrowsableState.Never)]
539 public override void Remove(View view)
541 if(SnapToPage && CurrentPage == Children.IndexOf(view) && CurrentPage == Children.Count -1)
543 // Target View is current page and also last child.
544 // CurrentPage should be changed to previous page.
545 CurrentPage = Math.Max(0, CurrentPage-1);
546 ScrollToIndex(CurrentPage);
549 ContentContainer.Remove(view);
552 private void OnScrollingChildRelayout(object source, EventArgs args)
554 // Size is changed. Calculate maxScrollDistance.
555 bool isSizeChanged = previousContainerSize.Width != ContentContainer.Size.Width || previousContainerSize.Height != ContentContainer.Size.Height;
559 maxScrollDistance = CalculateMaximumScrollDistance();
563 previousContainerSize = ContentContainer.Size;
567 /// The composition of a Scrollbar can vary depending on how you use ScrollableBase.
568 /// Set the composition that will go into the ScrollableBase according to your ScrollableBase.
570 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
571 [EditorBrowsable(EditorBrowsableState.Never)]
572 protected virtual void SetScrollbar()
576 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
577 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
578 float viewportLength = isHorizontal ? Size.Width : Size.Height;
579 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
580 Scrollbar.Initialize(contentLength, viewportLength, currentPosition, isHorizontal);
585 /// Scrolls to the item at the specified index.
587 /// <param name="index">Index of item.</param>
588 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
589 [EditorBrowsable(EditorBrowsableState.Never)]
590 public void ScrollToIndex(int index)
592 if (ContentContainer.ChildCount - 1 < index || index < 0)
602 float targetPosition = Math.Min(ScrollingDirection == Direction.Vertical ? Children[index].Position.Y : Children[index].Position.X, maxScrollDistance);
603 AnimateChildTo(ScrollDuration, -targetPosition);
606 private void OnScrollDragStarted()
608 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
609 ScrollDragStarted?.Invoke(this, eventArgs);
612 private void OnScrollDragEnded()
614 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
615 ScrollDragEnded?.Invoke(this, eventArgs);
618 private void OnScrollAnimationStarted()
620 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
621 ScrollAnimationStarted?.Invoke(this, eventArgs);
624 private void OnScrollAnimationEnded()
626 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
627 ScrollAnimationEnded?.Invoke(this, eventArgs);
630 private bool readyToNotice = false;
632 private float noticeAnimationEndBeforePosition = 0.0f;
633 // Let's consider more whether this needs to be set as protected.
634 public float NoticeAnimationEndBeforePosition { get => noticeAnimationEndBeforePosition; set => noticeAnimationEndBeforePosition = value; }
636 private void OnScroll()
638 ScrollEventArgs eventArgs = new ScrollEventArgs(ContentContainer.CurrentPosition);
639 Scrolling?.Invoke(this, eventArgs);
641 bool isHorizontal = ScrollingDirection == Direction.Horizontal;
642 float contentLength = isHorizontal ? ContentContainer.Size.Width : ContentContainer.Size.Height;
643 float currentPosition = isHorizontal ? ContentContainer.CurrentPosition.X : ContentContainer.CurrentPosition.Y;
645 scrollBar.Update(contentLength, Math.Abs(currentPosition));
646 CheckPreReachedTargetPosition();
649 private void CheckPreReachedTargetPosition()
651 // Check whether we reached pre-reached target position
653 ContentContainer.CurrentPosition.Y <= finalTargetPosition + NoticeAnimationEndBeforePosition &&
654 ContentContainer.CurrentPosition.Y >= finalTargetPosition - NoticeAnimationEndBeforePosition)
657 readyToNotice = false;
658 OnPreReachedTargetPosition(finalTargetPosition);
663 /// This helps developer who wants to know before scroll is reaching target position.
665 /// <param name="targetPosition">Index of item.</param>
666 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
667 [EditorBrowsable(EditorBrowsableState.Never)]
668 protected virtual void OnPreReachedTargetPosition(float targetPosition)
673 private void StopScroll()
675 if (scrollAnimation != null)
677 if (scrollAnimation.State == Animation.States.Playing)
679 Debug.WriteLineIf(LayoutDebugScrollableBase, "StopScroll Animation Playing");
680 scrollAnimation.Stop(Animation.EndActions.Cancel);
681 OnScrollAnimationEnded();
683 scrollAnimation.Clear();
687 // static constructor registers the control type
688 static ScrollableBase()
690 // ViewRegistry registers control type with DALi type registry
691 // also uses introspection to find any properties that need to be registered with type registry
692 CustomViewRegistry.Instance.Register(CreateInstance, typeof(ScrollableBase));
695 internal static CustomView CreateInstance()
697 return new ScrollableBase();
700 private void AnimateChildTo(int duration, float axisPosition)
702 Debug.WriteLineIf(LayoutDebugScrollableBase, "AnimationTo Animation Duration:" + duration + " Destination:" + axisPosition);
703 finalTargetPosition = axisPosition;
705 StopScroll(); // Will replace previous animation so will stop existing one.
707 if (scrollAnimation == null)
709 scrollAnimation = new Animation();
710 scrollAnimation.Finished += ScrollAnimationFinished;
713 scrollAnimation.Duration = duration;
714 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSine);
715 scrollAnimation.AnimateTo(ContentContainer, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", axisPosition);
717 OnScrollAnimationStarted();
718 scrollAnimation.Play();
722 /// Scroll to specific position with or without animation.
724 /// <param name="position">Destination.</param>
725 /// <param name="animate">Scroll with or without animation</param>
726 [EditorBrowsable(EditorBrowsableState.Never)]
727 public void ScrollTo(float position, bool animate)
729 float currentPositionX = ContentContainer.CurrentPosition.X != 0 ? ContentContainer.CurrentPosition.X : ContentContainer.Position.X;
730 float currentPositionY = ContentContainer.CurrentPosition.Y != 0 ? ContentContainer.CurrentPosition.Y : ContentContainer.Position.Y;
731 float delta = ScrollingDirection == Direction.Horizontal ? currentPositionX : currentPositionY;
732 // The argument position is the new pan position. So the new position of ScrollableBase becomes (-position).
733 // To move ScrollableBase's position to (-position), it moves by (-position - currentPosition).
734 delta = -position - delta;
736 ScrollBy(delta, animate);
739 private float BoundScrollPosition(float targetPosition)
741 if (ScrollAvailableArea != null)
743 float minScrollPosition = ScrollAvailableArea.X;
744 float maxScrollPosition = ScrollAvailableArea.Y;
746 targetPosition = Math.Min(-minScrollPosition, targetPosition);
747 targetPosition = Math.Max(-maxScrollPosition, targetPosition);
751 targetPosition = Math.Min(0, targetPosition);
752 targetPosition = Math.Max(-maxScrollDistance, targetPosition);
755 return targetPosition;
758 private void ScrollBy(float displacement, bool animate)
760 if (GetChildCount() == 0 || maxScrollDistance < 0)
765 float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? ContentContainer.PositionX : ContentContainer.PositionY;
767 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy childCurrentPosition:" + childCurrentPosition +
768 " displacement:" + displacement,
769 " maxScrollDistance:" + maxScrollDistance);
771 childTargetPosition = childCurrentPosition + displacement; // child current position + gesture displacement
774 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition);
778 // Calculate scroll animaton duration
779 float scrollDistance = Math.Abs(displacement);
780 int duration = (int)((320 * FlickAnimationSpeed) + (scrollDistance * FlickAnimationSpeed));
781 Debug.WriteLineIf(LayoutDebugScrollableBase, "Scroll Animation Duration:" + duration + " Distance:" + scrollDistance);
783 readyToNotice = true;
785 AnimateChildTo(duration, BoundScrollPosition(AdjustTargetPositionOfScrollAnimation(BoundScrollPosition(childTargetPosition))));
789 finalTargetPosition = BoundScrollPosition(childTargetPosition);
791 // Set position of scrolling child without an animation
792 if (ScrollingDirection == Direction.Horizontal)
794 ContentContainer.PositionX = finalTargetPosition;
798 ContentContainer.PositionY = finalTargetPosition;
805 /// you can override it to clean-up your own resources.
807 /// <param name="type">DisposeTypes</param>
808 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
809 [EditorBrowsable(EditorBrowsableState.Never)]
810 protected override void Dispose(DisposeTypes type)
817 if (type == DisposeTypes.Explicit)
821 if (mPanGestureDetector != null)
823 mPanGestureDetector.Detected -= OnPanGestureDetected;
824 mPanGestureDetector.Dispose();
825 mPanGestureDetector = null;
828 if (mTapGestureDetector != null)
830 mTapGestureDetector.Detected -= OnTapGestureDetected;
831 mTapGestureDetector.Dispose();
832 mTapGestureDetector = null;
838 private float CalculateDisplacementFromVelocity(float axisVelocity)
840 // Map: flick speed of range (2.0 - 6.0) to flick multiplier of range (0.7 - 1.6)
841 float speedMinimum = FlickThreshold;
842 float speedMaximum = FlickThreshold + 6.0f;
843 float multiplierMinimum = FlickDistanceMultiplierRange.X;
844 float multiplierMaximum = FlickDistanceMultiplierRange.Y;
846 float flickDisplacement = 0.0f;
848 float speed = Math.Min(4.0f, Math.Abs(axisVelocity));
850 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollableBase Candidate Flick speed:" + speed);
852 if (speed > FlickThreshold)
854 // Flick length is the length of the ScrollableBase.
855 float flickLength = (ScrollingDirection == Direction.Horizontal) ? CurrentSize.Width : CurrentSize.Height;
857 // Calculate multiplier by mapping speed between the multiplier minimum and maximum.
858 multiplier = ((speed - speedMinimum) / ((speedMaximum - speedMinimum) * (multiplierMaximum - multiplierMinimum))) + multiplierMinimum;
860 // flick displacement is the product of the flick length and multiplier
861 flickDisplacement = ((flickLength * multiplier) * speed) / axisVelocity; // *speed and /velocity to perserve sign.
863 Debug.WriteLineIf(LayoutDebugScrollableBase, "Calculated FlickDisplacement[" + flickDisplacement + "] from speed[" + speed + "] multiplier:"
866 return flickDisplacement;
869 private float CalculateMaximumScrollDistance()
871 float scrollingChildLength = 0;
872 float scrollerLength = 0;
873 if (ScrollingDirection == Direction.Horizontal)
875 Debug.WriteLineIf(LayoutDebugScrollableBase, "Horizontal");
877 scrollingChildLength = ContentContainer.Size.Width;
878 scrollerLength = Size.Width;
882 Debug.WriteLineIf(LayoutDebugScrollableBase, "Vertical");
883 scrollingChildLength = ContentContainer.Size.Height;
884 scrollerLength = Size.Height;
887 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy maxScrollDistance:" + (scrollingChildLength - scrollerLength) +
888 " parent length:" + scrollerLength +
889 " scrolling child length:" + scrollingChildLength);
891 return Math.Max(scrollingChildLength - scrollerLength, 0);
894 private void PageSnap()
896 Debug.WriteLineIf(LayoutDebugScrollableBase, "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan +
897 " currentPage[" + CurrentPage + "]");
899 //Increment current page if total displacement enough to warrant a page change.
900 if (Math.Abs(totalDisplacementForPan) > (mPageWidth * ratioOfScreenWidthToCompleteScroll))
902 if (totalDisplacementForPan < 0)
904 CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), ++CurrentPage);
908 CurrentPage = Math.Max(0, --CurrentPage);
912 // Animate to new page or reposition to current page
913 float destinationX = -(Children[CurrentPage].Position.X + Children[CurrentPage].CurrentSize.Width / 2 - CurrentSize.Width / 2); // set to middle of current page
914 Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to page[" + CurrentPage + "] to:" + destinationX + " from:" + ContentContainer.PositionX);
915 AnimateChildTo(ScrollDuration, destinationX);
918 private void Flick(float flickDisplacement)
920 if (SnapToPage && Children.Count > 0)
922 if ((flickWhenAnimating && scrolling == true) || (scrolling == false))
924 if (flickDisplacement < 0)
926 CurrentPage = Math.Min(Math.Max(Children.Count - 1, 0), CurrentPage + 1);
927 Debug.WriteLineIf(LayoutDebugScrollableBase, "Snap - to page:" + CurrentPage);
931 CurrentPage = Math.Max(0, CurrentPage - 1);
932 Debug.WriteLineIf(LayoutDebugScrollableBase, "Snap + to page:" + CurrentPage);
935 float destinationX = -(Children[CurrentPage].Position.X + Children[CurrentPage].CurrentSize.Width / 2.0f - CurrentSize.Width / 2.0f); // set to middle of current page
936 Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to :" + destinationX);
937 AnimateChildTo(ScrollDuration, destinationX);
942 ScrollBy(flickDisplacement, true); // Animate flickDisplacement.
946 private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
948 if (e.PanGesture.State == Gesture.StateType.Started)
950 base.Add(mInterruptTouchingChild);
951 Debug.WriteLineIf(LayoutDebugScrollableBase, "Gesture Start");
952 if (scrolling && !SnapToPage)
956 totalDisplacementForPan = 0.0f;
957 OnScrollDragStarted();
959 else if (e.PanGesture.State == Gesture.StateType.Continuing)
961 if (ScrollingDirection == Direction.Horizontal)
963 ScrollBy(e.PanGesture.Displacement.X, false);
964 totalDisplacementForPan += e.PanGesture.Displacement.X;
968 ScrollBy(e.PanGesture.Displacement.Y, false);
969 totalDisplacementForPan += e.PanGesture.Displacement.Y;
971 Debug.WriteLineIf(LayoutDebugScrollableBase, "OnPanGestureDetected Continue totalDisplacementForPan:" + totalDisplacementForPan);
973 else if (e.PanGesture.State == Gesture.StateType.Finished)
975 float axisVelocity = (ScrollingDirection == Direction.Horizontal) ? e.PanGesture.Velocity.X : e.PanGesture.Velocity.Y;
976 float flickDisplacement = CalculateDisplacementFromVelocity(axisVelocity);
978 Debug.WriteLineIf(LayoutDebugScrollableBase, "FlickDisplacement:" + flickDisplacement + "TotalDisplacementForPan:" + totalDisplacementForPan);
981 if (flickDisplacement > 0 | flickDisplacement < 0)// Flick detected
983 Flick(flickDisplacement);
987 // End of panning gesture but was not a flick
988 if (SnapToPage && Children.Count > 0)
997 totalDisplacementForPan = 0;
999 base.Remove(mInterruptTouchingChild);
1003 private new void OnTapGestureDetected(object source, TapGestureDetector.DetectedEventArgs e)
1005 if (e.TapGesture.Type == Gesture.GestureType.Tap)
1007 // Stop scrolling if tap detected (press then relase).
1008 // Unless in Pages mode, do not want a page change to stop part way.
1009 if (scrolling && !SnapToPage)
1016 private void ScrollAnimationFinished(object sender, EventArgs e)
1019 CheckPreReachedTargetPosition();
1020 OnScrollAnimationEnded();
1024 /// Adjust scrolling position by own scrolling rules.
1025 /// Override this function when developer wants to change destination of flicking.(e.g. always snap to center of item)
1027 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
1028 [EditorBrowsable(EditorBrowsableState.Never)]
1029 protected virtual float AdjustTargetPositionOfScrollAnimation(float position)