1 /* Copyright (c) 2019 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.ComponentModel;
19 using System.Diagnostics;
20 namespace Tizen.NUI.Components
23 /// [Draft] This class provides a View that can scroll a single View with a layout. This View can be a nest of Views.
25 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
26 [EditorBrowsable(EditorBrowsableState.Never)]
27 public class ScrollableBase : Control
29 static bool LayoutDebugScrollableBase = false; // Debug flag
30 private Direction mScrollingDirection = Direction.Vertical;
31 private bool mScrollEnabled = true;
32 private int mPageWidth = 0;
34 private class ScrollableBaseCustomLayout : LayoutGroup
36 protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
38 Extents padding = Padding;
39 float totalHeight = padding.Top + padding.Bottom;
40 float totalWidth = padding.Start + padding.End;
42 MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK;
43 MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK;
45 Direction scrollingDirection = Direction.Vertical;
46 ScrollableBase scrollableBase = this.Owner as ScrollableBase;
49 scrollingDirection = scrollableBase.ScrollingDirection;
52 // measure child, should be a single scrolling child
53 foreach( LayoutItem childLayout in LayoutChildren )
55 if (childLayout != null)
58 // Use an Unspecified MeasureSpecification mode so scrolling child is not restricted to it's parents size in Height (for vertical scrolling)
59 // or Width for horizontal scrolling
60 MeasureSpecification unrestrictedMeasureSpec = new MeasureSpecification( heightMeasureSpec.Size, MeasureSpecification.ModeType.Unspecified);
62 if (scrollingDirection == Direction.Vertical)
64 MeasureChild( childLayout, widthMeasureSpec, unrestrictedMeasureSpec ); // Height unrestricted by parent
68 MeasureChild( childLayout, unrestrictedMeasureSpec, heightMeasureSpec ); // Width unrestricted by parent
71 float childWidth = childLayout.MeasuredWidth.Size.AsDecimal();
72 float childHeight = childLayout.MeasuredHeight.Size.AsDecimal();
74 // Determine the width and height needed by the children using their given position and size.
75 // Children could overlap so find the left most and right most child.
76 Position2D childPosition = childLayout.Owner.Position2D;
77 float childLeft = childPosition.X;
78 float childTop = childPosition.Y;
80 // Store current width and height needed to contain all children.
81 Extents childMargin = childLayout.Margin;
82 totalWidth = childWidth + childMargin.Start + childMargin.End;
83 totalHeight = childHeight + childMargin.Top + childMargin.Bottom;
85 if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
87 childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
89 if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
91 childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
97 MeasuredSize widthSizeAndState = ResolveSizeAndState(new LayoutLength(totalWidth), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
98 MeasuredSize heightSizeAndState = ResolveSizeAndState(new LayoutLength(totalHeight), heightMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
99 totalWidth = widthSizeAndState.Size.AsDecimal();
100 totalHeight = heightSizeAndState.Size.AsDecimal();
102 // Ensure layout respects it's given minimum size
103 totalWidth = Math.Max( totalWidth, SuggestedMinimumWidth.AsDecimal() );
104 totalHeight = Math.Max( totalHeight, SuggestedMinimumHeight.AsDecimal() );
106 widthSizeAndState.State = childWidthState;
107 heightSizeAndState.State = childHeightState;
109 SetMeasuredDimensions( ResolveSizeAndState( new LayoutLength(totalWidth), widthMeasureSpec, childWidthState ),
110 ResolveSizeAndState( new LayoutLength(totalHeight), heightMeasureSpec, childHeightState ) );
112 // Size of ScrollableBase is changed. Change Page width too.
113 scrollableBase.mPageWidth = (int)MeasuredWidth.Size.AsRoundedValue();
116 protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
118 foreach( LayoutItem childLayout in LayoutChildren )
120 if( childLayout != null )
122 LayoutLength childWidth = childLayout.MeasuredWidth.Size;
123 LayoutLength childHeight = childLayout.MeasuredHeight.Size;
125 Position2D childPosition = childLayout.Owner.Position2D;
126 Extents padding = Padding;
127 Extents childMargin = childLayout.Margin;
129 LayoutLength childLeft = new LayoutLength(childPosition.X + childMargin.Start + padding.Start);
130 LayoutLength childTop = new LayoutLength(childPosition.Y + childMargin.Top + padding.Top);
132 childLayout.Layout( childLeft, childTop, childLeft + childWidth, childTop + childHeight );
136 } // ScrollableBaseCustomLayout
139 /// The direction axis to scroll.
141 /// <since_tizen> 6 </since_tizen>
142 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
143 [EditorBrowsable(EditorBrowsableState.Never)]
144 public enum Direction
149 /// <since_tizen> 6 </since_tizen>
155 /// <since_tizen> 6 </since_tizen>
160 /// [Draft] Configurable speed threshold that register the gestures as a flick.
161 /// If the flick speed less than the threshold then will not be considered a flick.
163 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
164 [EditorBrowsable(EditorBrowsableState.Never)]
165 public float FlickThreshold { get; set; } = 0.2f;
168 /// [Draft] Configurable duration modifer for the flick animation.
169 /// Determines the speed of the scroll, large value results in a longer flick animation. Range (0.1 - 1.0)
171 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
172 [EditorBrowsable(EditorBrowsableState.Never)]
173 public float FlickAnimationSpeed { get; set; } = 0.4f;
176 /// [Draft] Configurable modifer for the distance to be scrolled when flicked detected.
177 /// It a ratio of the ScrollableBase's length. (not child's length).
178 /// First value is the ratio of the distance to scroll with the weakest flick.
179 /// Second value is the ratio of the distance to scroll with the strongest flick.
182 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
183 [EditorBrowsable(EditorBrowsableState.Never)]
184 public Vector2 FlickDistanceMultiplierRange { get; set; } = new Vector2(0.6f, 1.8f);
187 /// [Draft] Scrolling direction mode.
188 /// Default is Vertical scrolling.
190 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
191 [EditorBrowsable(EditorBrowsableState.Never)]
192 public Direction ScrollingDirection
196 return mScrollingDirection;
200 if(value != mScrollingDirection)
202 mScrollingDirection = value;
203 mPanGestureDetector.RemoveDirection(value == Direction.Horizontal ? PanGestureDetector.DirectionVertical : PanGestureDetector.DirectionHorizontal);
204 mPanGestureDetector.AddDirection(value == Direction.Horizontal ? PanGestureDetector.DirectionHorizontal : PanGestureDetector.DirectionVertical);
210 /// [Draft] Enable or disable scrolling.
212 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
213 [EditorBrowsable(EditorBrowsableState.Never)]
214 public bool ScrollEnabled
218 return mScrollEnabled;
222 if (value != mScrollEnabled)
224 mScrollEnabled = value;
227 mPanGestureDetector.Detected += OnPanGestureDetected;
228 mTapGestureDetector.Detected += OnTapGestureDetected;
232 mPanGestureDetector.Detected -= OnPanGestureDetected;
233 mTapGestureDetector.Detected -= OnTapGestureDetected;
240 /// [Draft] Pages mode, enables moving to the next or return to current page depending on pan displacement.
241 /// Default is false.
243 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
244 [EditorBrowsable(EditorBrowsableState.Never)]
245 public bool SnapToPage { set; get; } = false;
248 /// [Draft] Get current page.
249 /// Working propery with SnapToPage property.
251 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
252 [EditorBrowsable(EditorBrowsableState.Never)]
253 public int CurrentPage { get; private set; } = 0;
256 /// [Draft] Duration of scroll animation.
258 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
259 [EditorBrowsable(EditorBrowsableState.Never)]
260 public int ScrollDuration { set; get; } = 125;
263 /// ScrollEventArgs is a class to record scroll event arguments which will sent to user.
265 /// <since_tizen> 6 </since_tizen>
266 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
267 [EditorBrowsable(EditorBrowsableState.Never)]
268 public class ScrollEventArgs : EventArgs
273 /// An event emitted when user starts dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
275 /// <since_tizen> 6 </since_tizen>
276 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
277 [EditorBrowsable(EditorBrowsableState.Never)]
278 public event EventHandler<ScrollEventArgs> ScrollDragStartEvent;
281 /// An event emitted when user stops dragging ScrollableBase, user can subscribe or unsubscribe to this event handler.<br />
283 /// <since_tizen> 6 </since_tizen>
284 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
285 [EditorBrowsable(EditorBrowsableState.Never)]
286 public event EventHandler<ScrollEventArgs> ScrollDragEndEvent;
290 /// An event emitted when the scrolling slide animation starts, user can subscribe or unsubscribe to this event handler.<br />
292 /// <since_tizen> 6 </since_tizen>
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 event EventHandler<ScrollEventArgs> ScrollAnimationStartEvent;
298 /// An event emitted when the scrolling slide animation ends, user can subscribe or unsubscribe to this event handler.<br />
300 /// <since_tizen> 6 </since_tizen>
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 event EventHandler<ScrollEventArgs> ScrollAnimationEndEvent;
307 private Animation scrollAnimation;
308 private float maxScrollDistance;
309 private float childTargetPosition = 0.0f;
310 private PanGestureDetector mPanGestureDetector;
311 private TapGestureDetector mTapGestureDetector;
312 private View mScrollingChild;
313 private float multiplier =1.0f;
314 private bool scrolling = false;
315 private float ratioOfScreenWidthToCompleteScroll = 0.5f;
316 private float totalDisplacementForPan = 0.0f;
318 // If false then can only flick pages when the current animation/scroll as ended.
319 private bool flickWhenAnimating = false;
322 /// [Draft] Constructor
324 /// <since_tizen> 6 </since_tizen>
325 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
326 [EditorBrowsable(EditorBrowsableState.Never)]
327 public ScrollableBase() : base()
329 mPanGestureDetector = new PanGestureDetector();
330 mPanGestureDetector.Attach(this);
331 mPanGestureDetector.AddDirection(PanGestureDetector.DirectionVertical);
332 mPanGestureDetector.Detected += OnPanGestureDetected;
334 mTapGestureDetector = new TapGestureDetector();
335 mTapGestureDetector.Attach(this);
336 mTapGestureDetector.Detected += OnTapGestureDetected;
338 ClippingMode = ClippingModeType.ClipToBoundingBox;
340 mScrollingChild = new View();
342 Layout = new ScrollableBaseCustomLayout();
346 /// Called after a child has been added to the owning view.
348 /// <param name="view">The child which has been added.</param>
349 /// <since_tizen> 6 </since_tizen>
350 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
351 [EditorBrowsable(EditorBrowsableState.Never)]
352 public override void OnChildAdd(View view)
354 mScrollingChild = view;
356 if (Children.Count > 1)
357 Log.Error("ScrollableBase", $"Only 1 child should be added to ScrollableBase.");
362 /// Called after a child has been removed from the owning view.
364 /// <param name="view">The child which has been removed.</param>
365 /// <since_tizen> 6 </since_tizen>
366 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
367 [EditorBrowsable(EditorBrowsableState.Never)]
368 public override void OnChildRemove(View view)
370 mScrollingChild = new View();
375 /// Scrolls to the item at the specified index.
377 /// <param name="index">Index of item.</param>
378 /// <since_tizen> 6 </since_tizen>
379 /// This may be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API
380 [EditorBrowsable(EditorBrowsableState.Never)]
381 public void ScrollToIndex(int index)
383 if(mScrollingChild.ChildCount-1 < index || index < 0)
393 maxScrollDistance = CalculateMaximumScrollDistance();
395 float targetPosition = Math.Min(ScrollingDirection == Direction.Vertical ? mScrollingChild.Children[index].Position.Y : mScrollingChild.Children[index].Position.X, maxScrollDistance);
396 AnimateChildTo(ScrollDuration, -targetPosition);
399 private void OnScrollDragStart()
401 ScrollEventArgs eventArgs = new ScrollEventArgs();
402 ScrollDragStartEvent?.Invoke(this, eventArgs);
405 private void OnScrollDragEnd()
407 ScrollEventArgs eventArgs = new ScrollEventArgs();
408 ScrollDragEndEvent?.Invoke(this, eventArgs);
411 private void OnScrollAnimationStart()
413 ScrollEventArgs eventArgs = new ScrollEventArgs();
414 ScrollAnimationStartEvent?.Invoke(this, eventArgs);
417 private void OnScrollAnimationEnd()
419 ScrollEventArgs eventArgs = new ScrollEventArgs();
420 ScrollAnimationEndEvent?.Invoke(this, eventArgs);
423 private void StopScroll()
425 if (scrollAnimation != null)
427 if (scrollAnimation.State == Animation.States.Playing)
429 Debug.WriteLineIf(LayoutDebugScrollableBase, "StopScroll Animation Playing");
430 scrollAnimation.Stop(Animation.EndActions.Cancel);
431 OnScrollAnimationEnd();
433 scrollAnimation.Clear();
437 // static constructor registers the control type
438 static ScrollableBase()
440 // ViewRegistry registers control type with DALi type registry
441 // also uses introspection to find any properties that need to be registered with type registry
442 CustomViewRegistry.Instance.Register(CreateInstance, typeof(ScrollableBase));
445 internal static CustomView CreateInstance()
447 return new ScrollableBase();
450 private void AnimateChildTo(int duration, float axisPosition)
452 Debug.WriteLineIf(LayoutDebugScrollableBase, "AnimationTo Animation Duration:" + duration + " Destination:" + axisPosition);
454 StopScroll(); // Will replace previous animation so will stop existing one.
456 if (scrollAnimation == null)
458 scrollAnimation = new Animation();
459 scrollAnimation.Finished += ScrollAnimationFinished;
462 scrollAnimation.Duration = duration;
463 scrollAnimation.DefaultAlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseOutSine);
464 scrollAnimation.AnimateTo(mScrollingChild, (ScrollingDirection == Direction.Horizontal) ? "PositionX" : "PositionY", axisPosition);
466 OnScrollAnimationStart();
467 scrollAnimation.Play();
470 private void ScrollBy(float displacement, bool animate)
472 if (GetChildCount() == 0 || displacement == 0 || maxScrollDistance < 0)
477 float childCurrentPosition = (ScrollingDirection == Direction.Horizontal) ? mScrollingChild.PositionX: mScrollingChild.PositionY;
479 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy childCurrentPosition:" + childCurrentPosition +
480 " displacement:" + displacement,
481 " maxScrollDistance:" + maxScrollDistance );
483 childTargetPosition = childCurrentPosition + displacement; // child current position + gesture displacement
484 childTargetPosition = Math.Min(0,childTargetPosition);
485 childTargetPosition = Math.Max(-maxScrollDistance,childTargetPosition);
487 Debug.WriteLineIf( LayoutDebugScrollableBase, "ScrollBy currentAxisPosition:" + childCurrentPosition + "childTargetPosition:" + childTargetPosition);
491 // Calculate scroll animaton duration
492 float scrollDistance = 0.0f;
493 if (childCurrentPosition < childTargetPosition)
495 scrollDistance = Math.Abs(childCurrentPosition + childTargetPosition);
499 scrollDistance = Math.Abs(childCurrentPosition - childTargetPosition);
502 int duration = (int)((320*FlickAnimationSpeed) + (scrollDistance * FlickAnimationSpeed));
503 Debug.WriteLineIf(LayoutDebugScrollableBase, "Scroll Animation Duration:" + duration + " Distance:" + scrollDistance);
505 AnimateChildTo(duration, childTargetPosition);
509 // Set position of scrolling child without an animation
510 if (ScrollingDirection == Direction.Horizontal)
512 mScrollingChild.PositionX = childTargetPosition;
516 mScrollingChild.PositionY = childTargetPosition;
522 /// you can override it to clean-up your own resources.
524 /// <param name="type">DisposeTypes</param>
525 /// <since_tizen> 6 </since_tizen>
526 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API.
527 [EditorBrowsable(EditorBrowsableState.Never)]
528 protected override void Dispose(DisposeTypes type)
535 if (type == DisposeTypes.Explicit)
539 if (mPanGestureDetector != null)
541 mPanGestureDetector.Detected -= OnPanGestureDetected;
542 mPanGestureDetector.Dispose();
543 mPanGestureDetector = null;
546 if (mTapGestureDetector != null)
548 mTapGestureDetector.Detected -= OnTapGestureDetected;
549 mTapGestureDetector.Dispose();
550 mTapGestureDetector = null;
556 private float CalculateDisplacementFromVelocity(float axisVelocity)
558 // Map: flick speed of range (2.0 - 6.0) to flick multiplier of range (0.7 - 1.6)
559 float speedMinimum = FlickThreshold;
560 float speedMaximum = FlickThreshold + 6.0f;
561 float multiplierMinimum = FlickDistanceMultiplierRange.X;
562 float multiplierMaximum = FlickDistanceMultiplierRange.Y;
564 float flickDisplacement = 0.0f;
566 float speed = Math.Min(4.0f,Math.Abs(axisVelocity));
568 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollableBase Candidate Flick speed:" + speed);
570 if (speed > FlickThreshold)
572 // Flick length is the length of the ScrollableBase.
573 float flickLength = (ScrollingDirection == Direction.Horizontal) ?CurrentSize.Width:CurrentSize.Height;
575 // Calculate multiplier by mapping speed between the multiplier minimum and maximum.
576 multiplier =( (speed - speedMinimum) / ( (speedMaximum - speedMinimum) * (multiplierMaximum - multiplierMinimum) ) )+ multiplierMinimum;
578 // flick displacement is the product of the flick length and multiplier
579 flickDisplacement = ((flickLength * multiplier) * speed) / axisVelocity; // *speed and /velocity to perserve sign.
581 Debug.WriteLineIf(LayoutDebugScrollableBase, "Calculated FlickDisplacement[" + flickDisplacement +"] from speed[" + speed + "] multiplier:"
584 return flickDisplacement;
587 private float CalculateMaximumScrollDistance()
589 int scrollingChildLength = 0;
590 int scrollerLength = 0;
591 if (ScrollingDirection == Direction.Horizontal)
593 Debug.WriteLineIf(LayoutDebugScrollableBase, "Horizontal");
595 scrollingChildLength = (int)mScrollingChild.Layout.MeasuredWidth.Size.AsRoundedValue();
596 scrollerLength = CurrentSize.Width;
600 Debug.WriteLineIf(LayoutDebugScrollableBase, "Vertical");
601 scrollingChildLength = (int)mScrollingChild.Layout.MeasuredHeight.Size.AsRoundedValue();
602 scrollerLength = CurrentSize.Height;
605 Debug.WriteLineIf(LayoutDebugScrollableBase, "ScrollBy maxScrollDistance:" + (scrollingChildLength - scrollerLength) +
606 " parent length:" + scrollerLength +
607 " scrolling child length:" + scrollingChildLength);
609 return Math.Max(scrollingChildLength - scrollerLength,0);
612 private void PageSnap()
614 Debug.WriteLineIf(LayoutDebugScrollableBase, "PageSnap with pan candidate totalDisplacement:" + totalDisplacementForPan +
615 " currentPage[" + CurrentPage + "]" );
617 //Increment current page if total displacement enough to warrant a page change.
618 if (Math.Abs(totalDisplacementForPan) > (mPageWidth * ratioOfScreenWidthToCompleteScroll))
620 if (totalDisplacementForPan < 0)
622 CurrentPage = Math.Min(Math.Max(mScrollingChild.Children.Count - 1,0), ++CurrentPage);
626 CurrentPage = Math.Max(0, --CurrentPage);
630 // Animate to new page or reposition to current page
631 int destinationX = -(CurrentPage * mPageWidth);
632 Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to page[" + CurrentPage + "] to:"+ destinationX + " from:" + mScrollingChild.PositionX);
633 AnimateChildTo(ScrollDuration, destinationX);
636 private void Flick(float flickDisplacement)
640 if ( ( flickWhenAnimating && scrolling == true) || ( scrolling == false) )
642 if(flickDisplacement < 0)
644 CurrentPage = Math.Min(Math.Max(mScrollingChild.Children.Count - 1,0), CurrentPage + 1);
645 Debug.WriteLineIf(LayoutDebugScrollableBase, "Snap - to page:" + CurrentPage);
649 CurrentPage = Math.Max(0, CurrentPage - 1);
650 Debug.WriteLineIf(LayoutDebugScrollableBase, "Snap + to page:" + CurrentPage);
652 float targetPosition = -(CurrentPage* mPageWidth); // page size
653 Debug.WriteLineIf(LayoutDebugScrollableBase, "Snapping to :" + targetPosition);
654 AnimateChildTo(ScrollDuration,targetPosition);
659 ScrollBy(flickDisplacement, true); // Animate flickDisplacement.
663 private void OnPanGestureDetected(object source, PanGestureDetector.DetectedEventArgs e)
665 if (e.PanGesture.State == Gesture.StateType.Started)
667 Debug.WriteLineIf(LayoutDebugScrollableBase, "Gesture Start");
668 if (scrolling && !SnapToPage)
672 maxScrollDistance = CalculateMaximumScrollDistance();
673 totalDisplacementForPan = 0.0f;
676 else if (e.PanGesture.State == Gesture.StateType.Continuing)
678 if (ScrollingDirection == Direction.Horizontal)
680 ScrollBy(e.PanGesture.Displacement.X, false);
681 totalDisplacementForPan += e.PanGesture.Displacement.X;
685 ScrollBy(e.PanGesture.Displacement.Y, false);
686 totalDisplacementForPan += e.PanGesture.Displacement.Y;
688 Debug.WriteLineIf(LayoutDebugScrollableBase, "OnPanGestureDetected Continue totalDisplacementForPan:" + totalDisplacementForPan);
691 else if (e.PanGesture.State == Gesture.StateType.Finished)
693 float axisVelocity = (ScrollingDirection == Direction.Horizontal) ? e.PanGesture.Velocity.X : e.PanGesture.Velocity.Y;
694 float flickDisplacement = CalculateDisplacementFromVelocity(axisVelocity);
696 Debug.WriteLineIf(LayoutDebugScrollableBase, "FlickDisplacement:" + flickDisplacement + "TotalDisplacementForPan:" + totalDisplacementForPan);
699 if (flickDisplacement > 0 | flickDisplacement < 0)// Flick detected
701 Flick(flickDisplacement);
705 // End of panning gesture but was not a flick
711 totalDisplacementForPan = 0;
715 private new void OnTapGestureDetected(object source, TapGestureDetector.DetectedEventArgs e)
717 if (e.TapGesture.Type == Gesture.GestureType.Tap)
719 // Stop scrolling if tap detected (press then relase).
720 // Unless in Pages mode, do not want a page change to stop part way.
721 if(scrolling && !SnapToPage)
728 private void ScrollAnimationFinished(object sender, EventArgs e)
731 OnScrollAnimationEnd();