diff options
Diffstat (limited to 'Xamarin.Forms.Platform.Android/VisualElementTracker.cs')
-rw-r--r-- | Xamarin.Forms.Platform.Android/VisualElementTracker.cs | 396 |
1 files changed, 396 insertions, 0 deletions
diff --git a/Xamarin.Forms.Platform.Android/VisualElementTracker.cs b/Xamarin.Forms.Platform.Android/VisualElementTracker.cs new file mode 100644 index 00000000..2b9815d3 --- /dev/null +++ b/Xamarin.Forms.Platform.Android/VisualElementTracker.cs @@ -0,0 +1,396 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using Android.Content; +using Android.OS; +using Android.Views; +using AView = Android.Views.View; +using Object = Java.Lang.Object; + +namespace Xamarin.Forms.Platform.Android +{ + public class VisualElementTracker : IDisposable + { + readonly EventHandler<EventArg<VisualElement>> _batchCommittedHandler; + readonly IList<string> _batchedProperties = new List<string>(); + readonly PropertyChangedEventHandler _propertyChangedHandler; + Context _context; + + bool _disposed; + + VisualElement _element; + bool _initialUpdateNeeded = true; + bool _layoutNeeded; + IVisualElementRenderer _renderer; + + public VisualElementTracker(IVisualElementRenderer renderer) + { + if (renderer == null) + throw new ArgumentNullException("renderer"); + + _batchCommittedHandler = HandleRedrawNeeded; + _propertyChangedHandler = HandlePropertyChanged; + + _renderer = renderer; + _context = renderer.ViewGroup.Context; + _renderer.ElementChanged += RendererOnElementChanged; + + VisualElement view = renderer.Element; + SetElement(null, view); + + renderer.ViewGroup.SetCameraDistance(3600); + + renderer.ViewGroup.AddOnAttachStateChangeListener(AttachTracker.Instance); + } + + public void Dispose() + { + if (_disposed) + return; + _disposed = true; + + SetElement(_element, null); + + if (_renderer != null) + { + _renderer.ElementChanged -= RendererOnElementChanged; + _renderer.ViewGroup.RemoveOnAttachStateChangeListener(AttachTracker.Instance); + _renderer = null; + _context = null; + } + } + + public void UpdateLayout() + { + Performance.Start(); + + VisualElement view = _renderer.Element; + AView aview = _renderer.ViewGroup; + + var x = (int)_context.ToPixels(view.X); + var y = (int)_context.ToPixels(view.Y); + var width = (int)_context.ToPixels(view.Width); + var height = (int)_context.ToPixels(view.Height); + + var formsViewGroup = aview as FormsViewGroup; + if (formsViewGroup == null) + { + Performance.Start("Measure"); + aview.Measure(MeasureSpecFactory.MakeMeasureSpec(width, MeasureSpecMode.Exactly), MeasureSpecFactory.MakeMeasureSpec(height, MeasureSpecMode.Exactly)); + Performance.Stop("Measure"); + + Performance.Start("Layout"); + aview.Layout(x, y, x + width, y + height); + Performance.Stop("Layout"); + } + else + { + Performance.Start("MeasureAndLayout"); + formsViewGroup.MeasureAndLayout(MeasureSpecFactory.MakeMeasureSpec(width, MeasureSpecMode.Exactly), MeasureSpecFactory.MakeMeasureSpec(height, MeasureSpecMode.Exactly), x, y, x + width, y + height); + Performance.Stop("MeasureAndLayout"); + } + + Performance.Stop(); + + //On Width or Height changes, the anchors needs to be updated + UpdateAnchorX(); + UpdateAnchorY(); + } + + void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == Layout.IsClippedToBoundsProperty.PropertyName) + { + UpdateClipToBounds(); + return; + } + + if (_renderer.Element.Batched) + { + if (e.PropertyName == VisualElement.XProperty.PropertyName || e.PropertyName == VisualElement.YProperty.PropertyName || e.PropertyName == VisualElement.WidthProperty.PropertyName || + e.PropertyName == VisualElement.HeightProperty.PropertyName) + _layoutNeeded = true; + else if (e.PropertyName == VisualElement.AnchorXProperty.PropertyName || e.PropertyName == VisualElement.AnchorYProperty.PropertyName || e.PropertyName == VisualElement.ScaleProperty.PropertyName || + e.PropertyName == VisualElement.RotationProperty.PropertyName || e.PropertyName == VisualElement.RotationXProperty.PropertyName || e.PropertyName == VisualElement.RotationYProperty.PropertyName || + e.PropertyName == VisualElement.IsVisibleProperty.PropertyName || e.PropertyName == VisualElement.OpacityProperty.PropertyName || + e.PropertyName == VisualElement.TranslationXProperty.PropertyName || e.PropertyName == VisualElement.TranslationYProperty.PropertyName) + { + if (!_batchedProperties.Contains(e.PropertyName)) + _batchedProperties.Add(e.PropertyName); + } + return; + } + + if (e.PropertyName == VisualElement.XProperty.PropertyName || e.PropertyName == VisualElement.YProperty.PropertyName || e.PropertyName == VisualElement.WidthProperty.PropertyName || + e.PropertyName == VisualElement.HeightProperty.PropertyName) + MaybeRequestLayout(); + else if (e.PropertyName == VisualElement.AnchorXProperty.PropertyName) + UpdateAnchorX(); + else if (e.PropertyName == VisualElement.AnchorYProperty.PropertyName) + UpdateAnchorY(); + else if (e.PropertyName == VisualElement.ScaleProperty.PropertyName) + UpdateScale(); + else if (e.PropertyName == VisualElement.RotationProperty.PropertyName) + UpdateRotation(); + else if (e.PropertyName == VisualElement.RotationXProperty.PropertyName) + UpdateRotationX(); + else if (e.PropertyName == VisualElement.RotationYProperty.PropertyName) + UpdateRotationY(); + else if (e.PropertyName == VisualElement.IsVisibleProperty.PropertyName) + UpdateIsVisible(); + else if (e.PropertyName == VisualElement.OpacityProperty.PropertyName) + UpdateOpacity(); + else if (e.PropertyName == VisualElement.TranslationXProperty.PropertyName) + UpdateTranslationX(); + else if (e.PropertyName == VisualElement.TranslationYProperty.PropertyName) + UpdateTranslationY(); + } + + void HandleRedrawNeeded(object sender, EventArg<VisualElement> e) + { + foreach (string propertyName in _batchedProperties) + HandlePropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + _batchedProperties.Clear(); + + if (_layoutNeeded) + MaybeRequestLayout(); + _layoutNeeded = false; + } + + void HandleViewAttachedToWindow() + { + if (_initialUpdateNeeded) + { + UpdateNativeView(this, EventArgs.Empty); + _initialUpdateNeeded = false; + } + + UpdateClipToBounds(); + } + + void MaybeRequestLayout() + { + var isInLayout = false; + if ((int)Build.VERSION.SdkInt >= 18) + isInLayout = _renderer.ViewGroup.IsInLayout; + + if (!isInLayout && !_renderer.ViewGroup.IsLayoutRequested) + _renderer.ViewGroup.RequestLayout(); + } + + void RendererOnElementChanged(object sender, VisualElementChangedEventArgs args) + { + SetElement(args.OldElement, args.NewElement); + } + + void SetElement(VisualElement oldElement, VisualElement newElement) + { + if (oldElement != null) + { + oldElement.BatchCommitted -= _batchCommittedHandler; + oldElement.PropertyChanged -= _propertyChangedHandler; + _context = null; + } + + _element = newElement; + if (newElement != null) + { + newElement.BatchCommitted += _batchCommittedHandler; + newElement.PropertyChanged += _propertyChangedHandler; + _context = _renderer.ViewGroup.Context; + + if (oldElement != null) + { + AView view = _renderer.ViewGroup; + + // ReSharper disable CompareOfFloatsByEqualityOperator + if (oldElement.AnchorX != newElement.AnchorX) + UpdateAnchorX(); + if (oldElement.AnchorY != newElement.AnchorY) + UpdateAnchorY(); + if (oldElement.IsVisible != newElement.IsVisible) + UpdateIsVisible(); + if (oldElement.IsEnabled != newElement.IsEnabled) + view.Enabled = newElement.IsEnabled; + if (oldElement.Opacity != newElement.Opacity) + UpdateOpacity(); + if (oldElement.Rotation != newElement.Rotation) + UpdateRotation(); + if (oldElement.RotationX != newElement.RotationX) + UpdateRotationX(); + if (oldElement.RotationY != newElement.RotationY) + UpdateRotationY(); + if (oldElement.Scale != newElement.Scale) + UpdateScale(); + // ReSharper restore CompareOfFloatsByEqualityOperator + + _initialUpdateNeeded = false; + } + } + } + + void UpdateAnchorX() + { + VisualElement view = _renderer.Element; + AView aview = _renderer.ViewGroup; + + float currentPivot = aview.PivotX; + var target = (float)(view.AnchorX * _context.ToPixels(view.Width)); + if (currentPivot != target) + aview.PivotX = target; + } + + void UpdateAnchorY() + { + VisualElement view = _renderer.Element; + AView aview = _renderer.ViewGroup; + + float currentPivot = aview.PivotY; + var target = (float)(view.AnchorY * _context.ToPixels(view.Height)); + if (currentPivot != target) + aview.PivotY = target; + } + + void UpdateClipToBounds() + { + var layout = _renderer.Element as Layout; + var parent = _renderer.ViewGroup.Parent as ViewGroup; + + if (parent == null || layout == null) + return; + + bool shouldClip = layout.IsClippedToBounds; + + if ((int)Build.VERSION.SdkInt >= 18 && parent.ClipChildren == shouldClip) + return; + + parent.SetClipChildren(shouldClip); + parent.Invalidate(); + } + + void UpdateIsVisible() + { + VisualElement view = _renderer.Element; + AView aview = _renderer.ViewGroup; + + if (view.IsVisible && aview.Visibility != ViewStates.Visible) + aview.Visibility = ViewStates.Visible; + if (!view.IsVisible && aview.Visibility != ViewStates.Gone) + aview.Visibility = ViewStates.Gone; + } + + void UpdateNativeView(object sender, EventArgs e) + { + Performance.Start(); + + VisualElement view = _renderer.Element; + AView aview = _renderer.ViewGroup; + + if (aview is FormsViewGroup) + { + var formsViewGroup = (FormsViewGroup)aview; + formsViewGroup.SendBatchUpdate((float)(view.AnchorX * _context.ToPixels(view.Width)), (float)(view.AnchorY * _context.ToPixels(view.Height)), + (int)(view.IsVisible ? ViewStates.Visible : ViewStates.Invisible), view.IsEnabled, (float)view.Opacity, (float)view.Rotation, (float)view.RotationX, (float)view.RotationY, (float)view.Scale, + _context.ToPixels(view.TranslationX), _context.ToPixels(view.TranslationY)); + } + else + { + UpdateAnchorX(); + UpdateAnchorY(); + UpdateIsVisible(); + + if (view.IsEnabled != aview.Enabled) + aview.Enabled = view.IsEnabled; + + UpdateOpacity(); + UpdateRotation(); + UpdateRotationX(); + UpdateRotationY(); + UpdateScale(); + UpdateTranslationX(); + UpdateTranslationY(); + } + + Performance.Stop(); + } + + void UpdateOpacity() + { + Performance.Start(); + + VisualElement view = _renderer.Element; + AView aview = _renderer.ViewGroup; + + aview.Alpha = (float)view.Opacity; + + Performance.Stop(); + } + + void UpdateRotation() + { + VisualElement view = _renderer.Element; + AView aview = _renderer.ViewGroup; + + aview.Rotation = (float)view.Rotation; + } + + void UpdateRotationX() + { + VisualElement view = _renderer.Element; + AView aview = _renderer.ViewGroup; + + aview.RotationX = (float)view.RotationX; + } + + void UpdateRotationY() + { + VisualElement view = _renderer.Element; + AView aview = _renderer.ViewGroup; + + aview.RotationY = (float)view.RotationY; + } + + void UpdateScale() + { + VisualElement view = _renderer.Element; + AView aview = _renderer.ViewGroup; + + aview.ScaleX = (float)view.Scale; + aview.ScaleY = (float)view.Scale; + } + + void UpdateTranslationX() + { + VisualElement view = _renderer.Element; + AView aview = _renderer.ViewGroup; + + aview.TranslationX = _context.ToPixels(view.TranslationX); + } + + void UpdateTranslationY() + { + VisualElement view = _renderer.Element; + AView aview = _renderer.ViewGroup; + + aview.TranslationY = _context.ToPixels(view.TranslationY); + } + + class AttachTracker : Object, AView.IOnAttachStateChangeListener + { + public static readonly AttachTracker Instance = new AttachTracker(); + + public void OnViewAttachedToWindow(AView attachedView) + { + var renderer = attachedView as IVisualElementRenderer; + if (renderer == null || renderer.Tracker == null) + return; + + renderer.Tracker.HandleViewAttachedToWindow(); + } + + public void OnViewDetachedFromWindow(AView detachedView) + { + } + } + } +}
\ No newline at end of file |