using System; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using Android.Content; using Android.Support.V4.View; using Android.Support.V7.Widget; using Android.Views; using AColor = Android.Graphics.Color; using AView = Android.Views.View; namespace Xamarin.Forms.Platform.Android.AppCompat { public class FrameRenderer : CardView, IVisualElementRenderer, AView.IOnClickListener, AView.IOnTouchListener { readonly Lazy _gestureDetector; readonly PanGestureHandler _panGestureHandler; readonly PinchGestureHandler _pinchGestureHandler; readonly Lazy _scaleDetector; readonly TapGestureHandler _tapGestureHandler; float _defaultElevation = -1f; bool _clickable; bool _disposed; Frame _element; InnerGestureListener _gestureListener; VisualElementPackager _visualElementPackager; VisualElementTracker _visualElementTracker; NotifyCollectionChangedEventHandler _collectionChangeHandler; public FrameRenderer() : base(Forms.Context) { _tapGestureHandler = new TapGestureHandler(() => Element); _panGestureHandler = new PanGestureHandler(() => Element, Context.FromPixels); _pinchGestureHandler = new PinchGestureHandler(() => Element); _gestureDetector = new Lazy( () => new GestureDetector( _gestureListener = new InnerGestureListener(_tapGestureHandler.OnTap, _tapGestureHandler.TapGestureRecognizers, _panGestureHandler.OnPan, _panGestureHandler.OnPanStarted, _panGestureHandler.OnPanComplete))); _scaleDetector = new Lazy( () => new ScaleGestureDetector(Context, new InnerScaleListener(_pinchGestureHandler.OnPinch, _pinchGestureHandler.OnPinchStarted, _pinchGestureHandler.OnPinchEnded), Handler)); } protected CardView Control => this; protected Frame Element { get { return _element; } set { if (_element == value) return; Frame oldElement = _element; _element = value; OnElementChanged(new ElementChangedEventArgs(oldElement, _element)); if (_element != null) _element.SendViewInitialized(Control); } } void IOnClickListener.OnClick(AView v) { _tapGestureHandler.OnSingleClick(); } bool IOnTouchListener.OnTouch(AView v, MotionEvent e) { var handled = false; if (_pinchGestureHandler.IsPinchSupported) { if (!_scaleDetector.IsValueCreated) ScaleGestureDetectorCompat.SetQuickScaleEnabled(_scaleDetector.Value, true); handled = _scaleDetector.Value.OnTouchEvent(e); } return _gestureDetector.Value.OnTouchEvent(e) || handled; } VisualElement IVisualElementRenderer.Element => Element; public event EventHandler ElementChanged; SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint) { Context context = Context; return new SizeRequest(new Size(context.ToPixels(20), context.ToPixels(20))); } void IVisualElementRenderer.SetElement(VisualElement element) { var frame = element as Frame; if (frame == null) throw new ArgumentException("Element must be of type Frame"); Element = frame; if (!string.IsNullOrEmpty(Element.AutomationId)) ContentDescription = Element.AutomationId; } VisualElementTracker IVisualElementRenderer.Tracker => _visualElementTracker; void IVisualElementRenderer.UpdateLayout() { VisualElementTracker tracker = _visualElementTracker; tracker?.UpdateLayout(); } ViewGroup IVisualElementRenderer.ViewGroup => this; protected override void Dispose(bool disposing) { if (disposing && !_disposed) { _disposed = true; if (_gestureListener != null) { _gestureListener.Dispose(); _gestureListener = null; } if (_visualElementTracker != null) { _visualElementTracker.Dispose(); _visualElementTracker = null; } if (_visualElementPackager != null) { _visualElementPackager.Dispose(); _visualElementPackager = null; } int count = ChildCount; for (var i = 0; i < count; i++) { AView child = GetChildAt(i); child.Dispose(); } if (Element != null) { Element.PropertyChanged -= OnElementPropertyChanged; UnsubscribeGestureRecognizers(Element); } } base.Dispose(disposing); } protected virtual void OnElementChanged(ElementChangedEventArgs e) { ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement)); if (e.OldElement != null) { e.OldElement.PropertyChanged -= OnElementPropertyChanged; UnsubscribeGestureRecognizers(e.OldElement); } if (e.NewElement != null) { if (_visualElementTracker == null) { SetOnClickListener(this); SetOnTouchListener(this); UpdateGestureRecognizers(true); _visualElementTracker = new VisualElementTracker(this); _visualElementPackager = new VisualElementPackager(this); _visualElementPackager.Load(); } e.NewElement.PropertyChanged += OnElementPropertyChanged; UpdateShadow(); UpdateBackgroundColor(); SubscribeGestureRecognizers(e.NewElement); } } protected override void OnLayout(bool changed, int left, int top, int right, int bottom) { if (Element == null) return; var children = ((IElementController)Element).LogicalChildren; for (var i = 0; i < children.Count; i++) { var visualElement = children[i] as VisualElement; if (visualElement == null) continue; IVisualElementRenderer renderer = Android.Platform.GetRenderer(visualElement); renderer?.UpdateLayout(); } } void HandleGestureRecognizerCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs) { UpdateGestureRecognizers(); } void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == Frame.HasShadowProperty.PropertyName) UpdateShadow(); else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) UpdateBackgroundColor(); } void SubscribeGestureRecognizers(VisualElement element) { var view = element as View; if (view == null) return; if (_collectionChangeHandler == null) _collectionChangeHandler = HandleGestureRecognizerCollectionChanged; var observableCollection = (ObservableCollection)view.GestureRecognizers; if (observableCollection != null) { observableCollection.CollectionChanged += _collectionChangeHandler; } } void UnsubscribeGestureRecognizers(VisualElement element) { var view = element as View; if (view == null || _collectionChangeHandler == null) return; var observableCollection = (ObservableCollection)view.GestureRecognizers; if (observableCollection != null) { observableCollection.CollectionChanged -= _collectionChangeHandler; } } void UpdateBackgroundColor() { Color bgColor = Element.BackgroundColor; SetCardBackgroundColor(bgColor.IsDefault ? AColor.White : bgColor.ToAndroid()); } void UpdateClickable(bool force = false) { var view = Element as View; if (view == null) return; bool newValue = view.ShouldBeMadeClickable(); if (force || _clickable != newValue) { Clickable = newValue; _clickable = newValue; } } void UpdateGestureRecognizers(bool forceClick = false) { if (Element == null) return; UpdateClickable(forceClick); } void UpdateShadow() { float elevation = _defaultElevation; if (elevation == -1f) _defaultElevation = elevation = CardElevation; if (Element.HasShadow) CardElevation = elevation; else CardElevation = 0f; } } }