summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Platform.Android/Renderers/ScrollViewRenderer.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Xamarin.Forms.Platform.Android/Renderers/ScrollViewRenderer.cs')
-rw-r--r--Xamarin.Forms.Platform.Android/Renderers/ScrollViewRenderer.cs331
1 files changed, 331 insertions, 0 deletions
diff --git a/Xamarin.Forms.Platform.Android/Renderers/ScrollViewRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ScrollViewRenderer.cs
new file mode 100644
index 00000000..15f04278
--- /dev/null
+++ b/Xamarin.Forms.Platform.Android/Renderers/ScrollViewRenderer.cs
@@ -0,0 +1,331 @@
+using System;
+using System.ComponentModel;
+using System.Threading.Tasks;
+using Android.Animation;
+using Android.Graphics;
+using Android.Views;
+using Android.Widget;
+using AScrollView = Android.Widget.ScrollView;
+
+namespace Xamarin.Forms.Platform.Android
+{
+ public class ScrollViewRenderer : AScrollView, IVisualElementRenderer
+ {
+ ScrollViewContainer _container;
+ HorizontalScrollView _hScrollView;
+ bool _isAttached;
+
+ bool _isBidirectional;
+ ScrollToRequestedEventArgs _pendingScrollTo;
+ ScrollView _view;
+
+ public ScrollViewRenderer() : base(Forms.Context)
+ {
+ }
+
+ protected IScrollViewController Controller
+ {
+ get { return (IScrollViewController)Element; }
+ }
+
+ internal float LastX { get; set; }
+
+ internal float LastY { get; set; }
+
+ public VisualElement Element
+ {
+ get { return _view; }
+ }
+
+ public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
+
+ public SizeRequest GetDesiredSize(int widthConstraint, int heightConstraint)
+ {
+ Measure(widthConstraint, heightConstraint);
+ return new SizeRequest(new Size(MeasuredWidth, MeasuredHeight), new Size(40, 40));
+ }
+
+ public void SetElement(VisualElement element)
+ {
+ ScrollView oldElement = _view;
+ _view = (ScrollView)element;
+
+ if (oldElement != null)
+ {
+ oldElement.PropertyChanged -= HandlePropertyChanged;
+ ((IScrollViewController)oldElement).ScrollToRequested -= OnScrollToRequested;
+ }
+ if (element != null)
+ {
+ OnElementChanged(new VisualElementChangedEventArgs(oldElement, element));
+
+ if (_container == null)
+ {
+ Tracker = new VisualElementTracker(this);
+ _container = new ScrollViewContainer(_view, Forms.Context);
+ }
+
+ _view.PropertyChanged += HandlePropertyChanged;
+ Controller.ScrollToRequested += OnScrollToRequested;
+
+ LoadContent();
+ UpdateBackgroundColor();
+
+ UpdateOrientation();
+
+ element.SendViewInitialized(this);
+
+ if (!string.IsNullOrEmpty(element.AutomationId))
+ ContentDescription = element.AutomationId;
+ }
+ }
+
+ public VisualElementTracker Tracker { get; private set; }
+
+ public void UpdateLayout()
+ {
+ if (Tracker != null)
+ Tracker.UpdateLayout();
+ }
+
+ public ViewGroup ViewGroup
+ {
+ get { return this; }
+ }
+
+ public override void Draw(Canvas canvas)
+ {
+ canvas.ClipRect(canvas.ClipBounds);
+
+ base.Draw(canvas);
+ }
+
+ public override bool OnInterceptTouchEvent(MotionEvent ev)
+ {
+ if (Element.InputTransparent)
+ return false;
+
+ // set the start point for the bidirectional scroll;
+ // Down is swallowed by other controls, so we'll just sneak this in here without actually preventing
+ // other controls from getting the event.
+ if (_isBidirectional && ev.Action == MotionEventActions.Down)
+ {
+ LastY = ev.RawY;
+ LastX = ev.RawX;
+ }
+
+ return base.OnInterceptTouchEvent(ev);
+ }
+
+ public override bool OnTouchEvent(MotionEvent ev)
+ {
+ // The nested ScrollViews will allow us to scroll EITHER vertically OR horizontally in a single gesture.
+ // This will allow us to also scroll diagonally.
+ // We'll fall through to the base event so we still get the fling from the ScrollViews.
+ // We have to do this in both ScrollViews, since a single gesture will be owned by one or the other, depending
+ // on the initial direction of movement (i.e., horizontal/vertical).
+ if (_isBidirectional && !Element.InputTransparent)
+ {
+ float dX = LastX - ev.RawX;
+ float dY = LastY - ev.RawY;
+ LastY = ev.RawY;
+ LastX = ev.RawX;
+ if (ev.Action == MotionEventActions.Move)
+ {
+ ScrollBy(0, (int)dY);
+ foreach (AHorizontalScrollView child in this.GetChildrenOfType<AHorizontalScrollView>())
+ {
+ child.ScrollBy((int)dX, 0);
+ break;
+ }
+ }
+ }
+ return base.OnTouchEvent(ev);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ SetElement(null);
+
+ if (disposing)
+ {
+ Tracker.Dispose();
+ Tracker = null;
+ RemoveAllViews();
+ _container.Dispose();
+ _container = null;
+ }
+ }
+
+ protected override void OnAttachedToWindow()
+ {
+ base.OnAttachedToWindow();
+
+ _isAttached = true;
+ }
+
+ protected override void OnDetachedFromWindow()
+ {
+ base.OnDetachedFromWindow();
+
+ _isAttached = false;
+ }
+
+ protected virtual void OnElementChanged(VisualElementChangedEventArgs e)
+ {
+ EventHandler<VisualElementChangedEventArgs> changed = ElementChanged;
+ if (changed != null)
+ changed(this, e);
+ }
+
+ protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
+ {
+ base.OnLayout(changed, left, top, right, bottom);
+ if (_view.Content != null && _hScrollView != null)
+ _hScrollView.Layout(0, 0, right - left, Math.Max(bottom - top, (int)Context.ToPixels(_view.Content.Height)));
+ }
+
+ protected override void OnScrollChanged(int l, int t, int oldl, int oldt)
+ {
+ base.OnScrollChanged(l, t, oldl, oldt);
+
+ UpdateScrollPosition(Forms.Context.FromPixels(l), Forms.Context.FromPixels(t));
+ }
+
+ internal void UpdateScrollPosition(double x, double y)
+ {
+ if (_view != null)
+ Controller.SetScrolledPosition(x, y);
+ }
+
+ static int GetDistance(double start, double position, double v)
+ {
+ return (int)(start + (position - start) * v);
+ }
+
+ void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == "Content")
+ LoadContent();
+ else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
+ UpdateBackgroundColor();
+ else if (e.PropertyName == ScrollView.OrientationProperty.PropertyName)
+ UpdateOrientation();
+ }
+
+ void LoadContent()
+ {
+ _container.ChildView = _view.Content;
+ }
+
+ async void OnScrollToRequested(object sender, ScrollToRequestedEventArgs e)
+ {
+ if (!_isAttached)
+ {
+ _pendingScrollTo = e;
+ return;
+ }
+
+ // 99.99% of the time simply queuing to the end of the execution queue should handle this case.
+ // However it is possible to end a layout cycle and STILL be layout requested. We want to
+ // back off until all are done, even if they trigger layout storms over and over. So we back off
+ // for 10ms tops then move on.
+ var cycle = 0;
+ while (IsLayoutRequested)
+ {
+ await Task.Delay(TimeSpan.FromMilliseconds(1));
+ cycle++;
+
+ if (cycle >= 10)
+ break;
+ }
+
+ var x = (int)Forms.Context.ToPixels(e.ScrollX);
+ var y = (int)Forms.Context.ToPixels(e.ScrollY);
+ int currentX = _view.Orientation == ScrollOrientation.Horizontal ? _hScrollView.ScrollX : ScrollX;
+ int currentY = _view.Orientation == ScrollOrientation.Horizontal ? _hScrollView.ScrollY : ScrollY;
+ if (e.Mode == ScrollToMode.Element)
+ {
+ Point itemPosition = Controller.GetScrollPositionForElement(e.Element as VisualElement, e.Position);
+
+ x = (int)Forms.Context.ToPixels(itemPosition.X);
+ y = (int)Forms.Context.ToPixels(itemPosition.Y);
+ }
+ if (e.ShouldAnimate)
+ {
+ ValueAnimator animator = ValueAnimator.OfFloat(0f, 1f);
+ animator.SetDuration(1000);
+ animator.Update += (o, animatorUpdateEventArgs) =>
+ {
+ var v = (double)animatorUpdateEventArgs.Animation.AnimatedValue;
+ int distX = GetDistance(currentX, x, v);
+ int distY = GetDistance(currentY, y, v);
+
+ if (_view == null)
+ {
+ // This is probably happening because the page with this Scroll View
+ // was popped off the stack during animation
+ animator.Cancel();
+ return;
+ }
+
+ if (_view.Orientation == ScrollOrientation.Horizontal)
+ _hScrollView.ScrollTo(distX, distY);
+ else
+ ScrollTo(distX, distY);
+ };
+ animator.AnimationEnd += delegate
+ {
+ if (Controller == null)
+ return;
+ Controller.SendScrollFinished();
+ };
+
+ animator.Start();
+ }
+ else
+ {
+ if (_view.Orientation == ScrollOrientation.Horizontal)
+ _hScrollView.ScrollTo(x, y);
+ else
+ ScrollTo(x, y);
+ Controller.SendScrollFinished();
+ }
+ }
+
+ void UpdateBackgroundColor()
+ {
+ SetBackgroundColor(Element.BackgroundColor.ToAndroid(Color.Transparent));
+ }
+
+ void UpdateOrientation()
+ {
+ if (_view.Orientation == ScrollOrientation.Horizontal || _view.Orientation == ScrollOrientation.Both)
+ {
+ if (_hScrollView == null)
+ _hScrollView = new AHorizontalScrollView(Context, this);
+
+ ((AHorizontalScrollView)_hScrollView).IsBidirectional = _isBidirectional = _view.Orientation == ScrollOrientation.Both;
+
+ if (_hScrollView.Parent != this)
+ {
+ _container.RemoveFromParent();
+ _hScrollView.AddView(_container);
+ AddView(_hScrollView);
+ }
+ }
+ else
+ {
+ if (_container.Parent != this)
+ {
+ _container.RemoveFromParent();
+ if (_hScrollView != null)
+ _hScrollView.RemoveFromParent();
+ AddView(_container);
+ }
+ }
+ }
+ }
+} \ No newline at end of file