diff options
author | Rui Marinho <me@ruimarinho.net> | 2017-04-06 14:19:52 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-04-06 14:19:52 +0100 |
commit | 425fafb05723a299a5eaa0f6d801f87bdf7141fa (patch) | |
tree | a9e38aa0a0252e6a6d2da6990303d82f2163a5a9 /Xamarin.Forms.Platform.Android | |
parent | 829a4bda76166ddffce5a0e84538183b9527a1bd (diff) | |
download | xamarin-forms-425fafb05723a299a5eaa0f6d801f87bdf7141fa.tar.gz xamarin-forms-425fafb05723a299a5eaa0f6d801f87bdf7141fa.tar.bz2 xamarin-forms-425fafb05723a299a5eaa0f6d801f87bdf7141fa.zip |
Android fastrenderers (#845)
* Obsolete IVisualElementRenderer.ViewGroup in favor of .View
* Fix NRE
* Changing TContainer in PlatformEffect to View
* Fix "View" type
* new VisualElementRenderer
* First attempt at a fast(er) button renderer
* Fast Label Renderer
* Let's try that again. Behold: Label Fast Renderer
* Move FrameRenderer into Fast Renderers
* Fix Disposable on VisualElementRenderer
* Simplify touch and click handlers
* Drop empty if clause
* [Android] Add initial Image fast renderer
* Split accessibility out to a separate helper class; fix tapgesture bug with label
* [Android] Small fixes to VisualElementRenderer
* Move accessiblity stuff to a separate class (which needs a good name)
* Prevent query from looking to parent for fast renderers
* [Android] ImageRenderer refactoring
* Fix elevation/z-index bugs with Button (e.g., 40173)
* Move SetLabeledBy to Accessibilitizer
* Un-break automation IDs for Labels
* Move gesture handling to its own class
* Split gesture and effect management into separate classes
* Remove unneeded packager from LabelRenderer
* LabelRenderer inherits from FormsTextView
* Batch updates to View
* Fix isOnParentRenderer check for non-Android platforms
* [Controls] Update Xamarin.Forms.ControlGallery.iOS.csproj
* [Android,IOS] Small fixes to rebase and use of Internals
* [Android] Ignroe warning for now
* Fast renderers now passing InputTransparent and IsEnabled tests
* Fast and legacy renderers now pass the Enabled and InputTransparent tests
* Change PlatformEffect back, default container to null
* Fix mangled using directives
Diffstat (limited to 'Xamarin.Forms.Platform.Android')
41 files changed, 2054 insertions, 540 deletions
diff --git a/Xamarin.Forms.Platform.Android/AppCompat/ButtonRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/ButtonRenderer.cs index 61552f54..394f3325 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/ButtonRenderer.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/ButtonRenderer.cs @@ -17,7 +17,7 @@ using static System.String; namespace Xamarin.Forms.Platform.Android.AppCompat { - public class ButtonRenderer : ViewRenderer<Button, AppCompatButton>, AView.IOnAttachStateChangeListener + public class ButtonRenderer : ViewRenderer<Button, AppCompatButton>, AView.IOnAttachStateChangeListener { TextColorSwitcher _textColorSwitcher; float _defaultFontSize; diff --git a/Xamarin.Forms.Platform.Android/AppCompat/CarouselPageRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/CarouselPageRenderer.cs index f7a48d9b..766ee225 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/CarouselPageRenderer.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/CarouselPageRenderer.cs @@ -42,7 +42,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat IVisualElementRenderer pageRenderer = Android.Platform.GetRenderer(pageToRemove); if (pageRenderer != null) { - pageRenderer.ViewGroup.RemoveFromParent(); + pageRenderer.View.RemoveFromParent(); pageRenderer.Dispose(); } pageToRemove.ClearValue(Android.Platform.RendererProperty); diff --git a/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs b/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs index 4d588218..4ca0eab8 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/FormsAppCompatActivity.cs @@ -115,8 +115,8 @@ namespace Xamarin.Forms.Platform.Android RegisterHandlerForDefaultRenderer(typeof(NavigationPage), typeof(NavigationPageRenderer), typeof(NavigationRenderer)); RegisterHandlerForDefaultRenderer(typeof(TabbedPage), typeof(TabbedPageRenderer), typeof(TabbedRenderer)); RegisterHandlerForDefaultRenderer(typeof(MasterDetailPage), typeof(MasterDetailPageRenderer), typeof(MasterDetailRenderer)); - RegisterHandlerForDefaultRenderer(typeof(Button), typeof(AppCompat.ButtonRenderer), typeof(ButtonRenderer)); - RegisterHandlerForDefaultRenderer(typeof(Switch), typeof(AppCompat.SwitchRenderer), typeof(SwitchRenderer)); + RegisterHandlerForDefaultRenderer(typeof(Button), typeof(FastRenderers.ButtonRenderer), typeof(ButtonRenderer)); + RegisterHandlerForDefaultRenderer(typeof(Switch), typeof(AppCompat.SwitchRenderer), typeof(SwitchRenderer)); RegisterHandlerForDefaultRenderer(typeof(Picker), typeof(AppCompat.PickerRenderer), typeof(PickerRenderer)); RegisterHandlerForDefaultRenderer(typeof(Frame), typeof(AppCompat.FrameRenderer), typeof(FrameRenderer)); RegisterHandlerForDefaultRenderer(typeof(CarouselPage), typeof(AppCompat.CarouselPageRenderer), typeof(CarouselPageRenderer)); diff --git a/Xamarin.Forms.Platform.Android/AppCompat/FragmentContainer.cs b/Xamarin.Forms.Platform.Android/AppCompat/FragmentContainer.cs index 6cc493a1..e6c1b0ce 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/FragmentContainer.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/FragmentContainer.cs @@ -83,9 +83,9 @@ namespace Xamarin.Forms.Platform.Android.AppCompat { if (_visualElementRenderer != null) { - if (_visualElementRenderer.ViewGroup.Handle != IntPtr.Zero) + if (_visualElementRenderer.View.Handle != IntPtr.Zero) { - _visualElementRenderer.ViewGroup.RemoveFromParent(); + _visualElementRenderer.View.RemoveFromParent(); } _visualElementRenderer.Dispose(); diff --git a/Xamarin.Forms.Platform.Android/AppCompat/FrameRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/FrameRenderer.cs index 16ee0473..91ea94b9 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/FrameRenderer.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/FrameRenderer.cs @@ -1,372 +1,8 @@ -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> _gestureDetector; - readonly PanGestureHandler _panGestureHandler; - readonly PinchGestureHandler _pinchGestureHandler; - readonly Lazy<ScaleGestureDetector> _scaleDetector; - readonly TapGestureHandler _tapGestureHandler; - readonly MotionEventHelper _motionEventHelper = new MotionEventHelper(); - - float _defaultElevation = -1f; - float _defaultCornerRadius = -1f; - int? _defaultLabelFor; - - bool _clickable; - bool _disposed; - Frame _element; - InnerGestureListener _gestureListener; - VisualElementPackager _visualElementPackager; - VisualElementTracker _visualElementTracker; - NotifyCollectionChangedEventHandler _collectionChangeHandler; - - bool _inputTransparent; - bool _isEnabled; - - public FrameRenderer() : base(Forms.Context) - { - _tapGestureHandler = new TapGestureHandler(() => Element); - _panGestureHandler = new PanGestureHandler(() => Element, Context.FromPixels); - _pinchGestureHandler = new PinchGestureHandler(() => Element); - - _gestureDetector = - new Lazy<GestureDetector>( - () => - new GestureDetector( - _gestureListener = - new InnerGestureListener(_tapGestureHandler.OnTap, _tapGestureHandler.TapGestureRecognizers, _panGestureHandler.OnPan, _panGestureHandler.OnPanStarted, _panGestureHandler.OnPanComplete))); - - _scaleDetector = - new Lazy<ScaleGestureDetector>( - () => 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<Frame>(oldElement, _element)); - - if (_element != null) - _element.SendViewInitialized(Control); - } - } - - public override bool OnTouchEvent(MotionEvent e) - { - if (_inputTransparent) - { - return false; - } - - if (Element.GestureRecognizers.Count == 0) - { - return _motionEventHelper.HandleMotionEvent(Parent); - } - - return base.OnTouchEvent(e); - } - - void IOnClickListener.OnClick(AView v) - { - _tapGestureHandler.OnSingleClick(); - } - - bool IOnTouchListener.OnTouch(AView v, MotionEvent e) - { - if (!_isEnabled) - return true; - - if (_inputTransparent) - return false; - - var handled = false; - if (_pinchGestureHandler.IsPinchSupported) - { - if (!_scaleDetector.IsValueCreated) - ScaleGestureDetectorCompat.SetQuickScaleEnabled(_scaleDetector.Value, true); - handled = _scaleDetector.Value.OnTouchEvent(e); - } - - if (_gestureDetector.IsValueCreated && _gestureDetector.Value.Handle == IntPtr.Zero) - { - // This gesture detector has already been disposed, probably because it's on a cell which is going away - return handled; - } - - // It's very important that the gesture detection happen first here - // if we check handled first, we might short-circuit and never check for tap/pan - return _gestureDetector.Value.OnTouchEvent(e) || handled; - } - - VisualElement IVisualElementRenderer.Element => Element; - - public event EventHandler<VisualElementChangedEventArgs> 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; - } - - void IVisualElementRenderer.SetLabelFor(int? id) - { - if (_defaultLabelFor == null) - _defaultLabelFor = LabelFor; - - LabelFor = (int)(id ?? _defaultLabelFor); - } - - 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<Frame> 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(); - UpdateCornerRadius(); - UpdateInputTransparent(); - UpdateIsEnabled(); - SubscribeGestureRecognizers(e.NewElement); - } - - _motionEventHelper.UpdateElement(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(); - else if (e.PropertyName == Frame.CornerRadiusProperty.PropertyName) - UpdateCornerRadius(); - else if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName) - UpdateInputTransparent(); - else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName) - UpdateIsEnabled(); - } - - void UpdateIsEnabled() - { - _isEnabled = Element.IsEnabled; - } - - void UpdateInputTransparent() - { - _inputTransparent = Element.InputTransparent; - } - - void SubscribeGestureRecognizers(VisualElement element) - { - var view = element as View; - if (view == null) - return; - - if (_collectionChangeHandler == null) - _collectionChangeHandler = HandleGestureRecognizerCollectionChanged; - - var observableCollection = (ObservableCollection<IGestureRecognizer>)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<IGestureRecognizer>)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; - } - - void UpdateCornerRadius() - { - if (_defaultCornerRadius == -1f) - { - _defaultCornerRadius = Radius; - } - - float cornerRadius = Element.CornerRadius; - - if (cornerRadius == -1f) - cornerRadius = _defaultCornerRadius; - else - cornerRadius = Context.ToPixels(cornerRadius); - - Radius = cornerRadius; - } - } + // This version of FrameRenderer is here for backward compatibility with anyone referencing + // FrameRenderer from this namespace + public class FrameRenderer : FastRenderers.FrameRenderer + { + } }
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/AppCompat/MasterDetailPageRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/MasterDetailPageRenderer.cs index 85ab6b73..a1c4092e 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/MasterDetailPageRenderer.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/MasterDetailPageRenderer.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Android.Support.V4.Widget; using Android.Views; using Android.Support.V4.App; +using AView = Android.Views.View; namespace Xamarin.Forms.Platform.Android.AppCompat { @@ -87,6 +88,12 @@ namespace Xamarin.Forms.Platform.Android.AppCompat remove { ElementChanged -= value; } } + event EventHandler<PropertyChangedEventArgs> IVisualElementRenderer.ElementPropertyChanged + { + add { ElementPropertyChanged += value; } + remove { ElementPropertyChanged -= value; } + } + SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint) { Measure(widthConstraint, heightConstraint); @@ -178,6 +185,8 @@ namespace Xamarin.Forms.Platform.Android.AppCompat ViewGroup IVisualElementRenderer.ViewGroup => this; + AView IVisualElementRenderer.View => this; + protected override void Dispose(bool disposing) { if (disposing && !_disposed) @@ -266,6 +275,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat } event EventHandler<VisualElementChangedEventArgs> ElementChanged; + event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged; bool HasAncestorNavigationPage(Element element) { @@ -283,6 +293,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) { + ElementPropertyChanged?.Invoke(this, e); if (e.PropertyName == "Master") UpdateMaster(); else if (e.PropertyName == "Detail") diff --git a/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs index 7fe1f1e8..d405b42b 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/NavigationPageRenderer.cs @@ -534,7 +534,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat void RemovePage(Page page) { IVisualElementRenderer rendererToRemove = Android.Platform.GetRenderer(page); - var containerToRemove = (PageContainer)rendererToRemove?.ViewGroup.Parent; + var containerToRemove = (PageContainer)rendererToRemove?.View.Parent; // Also remove this page from the fragmentStack FilterPageFragment(page); @@ -542,7 +542,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat containerToRemove.RemoveFromParent(); if (rendererToRemove != null) { - rendererToRemove.ViewGroup.RemoveFromParent(); + rendererToRemove.View.RemoveFromParent(); rendererToRemove.Dispose(); } containerToRemove?.Dispose(); diff --git a/Xamarin.Forms.Platform.Android/AppCompat/Platform.cs b/Xamarin.Forms.Platform.Android/AppCompat/Platform.cs index 69365c63..0416ae3a 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/Platform.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/Platform.cs @@ -88,7 +88,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat IVisualElementRenderer modalRenderer = Android.Platform.GetRenderer(modal); if (modalRenderer != null) { - var modalContainer = modalRenderer.ViewGroup.Parent as ModalContainer; + var modalContainer = modalRenderer.View.Parent as ModalContainer; if (animated) { modalContainer.Animate().TranslationY(_renderer.Height).SetInterpolator(new AccelerateInterpolator(1)).SetDuration(300).SetListener(new GenericAnimatorListener @@ -254,7 +254,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat if (layout) LayoutRootPage((FormsAppCompatActivity)_context, page, _renderer.Width, _renderer.Height); - _renderer.AddView(renderView.ViewGroup); + _renderer.AddView(renderView.View); } bool HandleBackPressed(object sender, EventArgs e) @@ -336,7 +336,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat _renderer = Android.Platform.CreateRenderer(modal); Android.Platform.SetRenderer(modal, _renderer); - AddView(_renderer.ViewGroup); + AddView(_renderer.View); } protected override void Dispose(bool disposing) diff --git a/Xamarin.Forms.Platform.Android/AppCompat/TabbedPageRenderer.cs b/Xamarin.Forms.Platform.Android/AppCompat/TabbedPageRenderer.cs index b1aed05d..bb14100d 100644 --- a/Xamarin.Forms.Platform.Android/AppCompat/TabbedPageRenderer.cs +++ b/Xamarin.Forms.Platform.Android/AppCompat/TabbedPageRenderer.cs @@ -97,7 +97,7 @@ namespace Xamarin.Forms.Platform.Android.AppCompat IVisualElementRenderer pageRenderer = Android.Platform.GetRenderer(pageToRemove); if (pageRenderer != null) { - pageRenderer.ViewGroup.RemoveFromParent(); + pageRenderer.View.RemoveFromParent(); pageRenderer.Dispose(); } pageToRemove.PropertyChanged -= OnPagePropertyChanged; diff --git a/Xamarin.Forms.Platform.Android/Cells/ViewCellRenderer.cs b/Xamarin.Forms.Platform.Android/Cells/ViewCellRenderer.cs index 8134a9c1..48abb49b 100644 --- a/Xamarin.Forms.Platform.Android/Cells/ViewCellRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Cells/ViewCellRenderer.cs @@ -61,7 +61,7 @@ namespace Xamarin.Forms.Platform.Android _unevenRows = unevenRows; _rowHeight = rowHeight; _viewCell = viewCell; - AddView(view.ViewGroup); + AddView(view.View); UpdateIsEnabled(); } @@ -123,16 +123,16 @@ namespace Xamarin.Forms.Platform.Android return; } - RemoveView(_view.ViewGroup); + RemoveView(_view.View); Platform.SetRenderer(_viewCell.View, null); _viewCell.View.IsPlatformEnabled = false; - _view.ViewGroup.Dispose(); + _view.View.Dispose(); _viewCell = cell; _view = Platform.CreateRenderer(_viewCell.View); Platform.SetRenderer(_viewCell.View, _view); - AddView(_view.ViewGroup); + AddView(_view.View); UpdateIsEnabled(); diff --git a/Xamarin.Forms.Platform.Android/Extensions/ImageViewExtensions.cs b/Xamarin.Forms.Platform.Android/Extensions/ImageViewExtensions.cs new file mode 100644 index 00000000..f60acb7e --- /dev/null +++ b/Xamarin.Forms.Platform.Android/Extensions/ImageViewExtensions.cs @@ -0,0 +1,61 @@ +using System; +using System.Threading.Tasks; +using Android.Graphics; +using Java.IO; +using AImageView = Android.Widget.ImageView; + +namespace Xamarin.Forms.Platform.Android +{ + internal static class ImageViewExtensions + { + public static async void UpdateBitmap(this AImageView imageView, Image newImage, Image previousImage = null) + { + if (Device.IsInvokeRequired) + throw new InvalidOperationException("Image Bitmap must not be updated from background thread"); + + if (previousImage != null && Equals(previousImage.Source, newImage.Source)) + return; + + ((IImageController)newImage).SetIsLoading(true); + + (imageView as IImageRendererController).SkipInvalidate(); + + imageView.SetImageResource(global::Android.Resource.Color.Transparent); + + ImageSource source = newImage.Source; + Bitmap bitmap = null; + IImageSourceHandler handler; + + if (source != null && (handler = Internals.Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null) + { + try + { + bitmap = await handler.LoadImageAsync(source, imageView.Context); + } + catch (TaskCanceledException) + { + } + catch (IOException ex) + { + Internals.Log.Warning("Xamarin.Forms.Platform.Android.ImageRenderer", "Error updating bitmap: {0}", ex); + } + } + + if (newImage == null || !Equals(newImage.Source, source)) + { + bitmap?.Dispose(); + return; + } + + if (bitmap == null && source is FileImageSource) + imageView.SetImageResource(ResourceManager.GetDrawableByName(((FileImageSource)source).File)); + else + imageView.SetImageBitmap(bitmap); + + bitmap?.Dispose(); + + ((IImageController)newImage).SetIsLoading(false); + ((IVisualElementController)newImage).NativeSizeChanged(); + } + } +} diff --git a/Xamarin.Forms.Platform.Android/FastRenderers/AccessibilityProvider.cs b/Xamarin.Forms.Platform.Android/FastRenderers/AccessibilityProvider.cs new file mode 100644 index 00000000..29327dcd --- /dev/null +++ b/Xamarin.Forms.Platform.Android/FastRenderers/AccessibilityProvider.cs @@ -0,0 +1,214 @@ +using System; +using System.ComponentModel; +using Android.Widget; + +namespace Xamarin.Forms.Platform.Android.FastRenderers +{ + internal class AccessibilityProvider : IDisposable + { + const string GetFromElement = "GetValueFromElement"; + string _defaultContentDescription; + bool? _defaultFocusable; + string _defaultHint; + bool _disposed; + + IVisualElementRenderer _renderer; + + public AccessibilityProvider(IVisualElementRenderer renderer) + { + _renderer = renderer; + _renderer.ElementPropertyChanged += OnElementPropertyChanged; + _renderer.ElementChanged += OnElementChanged; + } + + global::Android.Views.View Control => _renderer?.View; + + VisualElement Element => _renderer?.Element; + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + _disposed = true; + + if (_renderer != null) + { + _renderer.ElementChanged -= OnElementChanged; + _renderer.ElementPropertyChanged -= OnElementPropertyChanged; + + _renderer = null; + } + } + + void SetAutomationId(string id = GetFromElement) + { + if (Element == null || Control == null) + { + return; + } + + string value = id; + if (value == GetFromElement) + { + value = Element.AutomationId; + } + + if (!string.IsNullOrEmpty(value)) + { + Control.ContentDescription = value; + } + } + + void SetContentDescription(string contentDescription = GetFromElement) + { + if (Element == null || Control == null) + { + return; + } + + if (SetHint()) + { + return; + } + + if (_defaultContentDescription == null) + { + _defaultContentDescription = Control.ContentDescription; + } + + string value = contentDescription; + if (value == GetFromElement) + { + value = string.Join(" ", (string)Element.GetValue(Accessibility.NameProperty), + (string)Element.GetValue(Accessibility.HintProperty)); + } + + if (!string.IsNullOrWhiteSpace(value)) + { + Control.ContentDescription = value; + } + else + { + Control.ContentDescription = _defaultContentDescription; + } + } + + void SetFocusable(bool? value = null) + { + if (Element == null || Control == null) + { + return; + } + + if (!_defaultFocusable.HasValue) + { + _defaultFocusable = Control.Focusable; + } + + Control.Focusable = + (bool)(value ?? (bool?)Element.GetValue(Accessibility.IsInAccessibleTreeProperty) ?? _defaultFocusable); + } + + bool SetHint(string hint = GetFromElement) + { + if (Element == null || Control == null) + { + return false; + } + + var textView = Control as TextView; + if (textView == null) + { + return false; + } + + // Let the specified Title/Placeholder take precedence, but don't set the ContentDescription (won't work anyway) + if (((Element as Picker)?.Title ?? (Element as Entry)?.Placeholder) != null) + { + return true; + } + + if (_defaultHint == null) + { + _defaultHint = textView.Hint; + } + + string value = hint; + if (value == GetFromElement) + { + value = string.Join(". ", (string)Element.GetValue(Accessibility.NameProperty), + (string)Element.GetValue(Accessibility.HintProperty)); + } + + textView.Hint = !string.IsNullOrWhiteSpace(value) ? value : _defaultHint; + + return true; + } + + void SetLabeledBy() + { + if (Element == null || Control == null) + return; + + var elemValue = (VisualElement)Element.GetValue(Accessibility.LabeledByProperty); + + if (elemValue != null) + { + var id = Control.Id; + if (id == -1) + id = Control.Id = FormsAppCompatActivity.GetUniqueId(); + + var renderer = elemValue?.GetRenderer(); + renderer?.SetLabelFor(id); + } + } + + void OnElementChanged(object sender, VisualElementChangedEventArgs e) + { + if (e.OldElement != null) + { + e.OldElement.PropertyChanged -= OnElementPropertyChanged; + } + + if (e.NewElement != null) + { + e.NewElement.PropertyChanged += OnElementPropertyChanged; + } + + SetHint(); + SetAutomationId(); + SetContentDescription(); + SetFocusable(); + SetLabeledBy(); + } + + void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == Accessibility.HintProperty.PropertyName) + { + SetContentDescription(); + } + else if (e.PropertyName == Accessibility.NameProperty.PropertyName) + { + SetContentDescription(); + } + else if (e.PropertyName == Accessibility.IsInAccessibleTreeProperty.PropertyName) + { + SetFocusable(); + } + else if (e.PropertyName == Accessibility.LabeledByProperty.PropertyName) + { + SetLabeledBy(); + } + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/FastRenderers/ButtonRenderer.cs b/Xamarin.Forms.Platform.Android/FastRenderers/ButtonRenderer.cs new file mode 100644 index 00000000..ae550a64 --- /dev/null +++ b/Xamarin.Forms.Platform.Android/FastRenderers/ButtonRenderer.cs @@ -0,0 +1,490 @@ +using System; +using System.ComponentModel; +using Android.Content; +using Android.Content.Res; +using Android.Graphics; +using Android.Graphics.Drawables; +using Android.Support.V7.Widget; +using Android.Util; +using Android.Views; +using Xamarin.Forms.Internals; +using GlobalResource = Android.Resource; +using AView = Android.Views.View; +using AMotionEvent = Android.Views.MotionEvent; +using AMotionEventActions = Android.Views.MotionEventActions; +using static System.String; +using Object = Java.Lang.Object; + +namespace Xamarin.Forms.Platform.Android.FastRenderers +{ + public class ButtonRenderer : AppCompatButton, IVisualElementRenderer, AView.IOnAttachStateChangeListener, + AView.IOnFocusChangeListener, IEffectControlProvider, AView.IOnClickListener, AView.IOnTouchListener + { + float _defaultFontSize; + int? _defaultLabelFor; + Typeface _defaultTypeface; + int _imageHeight = -1; + bool _isDisposed; + bool _inputTransparent; + readonly Lazy<TextColorSwitcher> _textColorSwitcher; + readonly AccessibilityProvider _accessibilityProvider; + readonly EffectControlProvider _effectControlProvider; + VisualElementTracker _tracker; + + public event EventHandler<VisualElementChangedEventArgs> ElementChanged; + public event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged; + + public ButtonRenderer() : base(Forms.Context) + { + _accessibilityProvider = new AccessibilityProvider(this); + _effectControlProvider = new EffectControlProvider(this); + _textColorSwitcher = new Lazy<TextColorSwitcher>(() => new TextColorSwitcher(TextColors)); + + Initialize(); + } + + public VisualElement Element => Button; + AView IVisualElementRenderer.View => this; + ViewGroup IVisualElementRenderer.ViewGroup => null; + VisualElementTracker IVisualElementRenderer.Tracker => _tracker; + + Button Button { get; set; } + + public void OnClick(AView v) + { + ((IButtonController)Button)?.SendClicked(); + } + + public bool OnTouch(AView v, MotionEvent e) + { + var buttonController = Element as IButtonController; + switch (e.Action) + { + case AMotionEventActions.Down: + buttonController?.SendPressed(); + break; + case AMotionEventActions.Up: + buttonController?.SendReleased(); + break; + } + + return false; + } + + void IEffectControlProvider.RegisterEffect(Effect effect) + { + _effectControlProvider.RegisterEffect(effect); + } + + void IOnAttachStateChangeListener.OnViewAttachedToWindow(AView attachedView) + { + UpdateText(); + } + + void IOnAttachStateChangeListener.OnViewDetachedFromWindow(AView detachedView) + { + } + + void IOnFocusChangeListener.OnFocusChange(AView v, bool hasFocus) + { + OnNativeFocusChanged(hasFocus); + + ((IElementController)Button).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, hasFocus); + } + + SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint) + { + UpdateText(); + + AView view = this; + view.Measure(widthConstraint, heightConstraint); + + return new SizeRequest(new Size(MeasuredWidth, MeasuredHeight), MinimumSize()); + } + + void IVisualElementRenderer.SetElement(VisualElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + if (!(element is Button)) + { + throw new ArgumentException($"{nameof(element)} must be of type {nameof(Button)}"); + } + + VisualElement oldElement = Button; + Button = (Button)element; + + Performance.Start(); + + if (oldElement != null) + { + oldElement.PropertyChanged -= OnElementPropertyChanged; + } + + Color currentColor = oldElement?.BackgroundColor ?? Color.Default; + if (element.BackgroundColor != currentColor) + { + UpdateBackgroundColor(); + } + + element.PropertyChanged += OnElementPropertyChanged; + + if (_tracker == null) + { + // Can't set up the tracker in the constructor because it access the Element (for now) + SetTracker(new VisualElementTracker(this)); + } + + OnElementChanged(new ElementChangedEventArgs<Button>(oldElement as Button, Button)); + + SendVisualElementInitialized(element, this); + + EffectUtilities.RegisterEffectControlProvider(this, oldElement, element); + + Performance.Stop(); + } + + void IVisualElementRenderer.SetLabelFor(int? id) + { + if (_defaultLabelFor == null) + { + _defaultLabelFor = LabelFor; + } + + LabelFor = (int)(id ?? _defaultLabelFor); + } + + void IVisualElementRenderer.UpdateLayout() + { + Performance.Start(); + _tracker?.UpdateLayout(); + Performance.Stop(); + } + + protected override void Dispose(bool disposing) + { + if (_isDisposed) + { + return; + } + + _isDisposed = true; + + if (disposing) + { + SetOnClickListener(null); + SetOnTouchListener(null); + RemoveOnAttachStateChangeListener(this); + + _accessibilityProvider?.Dispose(); + _tracker?.Dispose(); + + if (Element != null) + { + Element.PropertyChanged -= OnElementPropertyChanged; + } + } + + base.Dispose(disposing); + } + + public override bool OnTouchEvent(MotionEvent e) + { + if (!Enabled || (_inputTransparent && Enabled)) + return false; + + return base.OnTouchEvent(e); + } + + protected virtual Size MinimumSize() + { + return new Size(); + } + + protected virtual void OnElementChanged(ElementChangedEventArgs<Button> e) + { + if (e.NewElement != null) + { + UpdateFont(); + UpdateText(); + UpdateBitmap(); + UpdateTextColor(); + UpdateIsEnabled(); + UpdateInputTransparent(); + UpdateBackgroundColor(); + } + + ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement)); + } + + protected void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == Button.TextProperty.PropertyName) + { + UpdateText(); + } + else if (e.PropertyName == Button.TextColorProperty.PropertyName) + { + UpdateTextColor(); + } + else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName) + { + UpdateIsEnabled(); + } + else if (e.PropertyName == Button.FontProperty.PropertyName) + { + UpdateFont(); + } + else if (e.PropertyName == Button.ImageProperty.PropertyName) + { + UpdateBitmap(); + } + else if (e.PropertyName == VisualElement.IsVisibleProperty.PropertyName) + { + UpdateText(); + } + else if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName) + { + UpdateInputTransparent(); + } + else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) + { + UpdateBackgroundColor(); + } + + ElementPropertyChanged?.Invoke(this, e); + } + + protected override void OnLayout(bool changed, int l, int t, int r, int b) + { + if (Element == null) + { + return; + } + + if (_imageHeight > -1) + { + // We've got an image (and no text); it's already centered horizontally, + // we just need to adjust the padding so it centers vertically + int diff = (b - t - _imageHeight) / 2; + diff = Math.Max(diff, 0); + SetPadding(0, diff, 0, -diff); + } + + base.OnLayout(changed, l, t, r, b); + } + + protected void SetTracker(VisualElementTracker tracker) + { + _tracker = tracker; + } + + protected void UpdateBackgroundColor() + { + if (Element == null) + { + return; + } + + Color backgroundColor = Element.BackgroundColor; + if (backgroundColor.IsDefault) + { + if (SupportBackgroundTintList != null) + { + Context context = Context; + int id = GlobalResource.Attribute.ButtonTint; + unchecked + { + using (var value = new TypedValue()) + { + try + { + Resources.Theme theme = context.Theme; + if (theme != null && theme.ResolveAttribute(id, value, true)) +#pragma warning disable 618 + { + SupportBackgroundTintList = Resources.GetColorStateList(value.Data); + } +#pragma warning restore 618 + else + { + SupportBackgroundTintList = new ColorStateList(ColorExtensions.States, + new[] { (int)0xffd7d6d6, 0x7fd7d6d6 }); + } + } + catch (Exception ex) + { + Internals.Log.Warning("Xamarin.Forms.Platform.Android.ButtonRenderer", + "Could not retrieve button background resource: {0}", ex); + SupportBackgroundTintList = new ColorStateList(ColorExtensions.States, + new[] { (int)0xffd7d6d6, 0x7fd7d6d6 }); + } + } + } + } + } + else + { + int intColor = backgroundColor.ToAndroid().ToArgb(); + int disableColor = backgroundColor.MultiplyAlpha(0.5).ToAndroid().ToArgb(); + SupportBackgroundTintList = new ColorStateList(ColorExtensions.States, new[] { intColor, disableColor }); + } + } + + internal virtual void OnNativeFocusChanged(bool hasFocus) + { + } + + internal virtual void SendVisualElementInitialized(VisualElement element, AView nativeView) + { + element.SendViewInitialized(nativeView); + } + + void Initialize() + { + // Fixes issues with AppCompatButton elevation (z-index) + StateListAnimator = null; + + SoundEffectsEnabled = false; + SetOnClickListener(this); + SetOnTouchListener(this); + AddOnAttachStateChangeListener(this); + OnFocusChangeListener = this; + + Tag = this; + } + + void UpdateBitmap() + { + if (Element == null) + { + return; + } + + FileImageSource elementImage = Button.Image; + string imageFile = elementImage?.File; + _imageHeight = -1; + + if (elementImage == null || IsNullOrEmpty(imageFile)) + { + SetCompoundDrawablesWithIntrinsicBounds(null, null, null, null); + return; + } + + Drawable image = Context.Resources.GetDrawable(imageFile); + + if (IsNullOrEmpty(Button.Text)) + { + // No text, so no need for relative position; just center the image + // There's no option for just plain-old centering, so we'll use Top + // (which handles the horizontal centering) and some tricksy padding (in OnLayout) + // to handle the vertical centering + + // Clear any previous padding and set the image as top/center + SetPadding(0, 0, 0, 0); + SetCompoundDrawablesWithIntrinsicBounds(null, image, null, null); + + // Keep track of the image height so we can use it in OnLayout + _imageHeight = image.IntrinsicHeight; + + image.Dispose(); + return; + } + + Button.ButtonContentLayout layout = Button.ContentLayout; + + CompoundDrawablePadding = (int)layout.Spacing; + + switch (layout.Position) + { + case Button.ButtonContentLayout.ImagePosition.Top: + SetCompoundDrawablesWithIntrinsicBounds(null, image, null, null); + break; + case Button.ButtonContentLayout.ImagePosition.Bottom: + SetCompoundDrawablesWithIntrinsicBounds(null, null, null, image); + break; + case Button.ButtonContentLayout.ImagePosition.Right: + SetCompoundDrawablesWithIntrinsicBounds(null, null, image, null); + break; + default: + // Defaults to image on the left + SetCompoundDrawablesWithIntrinsicBounds(image, null, null, null); + break; + } + + image?.Dispose(); + } + + void UpdateFont() + { + if (Element == null) + { + return; + } + + Font font = Button.Font; + + if (font == Font.Default && _defaultFontSize == 0f) + { + return; + } + + if (_defaultFontSize == 0f) + { + _defaultTypeface = Typeface; + _defaultFontSize = TextSize; + } + + if (font == Font.Default) + { + Typeface = _defaultTypeface; + SetTextSize(ComplexUnitType.Px, _defaultFontSize); + } + else + { + Typeface = font.ToTypeface(); + SetTextSize(ComplexUnitType.Sp, font.ToScaledPixel()); + } + } + + void UpdateIsEnabled() + { + Enabled = Element.IsEnabled; + } + + void UpdateInputTransparent() + { + _inputTransparent = Element.InputTransparent; + } + + void UpdateText() + { + if (Element == null) + { + return; + } + + string oldText = Text; + Text = Button.Text; + + // If we went from or to having no text, we need to update the image position + if (IsNullOrEmpty(oldText) != IsNullOrEmpty(Text)) + { + UpdateBitmap(); + } + } + + void UpdateTextColor() + { + if (Element == null) + { + return; + } + + _textColorSwitcher.Value.UpdateTextColor(this, Button.TextColor); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/FastRenderers/EffectControlProvider.cs b/Xamarin.Forms.Platform.Android/FastRenderers/EffectControlProvider.cs new file mode 100644 index 00000000..ebb9fa95 --- /dev/null +++ b/Xamarin.Forms.Platform.Android/FastRenderers/EffectControlProvider.cs @@ -0,0 +1,35 @@ +using Android.Views; +using AView = Android.Views.View; + +namespace Xamarin.Forms.Platform.Android.FastRenderers +{ + internal class EffectControlProvider : IEffectControlProvider + { + readonly AView _control; + readonly ViewGroup _container; + + public EffectControlProvider(AView control) + { + _control = control; + _container = null; + } + + public EffectControlProvider(AView control, ViewGroup container) + { + _control = control; + _container = container; + } + + public void RegisterEffect(Effect effect) + { + var platformEffect = effect as PlatformEffect; + if (platformEffect == null) + { + return; + } + + platformEffect.SetControl(_control); + platformEffect.SetContainer(_container); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/FastRenderers/FrameRenderer.cs b/Xamarin.Forms.Platform.Android/FastRenderers/FrameRenderer.cs new file mode 100644 index 00000000..14b48812 --- /dev/null +++ b/Xamarin.Forms.Platform.Android/FastRenderers/FrameRenderer.cs @@ -0,0 +1,234 @@ +using System; +using System.ComponentModel; +using Android.Content; +using Android.Support.V7.Widget; +using Android.Views; +using Xamarin.Forms.Platform.Android.FastRenderers; +using AColor = Android.Graphics.Color; +using AView = Android.Views.View; + +namespace Xamarin.Forms.Platform.Android.FastRenderers +{ + public class FrameRenderer : CardView, IVisualElementRenderer, IEffectControlProvider + { + float _defaultElevation = -1f; + float _defaultCornerRadius = -1f; + int? _defaultLabelFor; + + bool _disposed; + Frame _element; + + VisualElementPackager _visualElementPackager; + VisualElementTracker _visualElementTracker; + + readonly GestureManager _gestureManager; + readonly EffectControlProvider _effectControlProvider; + + public event EventHandler<VisualElementChangedEventArgs> ElementChanged; + public event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged; + + public FrameRenderer() : base(Forms.Context) + { + _gestureManager = new GestureManager(this); + _effectControlProvider = new EffectControlProvider(this); + } + + protected CardView Control => this; + + protected Frame Element + { + get { return _element; } + set + { + if (_element == value) + return; + + Frame oldElement = _element; + _element = value; + + OnElementChanged(new ElementChangedEventArgs<Frame>(oldElement, _element)); + + _element?.SendViewInitialized(Control); + } + } + + VisualElement IVisualElementRenderer.Element => Element; + ViewGroup IVisualElementRenderer.ViewGroup => this; + AView IVisualElementRenderer.View => this; + + 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; + } + + void IVisualElementRenderer.SetLabelFor(int? id) + { + if (_defaultLabelFor == null) + _defaultLabelFor = LabelFor; + + LabelFor = (int)(id ?? _defaultLabelFor); + } + + VisualElementTracker IVisualElementRenderer.Tracker => _visualElementTracker; + + void IVisualElementRenderer.UpdateLayout() + { + VisualElementTracker tracker = _visualElementTracker; + tracker?.UpdateLayout(); + } + + void IEffectControlProvider.RegisterEffect(Effect effect) + { + _effectControlProvider.RegisterEffect(effect); + } + + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + if (disposing) + { + _gestureManager?.Dispose(); + + 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; + } + + } + + base.Dispose(disposing); + } + + protected virtual void OnElementChanged(ElementChangedEventArgs<Frame> e) + { + ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement)); + + if (e.OldElement != null) + { + e.OldElement.PropertyChanged -= OnElementPropertyChanged; + } + + if (e.NewElement != null) + { + if (_visualElementTracker == null) + { + _visualElementTracker = new VisualElementTracker(this); + _visualElementPackager = new VisualElementPackager(this); + _visualElementPackager.Load(); + } + + e.NewElement.PropertyChanged += OnElementPropertyChanged; + UpdateShadow(); + UpdateBackgroundColor(); + UpdateCornerRadius(); + } + } + + 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(); + } + } + + public override bool OnTouchEvent(MotionEvent e) + { + bool handled; + var result = _gestureManager.OnTouchEvent(e, Parent, out handled); + + return handled ? result : base.OnTouchEvent(e); + } + + void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + ElementPropertyChanged?.Invoke(this, e); + + if (e.PropertyName == Frame.HasShadowProperty.PropertyName) + UpdateShadow(); + else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) + UpdateBackgroundColor(); + else if (e.PropertyName == Frame.CornerRadiusProperty.PropertyName) + UpdateCornerRadius(); + } + + void UpdateBackgroundColor() + { + Color bgColor = Element.BackgroundColor; + SetCardBackgroundColor(bgColor.IsDefault ? AColor.White : bgColor.ToAndroid()); + } + + void UpdateShadow() + { + float elevation = _defaultElevation; + + if (elevation == -1f) + _defaultElevation = elevation = CardElevation; + + if (Element.HasShadow) + CardElevation = elevation; + else + CardElevation = 0f; + } + + void UpdateCornerRadius() + { + if (_defaultCornerRadius == -1f) + { + _defaultCornerRadius = Radius; + } + + float cornerRadius = Element.CornerRadius; + + if (cornerRadius == -1f) + cornerRadius = _defaultCornerRadius; + else + cornerRadius = Context.ToPixels(cornerRadius); + + Radius = cornerRadius; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/FastRenderers/GestureManager.cs b/Xamarin.Forms.Platform.Android/FastRenderers/GestureManager.cs new file mode 100644 index 00000000..8335b9d7 --- /dev/null +++ b/Xamarin.Forms.Platform.Android/FastRenderers/GestureManager.cs @@ -0,0 +1,263 @@ +using System; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using Android.Support.V4.View; +using Android.Views; +using Object = Java.Lang.Object; + +namespace Xamarin.Forms.Platform.Android.FastRenderers +{ + public class GestureManager : Object, global::Android.Views.View.IOnClickListener, global::Android.Views.View.IOnTouchListener + { + IVisualElementRenderer _renderer; + readonly Lazy<GestureDetector> _gestureDetector; + readonly PanGestureHandler _panGestureHandler; + readonly PinchGestureHandler _pinchGestureHandler; + readonly Lazy<ScaleGestureDetector> _scaleDetector; + readonly TapGestureHandler _tapGestureHandler; + readonly MotionEventHelper _motionEventHelper = new MotionEventHelper(); + InnerGestureListener _gestureListener; + + bool _clickable; + bool _disposed; + bool _inputTransparent; + bool _isEnabled; + + NotifyCollectionChangedEventHandler _collectionChangeHandler; + + VisualElement Element => _renderer?.Element; + + View View => _renderer?.Element as View; + + global::Android.Views.View Control => _renderer?.View; + + public GestureManager(IVisualElementRenderer renderer) + { + _renderer = renderer; + _renderer.ElementChanged += OnElementChanged; + + _tapGestureHandler = new TapGestureHandler(() => View); + _panGestureHandler = new PanGestureHandler(() => View, Control.Context.FromPixels); + _pinchGestureHandler = new PinchGestureHandler(() => View); + _gestureDetector = + new Lazy<GestureDetector>( + () => + new GestureDetector( + _gestureListener = + new InnerGestureListener(_tapGestureHandler.OnTap, _tapGestureHandler.TapGestureRecognizers, + _panGestureHandler.OnPan, _panGestureHandler.OnPanStarted, _panGestureHandler.OnPanComplete))); + + _scaleDetector = + new Lazy<ScaleGestureDetector>( + () => + new ScaleGestureDetector(Control.Context, + new InnerScaleListener(_pinchGestureHandler.OnPinch, _pinchGestureHandler.OnPinchStarted, + _pinchGestureHandler.OnPinchEnded), Control.Handler)); + + Control.SetOnClickListener(this); + Control.SetOnTouchListener(this); + } + + public bool OnTouchEvent(MotionEvent e, IViewParent parent, out bool handled) + { + if (_inputTransparent) + { + handled = true; + return false; + } + + if (View.GestureRecognizers.Count == 0) + { + handled = true; + return _motionEventHelper.HandleMotionEvent(parent); + } + + handled = false; + return false; + } + + void OnElementChanged(object sender, VisualElementChangedEventArgs e) + { + if (e.OldElement != null) + { + UnsubscribeGestureRecognizers(e.OldElement); + e.OldElement.PropertyChanged -= OnElementPropertyChanged; + } + + if (e.NewElement != null) + { + UpdateGestureRecognizers(true); + SubscribeGestureRecognizers(e.NewElement); + _motionEventHelper.UpdateElement(e.NewElement); + e.NewElement.PropertyChanged += OnElementPropertyChanged; + } + + UpdateInputTransparent(); + UpdateIsEnabled(); + } + + void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName) + UpdateInputTransparent(); + else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName) + UpdateIsEnabled(); + } + + protected override void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + _disposed = true; + + if (disposing) + { + if (Element != null) + { + Element.PropertyChanged -= OnElementPropertyChanged; + } + + Control.SetOnClickListener(null); + Control.SetOnTouchListener(null); + + if (_gestureListener != null) + { + _gestureListener.Dispose(); + _gestureListener = null; + } + + if (_renderer?.Element != null) + { + UnsubscribeGestureRecognizers(Element); + } + + _renderer = null; + } + + base.Dispose(disposing); + } + + void global::Android.Views.View.IOnClickListener.OnClick(global::Android.Views.View v) + { + _tapGestureHandler.OnSingleClick(); + } + + bool global::Android.Views.View.IOnTouchListener.OnTouch(global::Android.Views.View v, MotionEvent e) + { + if (!_isEnabled) + return true; + + if (_inputTransparent) + return false; + + var handled = false; + if (_pinchGestureHandler.IsPinchSupported) + { + if (!_scaleDetector.IsValueCreated) + ScaleGestureDetectorCompat.SetQuickScaleEnabled(_scaleDetector.Value, true); + handled = _scaleDetector.Value.OnTouchEvent(e); + } + + if (_gestureDetector.IsValueCreated && _gestureDetector.Value.Handle == IntPtr.Zero) + { + // This gesture detector has already been disposed, probably because it's on a cell which is going away + return handled; + } + + // It's very important that the gesture detection happen first here + // if we check handled first, we might short-circuit and never check for tap/pan + return _gestureDetector.Value.OnTouchEvent(e) || handled; + } + + void HandleGestureRecognizerCollectionChanged(object sender, + NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs) + { + UpdateGestureRecognizers(); + } + + void SubscribeGestureRecognizers(VisualElement element) + { + var view = element as View; + if (view == null) + { + return; + } + + if (_collectionChangeHandler == null) + { + _collectionChangeHandler = HandleGestureRecognizerCollectionChanged; + } + + var observableCollection = (ObservableCollection<IGestureRecognizer>)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<IGestureRecognizer>)view.GestureRecognizers; + if (observableCollection != null) + { + observableCollection.CollectionChanged -= _collectionChangeHandler; + } + } + + void UpdateClickable(bool force = false) + { + var view = Element as View; + if (view == null) + { + return; + } + + bool newValue = view.ShouldBeMadeClickable(); + if (force || _clickable != newValue) + { + Control.Clickable = newValue; + _clickable = newValue; + } + } + + void UpdateGestureRecognizers(bool forceClick = false) + { + if (Element == null) + { + return; + } + + UpdateClickable(forceClick); + } + + void UpdateInputTransparent() + { + if (Element == null) + { + return; + } + + _inputTransparent = Element.InputTransparent; + } + + void UpdateIsEnabled() + { + if (Element == null) + { + return; + } + + _isEnabled = Element.IsEnabled; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/FastRenderers/ImageRenderer.cs b/Xamarin.Forms.Platform.Android/FastRenderers/ImageRenderer.cs new file mode 100644 index 00000000..6a43d193 --- /dev/null +++ b/Xamarin.Forms.Platform.Android/FastRenderers/ImageRenderer.cs @@ -0,0 +1,163 @@ +using System; +using System.ComponentModel; +using AImageView = Android.Widget.ImageView; +using AView = Android.Views.View; +using Android.Views; + +namespace Xamarin.Forms.Platform.Android.FastRenderers +{ + public class ImageRenderer : AImageView, IVisualElementRenderer, IImageRendererController + { + bool _disposed; + Image _element; + bool _skipInvalidate; + int? _defaultLabelFor; + VisualElementTracker _visualElementTracker; + VisualElementRenderer _visualElementRenderer; + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (_disposed) + return; + + _disposed = true; + + if (!disposing) + return; + + if (_visualElementTracker != null) + { + _visualElementTracker.Dispose(); + _visualElementTracker = null; + } + + if (_visualElementRenderer != null) + { + _visualElementRenderer.Dispose(); + _visualElementRenderer = null; + } + + if (_element != null) + _element.PropertyChanged -= OnElementPropertyChanged; + } + + public override void Invalidate() + { + if (_skipInvalidate) + { + _skipInvalidate = false; + return; + } + + base.Invalidate(); + } + + protected virtual void OnElementChanged(ElementChangedEventArgs<Image> e) + { + this.UpdateBitmap(e.NewElement, e.OldElement); + UpdateAspect(); + + ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement)); + } + + public override bool OnTouchEvent(MotionEvent e) + { + bool handled; + var result = _visualElementRenderer.OnTouchEvent(e, Parent, out handled); + + return handled ? result : base.OnTouchEvent(e); + } + + protected virtual Size MinimumSize() + { + return new Size(); + } + + SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint) + { + Measure(widthConstraint, heightConstraint); + return new SizeRequest(new Size(MeasuredWidth, MeasuredHeight), MinimumSize()); + } + + void IVisualElementRenderer.SetElement(VisualElement element) + { + if (element == null) + throw new ArgumentNullException(nameof(element)); + + var image = element as Image; + if (image == null) + throw new ArgumentException("Element is not of type " + typeof(Image), nameof(element)); + + Image oldElement = _element; + _element = image; + + Internals.Performance.Start(); + + if (oldElement != null) + oldElement.PropertyChanged -= OnElementPropertyChanged; + + element.PropertyChanged += OnElementPropertyChanged; + + if (_visualElementTracker == null) + _visualElementTracker = new VisualElementTracker(this); + + if (_visualElementRenderer == null) + { + _visualElementRenderer = new VisualElementRenderer(this); + } + + Internals.Performance.Stop(); + + OnElementChanged(new ElementChangedEventArgs<Image>(oldElement, _element)); + + _element?.SendViewInitialized(Control); + } + + void IVisualElementRenderer.SetLabelFor(int? id) + { + if (_defaultLabelFor == null) + _defaultLabelFor = LabelFor; + + LabelFor = (int)(id ?? _defaultLabelFor); + } + + void IVisualElementRenderer.UpdateLayout() => _visualElementTracker?.UpdateLayout(); + + VisualElement IVisualElementRenderer.Element => _element; + + VisualElementTracker IVisualElementRenderer.Tracker => _visualElementTracker; + + AView IVisualElementRenderer.View => this; + + ViewGroup IVisualElementRenderer.ViewGroup => null; + + void IImageRendererController.SkipInvalidate() => _skipInvalidate = true; + + protected AImageView Control => this; + + public event EventHandler<VisualElementChangedEventArgs> ElementChanged; + public event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged; + + public ImageRenderer() : base(Forms.Context) + { + } + + protected void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == Image.SourceProperty.PropertyName) + this.UpdateBitmap(_element); + else if (e.PropertyName == Image.AspectProperty.PropertyName) + UpdateAspect(); + + ElementPropertyChanged?.Invoke(this, e); + } + + void UpdateAspect() + { + ScaleType type = _element.Aspect.ToScaleType(); + SetScaleType(type); + } + } +} diff --git a/Xamarin.Forms.Platform.Android/FastRenderers/LabelRenderer.cs b/Xamarin.Forms.Platform.Android/FastRenderers/LabelRenderer.cs new file mode 100644 index 00000000..b2f2d827 --- /dev/null +++ b/Xamarin.Forms.Platform.Android/FastRenderers/LabelRenderer.cs @@ -0,0 +1,313 @@ +using System; +using System.ComponentModel; +using Android.Content.Res; +using Android.Graphics; +using Android.Text; +using Android.Util; +using Android.Views; +using AView = Android.Views.View; + +namespace Xamarin.Forms.Platform.Android.FastRenderers +{ + public class LabelRenderer : FormsTextView, IVisualElementRenderer + { + int? _defaultLabelFor; + bool _disposed; + Label _element; + readonly ColorStateList _labelTextColorDefault; + int _lastConstraintHeight; + int _lastConstraintWidth; + SizeRequest? _lastSizeRequest; + float _lastTextSize = -1f; + Typeface _lastTypeface; + Color _lastUpdateColor = Color.Default; + VisualElementTracker _visualElementTracker; + VisualElementRenderer _visualElementRenderer; + + bool _wasFormatted; + + public LabelRenderer() : base(Forms.Context) + { + _labelTextColorDefault = TextColors; + _visualElementRenderer = new VisualElementRenderer(this); + } + + public event EventHandler<VisualElementChangedEventArgs> ElementChanged; + public event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged; + + VisualElement IVisualElementRenderer.Element => Element; + + VisualElementTracker IVisualElementRenderer.Tracker => _visualElementTracker; + + AView IVisualElementRenderer.View => this; + + ViewGroup IVisualElementRenderer.ViewGroup => null; + + protected Label Element + { + get { return _element; } + set + { + if (_element == value) + return; + + Label oldElement = _element; + _element = value; + + OnElementChanged(new ElementChangedEventArgs<Label>(oldElement, _element)); + + _element?.SendViewInitialized(this); + } + } + + SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint) + { + if (_lastSizeRequest.HasValue) + { + // if we are measuring the same thing, no need to waste the time + bool canRecycleLast = widthConstraint == _lastConstraintWidth && heightConstraint == _lastConstraintHeight; + + if (!canRecycleLast) + { + // if the last time we measured the returned size was all around smaller than the passed constraint + // and the constraint is bigger than the last size request, we can assume the newly measured size request + // will not change either. + int lastConstraintWidthSize = MeasureSpecFactory.GetSize(_lastConstraintWidth); + int lastConstraintHeightSize = MeasureSpecFactory.GetSize(_lastConstraintHeight); + + int currentConstraintWidthSize = MeasureSpecFactory.GetSize(widthConstraint); + int currentConstraintHeightSize = MeasureSpecFactory.GetSize(heightConstraint); + + bool lastWasSmallerThanConstraints = _lastSizeRequest.Value.Request.Width < lastConstraintWidthSize && _lastSizeRequest.Value.Request.Height < lastConstraintHeightSize; + + bool currentConstraintsBiggerThanLastRequest = currentConstraintWidthSize >= _lastSizeRequest.Value.Request.Width && currentConstraintHeightSize >= _lastSizeRequest.Value.Request.Height; + + canRecycleLast = lastWasSmallerThanConstraints && currentConstraintsBiggerThanLastRequest; + } + + if (canRecycleLast) + return _lastSizeRequest.Value; + } + + Measure(widthConstraint, heightConstraint); + SizeRequest result = new SizeRequest(new Size(MeasuredWidth, MeasuredHeight), new Size()); + result.Minimum = new Size(Math.Min(Context.ToPixels(10), result.Request.Width), result.Request.Height); + + _lastConstraintWidth = widthConstraint; + _lastConstraintHeight = heightConstraint; + _lastSizeRequest = result; + + return result; + } + + void IVisualElementRenderer.SetElement(VisualElement element) + { + var label = element as Label; + if (label == null) + throw new ArgumentException("Element must be of type Label"); + + Element = label; + } + + void IVisualElementRenderer.SetLabelFor(int? id) + { + if (_defaultLabelFor == null) + _defaultLabelFor = LabelFor; + + LabelFor = (int)(id ?? _defaultLabelFor); + } + + void IVisualElementRenderer.UpdateLayout() + { + VisualElementTracker tracker = _visualElementTracker; + tracker?.UpdateLayout(); + } + + protected override void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + if (disposing) + { + _disposed = true; + + if (_visualElementTracker != null) + { + _visualElementTracker.Dispose(); + _visualElementTracker = null; + } + + if (_visualElementRenderer != null) + { + _visualElementRenderer.Dispose(); + _visualElementRenderer = null; + } + + if (Element != null) + { + Element.PropertyChanged -= OnElementPropertyChanged; + } + } + + base.Dispose(disposing); + } + + public override bool OnTouchEvent(MotionEvent e) + { + bool handled; + var result = _visualElementRenderer.OnTouchEvent(e, Parent, out handled); + + return handled ? result : base.OnTouchEvent(e); + } + + protected virtual void OnElementChanged(ElementChangedEventArgs<Label> e) + { + ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement)); + + if (e.OldElement != null) + { + e.OldElement.PropertyChanged -= OnElementPropertyChanged; + } + + if (e.NewElement != null) + { + if (_visualElementTracker == null) + { + _visualElementTracker = new VisualElementTracker(this); + } + + e.NewElement.PropertyChanged += OnElementPropertyChanged; + + SkipNextInvalidate(); + UpdateText(); + if (e.OldElement?.LineBreakMode != e.NewElement.LineBreakMode) + UpdateLineBreakMode(); + if (e.OldElement?.HorizontalTextAlignment != e.NewElement.HorizontalTextAlignment + || e.OldElement?.VerticalTextAlignment != e.NewElement.VerticalTextAlignment) + UpdateGravity(); + } + } + + void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + ElementPropertyChanged?.Invoke(this, e); + + if (e.PropertyName == Label.HorizontalTextAlignmentProperty.PropertyName || e.PropertyName == Label.VerticalTextAlignmentProperty.PropertyName) + UpdateGravity(); + else if (e.PropertyName == Label.TextColorProperty.PropertyName) + UpdateText(); + else if (e.PropertyName == Label.FontProperty.PropertyName) + UpdateText(); + else if (e.PropertyName == Label.LineBreakModeProperty.PropertyName) + UpdateLineBreakMode(); + else if (e.PropertyName == Label.TextProperty.PropertyName || e.PropertyName == Label.FormattedTextProperty.PropertyName) + UpdateText(); + } + + void UpdateColor() + { + Color c = Element.TextColor; + if (c == _lastUpdateColor) + return; + _lastUpdateColor = c; + + if (c.IsDefault) + SetTextColor(_labelTextColorDefault); + else + SetTextColor(c.ToAndroid()); + } + + void UpdateFont() + { +#pragma warning disable 618 // We will need to update this when .Font goes away + Font f = Element.Font; +#pragma warning restore 618 + + Typeface newTypeface = f.ToTypeface(); + if (newTypeface != _lastTypeface) + { + Typeface = newTypeface; + _lastTypeface = newTypeface; + } + + float newTextSize = f.ToScaledPixel(); + if (newTextSize != _lastTextSize) + { + SetTextSize(ComplexUnitType.Sp, newTextSize); + _lastTextSize = newTextSize; + } + } + + void UpdateGravity() + { + Label label = Element; + + Gravity = label.HorizontalTextAlignment.ToHorizontalGravityFlags() | label.VerticalTextAlignment.ToVerticalGravityFlags(); + + _lastSizeRequest = null; + } + + void UpdateLineBreakMode() + { + SetSingleLine(false); + switch (Element.LineBreakMode) + { + case LineBreakMode.NoWrap: + SetMaxLines(1); + Ellipsize = null; + break; + case LineBreakMode.WordWrap: + Ellipsize = null; + SetMaxLines(100); + break; + case LineBreakMode.CharacterWrap: + Ellipsize = null; + SetMaxLines(100); + break; + case LineBreakMode.HeadTruncation: + SetMaxLines(1); + Ellipsize = TextUtils.TruncateAt.Start; + break; + case LineBreakMode.TailTruncation: + SetMaxLines(1); + Ellipsize = TextUtils.TruncateAt.End; + break; + case LineBreakMode.MiddleTruncation: + SetMaxLines(1); + Ellipsize = TextUtils.TruncateAt.Middle; + break; + } + _lastSizeRequest = null; + } + + void UpdateText() + { + if (Element.FormattedText != null) + { + FormattedString formattedText = Element.FormattedText ?? Element.Text; +#pragma warning disable 618 // We will need to update this when .Font goes away + TextFormatted = formattedText.ToAttributed(Element.Font, Element.TextColor, this); +#pragma warning restore 618 + _wasFormatted = true; + } + else + { + if (_wasFormatted) + { + SetTextColor(_labelTextColorDefault); + _lastUpdateColor = Color.Default; + } + Text = Element.Text; + UpdateColor(); + UpdateFont(); + + _wasFormatted = false; + } + + _lastSizeRequest = null; + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/FastRenderers/VisualElementRenderer.cs b/Xamarin.Forms.Platform.Android/FastRenderers/VisualElementRenderer.cs new file mode 100644 index 00000000..d94cfd01 --- /dev/null +++ b/Xamarin.Forms.Platform.Android/FastRenderers/VisualElementRenderer.cs @@ -0,0 +1,98 @@ +using System; +using System.ComponentModel; +using Android.Views; +using AView = Android.Views.View; +using Object = Java.Lang.Object; + +namespace Xamarin.Forms.Platform.Android.FastRenderers +{ + // TODO hartez 2017/03/03 14:11:17 It's weird that this class is called VisualElementRenderer but it doesn't implement that interface. The name should probably be different. + public class VisualElementRenderer : IDisposable, IEffectControlProvider + { + bool _disposed; + + IVisualElementRenderer _renderer; + readonly GestureManager _gestureManager; + readonly AccessibilityProvider _accessibilityProvider; + readonly EffectControlProvider _effectControlProvider; + + public VisualElementRenderer(IVisualElementRenderer renderer) + { + _renderer = renderer; + _renderer.ElementPropertyChanged += OnElementPropertyChanged; + _renderer.ElementChanged += OnElementChanged; + _gestureManager = new GestureManager(_renderer); + _accessibilityProvider = new AccessibilityProvider(_renderer); + _effectControlProvider = new EffectControlProvider(_renderer?.View); + } + + VisualElement Element => _renderer?.Element; + + AView Control => _renderer?.View; + + void IEffectControlProvider.RegisterEffect(Effect effect) + { + _effectControlProvider.RegisterEffect(effect); + } + + public void UpdateBackgroundColor(Color? color = null) + { + if (Element == null || Control == null) + return; + + Control.SetBackgroundColor((color ?? Element.BackgroundColor).ToAndroid()); + } + + public bool OnTouchEvent(MotionEvent e, IViewParent parent, out bool handled) + { + return _gestureManager.OnTouchEvent(e, parent, out handled); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected void Dispose(bool disposing) + { + if (_disposed) + return; + + _disposed = true; + + if (disposing) + { + _gestureManager?.Dispose(); + _accessibilityProvider?.Dispose(); + + if (_renderer != null) + { + _renderer.ElementChanged -= OnElementChanged; + _renderer.ElementPropertyChanged -= OnElementPropertyChanged; + _renderer = null; + } + } + } + + void OnElementChanged(object sender, VisualElementChangedEventArgs e) + { + if (e.OldElement != null) + { + e.OldElement.PropertyChanged -= OnElementPropertyChanged; + } + + if (e.NewElement != null) + { + e.NewElement.PropertyChanged += OnElementPropertyChanged; + UpdateBackgroundColor(); + } + } + + void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) + UpdateBackgroundColor(); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/IVisualElementRenderer.cs b/Xamarin.Forms.Platform.Android/IVisualElementRenderer.cs index da565ce2..71817db2 100644 --- a/Xamarin.Forms.Platform.Android/IVisualElementRenderer.cs +++ b/Xamarin.Forms.Platform.Android/IVisualElementRenderer.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using Android.Views; using AView = Android.Views.View; @@ -10,10 +11,15 @@ namespace Xamarin.Forms.Platform.Android VisualElementTracker Tracker { get; } + [Obsolete("Use View instead")] ViewGroup ViewGroup { get; } + AView View { get; } + event EventHandler<VisualElementChangedEventArgs> ElementChanged; + event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged; + SizeRequest GetDesiredSize(int widthConstraint, int heightConstraint); void SetElement(VisualElement element); diff --git a/Xamarin.Forms.Platform.Android/Platform.cs b/Xamarin.Forms.Platform.Android/Platform.cs index 004531a1..3f969091 100644 --- a/Xamarin.Forms.Platform.Android/Platform.cs +++ b/Xamarin.Forms.Platform.Android/Platform.cs @@ -206,11 +206,11 @@ namespace Xamarin.Forms.Platform.Android { if (animated) { - modalRenderer.ViewGroup.Animate().Alpha(0).ScaleX(0.8f).ScaleY(0.8f).SetDuration(250).SetListener(new GenericAnimatorListener + modalRenderer.View.Animate().Alpha(0).ScaleX(0.8f).ScaleY(0.8f).SetDuration(250).SetListener(new GenericAnimatorListener { OnEnd = a => { - modalRenderer.ViewGroup.RemoveFromParent(); + modalRenderer.View.RemoveFromParent(); modalRenderer.Dispose(); source.TrySetResult(modal); CurrentPageController?.SendAppearing(); @@ -219,7 +219,7 @@ namespace Xamarin.Forms.Platform.Android } else { - modalRenderer.ViewGroup.RemoveFromParent(); + modalRenderer.View.RemoveFromParent(); modalRenderer.Dispose(); source.TrySetResult(modal); CurrentPageController?.SendAppearing(); @@ -534,7 +534,7 @@ namespace Xamarin.Forms.Platform.Android if (layout) view.Layout(new Rectangle(0, 0, _context.FromPixels(_renderer.Width), _context.FromPixels(_renderer.Height))); - _renderer.AddView(renderView.ViewGroup); + _renderer.AddView(renderView.View); } #pragma warning disable 618 // This may need to be updated to work with TabLayout/AppCompat @@ -767,19 +767,19 @@ namespace Xamarin.Forms.Platform.Android SetRenderer(modal, modalRenderer); if (modal.BackgroundColor == Color.Default && modal.BackgroundImage == null) - modalRenderer.ViewGroup.SetWindowBackground(); + modalRenderer.View.SetWindowBackground(); } modalRenderer.Element.Layout(new Rectangle(0, 0, _context.FromPixels(_renderer.Width), _context.FromPixels(_renderer.Height))); - _renderer.AddView(modalRenderer.ViewGroup); + _renderer.AddView(modalRenderer.View); var source = new TaskCompletionSource<bool>(); NavAnimationInProgress = true; if (animated) { - modalRenderer.ViewGroup.Alpha = 0; - modalRenderer.ViewGroup.ScaleX = 0.8f; - modalRenderer.ViewGroup.ScaleY = 0.8f; - modalRenderer.ViewGroup.Animate().Alpha(1).ScaleX(1).ScaleY(1).SetDuration(250).SetListener(new GenericAnimatorListener + modalRenderer.View.Alpha = 0; + modalRenderer.View.ScaleX = 0.8f; + modalRenderer.View.ScaleY = 0.8f; + modalRenderer.View.Animate().Alpha(1).ScaleX(1).ScaleY(1).SetDuration(250).SetListener(new GenericAnimatorListener { OnEnd = a => { diff --git a/Xamarin.Forms.Platform.Android/PlatformEffect.cs b/Xamarin.Forms.Platform.Android/PlatformEffect.cs index 6830b722..716fce7f 100644 --- a/Xamarin.Forms.Platform.Android/PlatformEffect.cs +++ b/Xamarin.Forms.Platform.Android/PlatformEffect.cs @@ -1,3 +1,4 @@ +using System; using Android.Views; using AView = Android.Views.View; diff --git a/Xamarin.Forms.Platform.Android/RendererPool.cs b/Xamarin.Forms.Platform.Android/RendererPool.cs index 48665c81..51913f61 100644 --- a/Xamarin.Forms.Platform.Android/RendererPool.cs +++ b/Xamarin.Forms.Platform.Android/RendererPool.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Android.Views; namespace Xamarin.Forms.Platform.Android { @@ -60,18 +61,20 @@ namespace Xamarin.Forms.Platform.Android if (renderer == null) continue; - if (renderer.ViewGroup.Parent != _parent.ViewGroup) + if (renderer.View.Parent != _parent.View) continue; - renderer.ViewGroup.RemoveFromParent(); + renderer.View.RemoveFromParent(); Platform.SetRenderer(child, null); PushRenderer(renderer); } } - if (_parent.ViewGroup.ChildCount != 0) - _parent.ViewGroup.RemoveAllViews(); + var viewGroup = _parent.View as ViewGroup; + + if (viewGroup != null && viewGroup.ChildCount != 0) + viewGroup.RemoveAllViews(); } void PushRenderer(IVisualElementRenderer renderer) diff --git a/Xamarin.Forms.Platform.Android/Renderers/ButtonRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ButtonRenderer.cs index e07a2be5..7bb97945 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ButtonRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ButtonRenderer.cs @@ -26,6 +26,7 @@ namespace Xamarin.Forms.Platform.Android public ButtonRenderer() { + System.Diagnostics.Debug.WriteLine("Slow Button!"); AutoPackage = false; } diff --git a/Xamarin.Forms.Platform.Android/Renderers/CarouselPageAdapter.cs b/Xamarin.Forms.Platform.Android/Renderers/CarouselPageAdapter.cs index efee571d..c31e8e69 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/CarouselPageAdapter.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/CarouselPageAdapter.cs @@ -55,7 +55,7 @@ namespace Xamarin.Forms.Platform.Android Page destroyedPage = holder.Instance.Item2; IVisualElementRenderer renderer = Platform.GetRenderer(destroyedPage); - renderer.ViewGroup.RemoveFromParent(); + renderer.View.RemoveFromParent(); holder.Instance.Item1.RemoveFromParent(); } @@ -90,7 +90,7 @@ namespace Xamarin.Forms.Platform.Android Platform.SetRenderer(child, Platform.CreateRenderer(child)); IVisualElementRenderer renderer = Platform.GetRenderer(child); - renderer.ViewGroup.RemoveFromParent(); + renderer.View.RemoveFromParent(); ViewGroup frame = new PageContainer(_context, renderer); @@ -130,7 +130,7 @@ namespace Xamarin.Forms.Platform.Android IVisualElementRenderer childPageRenderer = Platform.GetRenderer(childPage); if (childPageRenderer != null) { - childPageRenderer.ViewGroup.RemoveFromParent(); + childPageRenderer.View.RemoveFromParent(); childPageRenderer.Dispose(); Platform.SetRenderer(childPage, null); } diff --git a/Xamarin.Forms.Platform.Android/Renderers/ConditionalFocusLayout.cs b/Xamarin.Forms.Platform.Android/Renderers/ConditionalFocusLayout.cs index 311db6b8..5b3002ce 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ConditionalFocusLayout.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ConditionalFocusLayout.cs @@ -30,7 +30,7 @@ namespace Xamarin.Forms.Platform.Android return; IVisualElementRenderer renderer = Platform.GetRenderer(viewCell.View); - (renderer?.ViewGroup?.GetChildAt(0) as EditText)?.SetOnTouchListener(this); + GetEditText(renderer)?.SetOnTouchListener(this); foreach (Element descendant in viewCell.View.Descendants()) { @@ -38,8 +38,14 @@ namespace Xamarin.Forms.Platform.Android if (element == null) continue; renderer = Platform.GetRenderer(element); - (renderer?.ViewGroup?.GetChildAt(0) as EditText)?.SetOnTouchListener(this); + GetEditText(renderer)?.SetOnTouchListener(this); } } + + internal EditText GetEditText(IVisualElementRenderer renderer) + { + var viewGroup = renderer?.View as ViewGroup; + return viewGroup?.GetChildAt(0) as EditText; + } } }
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/Renderers/FormsImageView.cs b/Xamarin.Forms.Platform.Android/Renderers/FormsImageView.cs index ae32e16c..dc0bd2fc 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/FormsImageView.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/FormsImageView.cs @@ -5,7 +5,7 @@ using Android.Widget; namespace Xamarin.Forms.Platform.Android { - internal class FormsImageView : ImageView + internal class FormsImageView : ImageView, IImageRendererController { bool _skipInvalidate; diff --git a/Xamarin.Forms.Platform.Android/Renderers/FormsTextView.cs b/Xamarin.Forms.Platform.Android/Renderers/FormsTextView.cs index 1dc25ea9..2b2bcb7a 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/FormsTextView.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/FormsTextView.cs @@ -6,7 +6,7 @@ using Android.Widget; namespace Xamarin.Forms.Platform.Android { - internal class FormsTextView : TextView + public class FormsTextView : TextView { bool _skip; diff --git a/Xamarin.Forms.Platform.Android/Renderers/ImageRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ImageRenderer.cs index fee05f1b..57937d89 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ImageRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ImageRenderer.cs @@ -1,14 +1,18 @@ using System; using System.ComponentModel; -using System.IO; -using System.Threading.Tasks; using Android.Graphics; using Android.Views; using AImageView = Android.Widget.ImageView; using Xamarin.Forms.Internals; +using static Xamarin.Forms.Platform.Android.ImageViewExtensions; namespace Xamarin.Forms.Platform.Android { + internal interface IImageRendererController + { + void SkipInvalidate(); + } + public class ImageRenderer : ViewRenderer<Image, AImageView> { bool _isDisposed; @@ -16,6 +20,7 @@ namespace Xamarin.Forms.Platform.Android public ImageRenderer() { + System.Diagnostics.Debug.WriteLine(">>>>> Old Image Renderer"); AutoPackage = false; } @@ -45,8 +50,9 @@ namespace Xamarin.Forms.Platform.Android } _motionEventHelper.UpdateElement(e.NewElement); + + Control.UpdateBitmap(e.NewElement, e.OldElement); - UpdateBitmap(e.OldElement); UpdateAspect(); } @@ -55,7 +61,7 @@ namespace Xamarin.Forms.Platform.Android base.OnElementPropertyChanged(sender, e); if (e.PropertyName == Image.SourceProperty.PropertyName) - UpdateBitmap(); + Control.UpdateBitmap(Element); else if (e.PropertyName == Image.AspectProperty.PropertyName) UpdateAspect(); } @@ -66,66 +72,12 @@ namespace Xamarin.Forms.Platform.Android Control.SetScaleType(type); } - async void UpdateBitmap(Image previous = null) - { - if (Device.IsInvokeRequired) - throw new InvalidOperationException("Image Bitmap must not be updated from background thread"); - - if (previous != null && Equals(previous.Source, Element.Source)) - return; - - ((IImageController)Element).SetIsLoading(true); + public override bool OnTouchEvent(MotionEvent e) + { + if (base.OnTouchEvent(e)) + return true; - var formsImageView = Control as FormsImageView; - formsImageView?.SkipInvalidate(); - - Control.SetImageResource(global::Android.Resource.Color.Transparent); - - ImageSource source = Element.Source; - Bitmap bitmap = null; - IImageSourceHandler handler; - - if (source != null && (handler = Internals.Registrar.Registered.GetHandler<IImageSourceHandler>(source.GetType())) != null) - { - try - { - bitmap = await handler.LoadImageAsync(source, Context); - } - catch (TaskCanceledException) - { - } - catch (IOException ex) - { - Log.Warning("Xamarin.Forms.Platform.Android.ImageRenderer", "Error updating bitmap: {0}", ex); - } - } - - if (Element == null || !Equals(Element.Source, source)) - { - bitmap?.Dispose(); - return; - } - - if (!_isDisposed) - { - if (bitmap == null && source is FileImageSource) - Control.SetImageResource(ResourceManager.GetDrawableByName(((FileImageSource)source).File)); - else - Control.SetImageBitmap(bitmap); - - bitmap?.Dispose(); - - ((IImageController)Element).SetIsLoading(false); - ((IVisualElementController)Element).NativeSizeChanged(); - } - } - - public override bool OnTouchEvent(MotionEvent e) - { - if (base.OnTouchEvent(e)) - return true; - - return _motionEventHelper.HandleMotionEvent(Parent); - } - } + return _motionEventHelper.HandleMotionEvent(Parent); + } + } }
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/Renderers/ListViewRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ListViewRenderer.cs index a0906915..bd241c1a 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ListViewRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ListViewRenderer.cs @@ -45,14 +45,14 @@ namespace Xamarin.Forms.Platform.Android if (_headerRenderer != null) { - _headerRenderer.ViewGroup.RemoveAllViews(); + (_headerRenderer.View as ViewGroup)?.RemoveAllViews(); _headerRenderer.Dispose(); _headerRenderer = null; } if (_footerRenderer != null) { - _footerRenderer.ViewGroup.RemoveAllViews(); + (_footerRenderer.View as ViewGroup)?.RemoveAllViews(); _footerRenderer.Dispose(); _footerRenderer = null; } @@ -352,12 +352,12 @@ namespace Xamarin.Forms.Platform.Android set { if (_child != null) - RemoveView(_child.ViewGroup); + RemoveView(_child.View); _child = value; if (value != null) - AddView(value.ViewGroup); + AddView(value.View); } } @@ -389,7 +389,7 @@ namespace Xamarin.Forms.Platform.Android int widthSpec = MeasureSpecFactory.MakeMeasureSpec((int)ctx.ToPixels(width), MeasureSpecMode.Exactly); int heightSpec = MeasureSpecFactory.MakeMeasureSpec((int)ctx.ToPixels(request.Request.Height), MeasureSpecMode.Exactly); - _child.ViewGroup.Measure(widthMeasureSpec, heightMeasureSpec); + _child.View.Measure(widthMeasureSpec, heightMeasureSpec); SetMeasuredDimension(widthSpec, heightSpec); } } diff --git a/Xamarin.Forms.Platform.Android/Renderers/MasterDetailContainer.cs b/Xamarin.Forms.Platform.Android/Renderers/MasterDetailContainer.cs index a7bebf1e..fd028df1 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/MasterDetailContainer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/MasterDetailContainer.cs @@ -54,12 +54,12 @@ namespace Xamarin.Forms.Platform.Android if (renderer == null) Platform.SetRenderer(childView, renderer = Platform.CreateRenderer(childView)); - if (renderer.ViewGroup.Parent != this) + if (renderer.View.Parent != this) { - if (renderer.ViewGroup.Parent != null) - renderer.ViewGroup.RemoveFromParent(); + if (renderer.View.Parent != null) + renderer.View.RemoveFromParent(); SetDefaultBackgroundColor(renderer); - AddView(renderer.ViewGroup); + AddView(renderer.View); renderer.UpdateLayout(); } } @@ -148,7 +148,7 @@ namespace Xamarin.Forms.Platform.Android if (ChildView.BackgroundColor == Color.Default) { TypedArray colors = Context.Theme.ObtainStyledAttributes(new[] { global::Android.Resource.Attribute.ColorBackground }); - renderer.ViewGroup.SetBackgroundColor(new global::Android.Graphics.Color(colors.GetColor(0, 0))); + renderer.View.SetBackgroundColor(new global::Android.Graphics.Color(colors.GetColor(0, 0))); } } } diff --git a/Xamarin.Forms.Platform.Android/Renderers/MasterDetailRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/MasterDetailRenderer.cs index d18781a7..7e744bc7 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/MasterDetailRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/MasterDetailRenderer.cs @@ -73,6 +73,13 @@ namespace Xamarin.Forms.Platform.Android public event EventHandler<VisualElementChangedEventArgs> ElementChanged; + event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged; + event EventHandler<PropertyChangedEventArgs> IVisualElementRenderer.ElementPropertyChanged + { + add { ElementPropertyChanged += value; } + remove { ElementPropertyChanged -= value; } + } + public SizeRequest GetDesiredSize(int widthConstraint, int heightConstraint) { Measure(widthConstraint, heightConstraint); @@ -142,10 +149,8 @@ namespace Xamarin.Forms.Platform.Android Tracker.UpdateLayout(); } - public ViewGroup ViewGroup - { - get { return this; } - } + public ViewGroup ViewGroup => this; + AView IVisualElementRenderer.View => this; protected override void Dispose(bool disposing) { @@ -237,6 +242,7 @@ namespace Xamarin.Forms.Platform.Android void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) { + ElementPropertyChanged?.Invoke(this, e); if (e.PropertyName == "Master") UpdateMaster(); else if (e.PropertyName == "Detail") diff --git a/Xamarin.Forms.Platform.Android/Renderers/NavigationRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/NavigationRenderer.cs index bf6540b9..5d678287 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/NavigationRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/NavigationRenderer.cs @@ -183,13 +183,13 @@ namespace Xamarin.Forms.Platform.Android void RemovePage(Page page) { IVisualElementRenderer rendererToRemove = Platform.GetRenderer(page); - PageContainer containerToRemove = rendererToRemove == null ? null : (PageContainer)rendererToRemove.ViewGroup.Parent; + PageContainer containerToRemove = rendererToRemove == null ? null : (PageContainer)rendererToRemove.View.Parent; containerToRemove.RemoveFromParent(); if (rendererToRemove != null) { - rendererToRemove.ViewGroup.RemoveFromParent(); + rendererToRemove.View.RemoveFromParent(); rendererToRemove.Dispose(); } @@ -213,8 +213,8 @@ namespace Xamarin.Forms.Platform.Android Page pageToRemove = _current; IVisualElementRenderer rendererToRemove = pageToRemove == null ? null : Platform.GetRenderer(pageToRemove); - PageContainer containerToRemove = rendererToRemove == null ? null : (PageContainer)rendererToRemove.ViewGroup.Parent; - PageContainer containerToAdd = (PageContainer)rendererToAdd.ViewGroup.Parent ?? new PageContainer(Context, rendererToAdd); + PageContainer containerToRemove = rendererToRemove == null ? null : (PageContainer)rendererToRemove.View.Parent; + PageContainer containerToAdd = (PageContainer)rendererToAdd.View.Parent ?? new PageContainer(Context, rendererToAdd); containerToAdd.SetWindowBackground(); diff --git a/Xamarin.Forms.Platform.Android/Renderers/PageContainer.cs b/Xamarin.Forms.Platform.Android/Renderers/PageContainer.cs index 06e33e18..a4e77361 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/PageContainer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/PageContainer.cs @@ -7,7 +7,7 @@ namespace Xamarin.Forms.Platform.Android { public PageContainer(Context context, IVisualElementRenderer child, bool inFragment = false) : base(context) { - AddView(child.ViewGroup); + AddView(child.View); Child = child; IsInFragment = inFragment; } @@ -23,8 +23,8 @@ namespace Xamarin.Forms.Platform.Android protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) { - Child.ViewGroup.Measure(widthMeasureSpec, heightMeasureSpec); - SetMeasuredDimension(Child.ViewGroup.MeasuredWidth, Child.ViewGroup.MeasuredHeight); + Child.View.Measure(widthMeasureSpec, heightMeasureSpec); + SetMeasuredDimension(Child.View.MeasuredWidth, Child.View.MeasuredHeight); } } }
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/Renderers/ScrollViewContainer.cs b/Xamarin.Forms.Platform.Android/Renderers/ScrollViewContainer.cs index c79e6137..c9354ce2 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ScrollViewContainer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ScrollViewContainer.cs @@ -32,10 +32,10 @@ namespace Xamarin.Forms.Platform.Android if ((renderer = Platform.GetRenderer(_childView)) == null) Platform.SetRenderer(_childView, renderer = Platform.CreateRenderer(_childView)); - if (renderer.ViewGroup.Parent != null) - renderer.ViewGroup.RemoveFromParent(); + if (renderer.View.Parent != null) + renderer.View.RemoveFromParent(); - AddView(renderer.ViewGroup); + AddView(renderer.View); } } diff --git a/Xamarin.Forms.Platform.Android/Renderers/ScrollViewRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/ScrollViewRenderer.cs index b7961411..69020de6 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/ScrollViewRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/ScrollViewRenderer.cs @@ -7,6 +7,7 @@ using Android.Views; using Android.Widget; using Xamarin.Forms.Internals; using AScrollView = Android.Widget.ScrollView; +using AView = Android.Views.View; namespace Xamarin.Forms.Platform.Android { @@ -39,6 +40,13 @@ namespace Xamarin.Forms.Platform.Android public event EventHandler<VisualElementChangedEventArgs> ElementChanged; + event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged; + event EventHandler<PropertyChangedEventArgs> IVisualElementRenderer.ElementPropertyChanged + { + add { ElementPropertyChanged += value; } + remove { ElementPropertyChanged -= value; } + } + public SizeRequest GetDesiredSize(int widthConstraint, int heightConstraint) { Measure(widthConstraint, heightConstraint); @@ -90,10 +98,9 @@ namespace Xamarin.Forms.Platform.Android Tracker.UpdateLayout(); } - public ViewGroup ViewGroup - { - get { return this; } - } + public ViewGroup ViewGroup => this; + + AView IVisualElementRenderer.View => this; public override void Draw(Canvas canvas) { @@ -239,6 +246,8 @@ namespace Xamarin.Forms.Platform.Android void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) { + ElementPropertyChanged?.Invoke(this, e); + if (e.PropertyName == "Content") LoadContent(); else if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName) diff --git a/Xamarin.Forms.Platform.Android/Renderers/TabbedRenderer.cs b/Xamarin.Forms.Platform.Android/Renderers/TabbedRenderer.cs index f2878a80..2d5f3b02 100644 --- a/Xamarin.Forms.Platform.Android/Renderers/TabbedRenderer.cs +++ b/Xamarin.Forms.Platform.Android/Renderers/TabbedRenderer.cs @@ -71,7 +71,7 @@ namespace Xamarin.Forms.Platform.Android if (Platform.GetRenderer(view) == null) Platform.SetRenderer(view, Platform.CreateRenderer(view)); - AddView(Platform.GetRenderer(view).ViewGroup); + AddView(Platform.GetRenderer(view).View); } } }
\ No newline at end of file diff --git a/Xamarin.Forms.Platform.Android/VisualElementPackager.cs b/Xamarin.Forms.Platform.Android/VisualElementPackager.cs index 0e08cfed..129ac33d 100644 --- a/Xamarin.Forms.Platform.Android/VisualElementPackager.cs +++ b/Xamarin.Forms.Platform.Android/VisualElementPackager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using Xamarin.Forms.Internals; +using Android.Views; using AView = Android.Views.View; namespace Xamarin.Forms.Platform.Android @@ -99,7 +100,7 @@ namespace Xamarin.Forms.Platform.Android Performance.Start("Add view"); if (!sameChildren) { - _renderer.ViewGroup.AddView(renderer.ViewGroup); + (_renderer.View as ViewGroup)?.AddView(renderer.View); _childViews.Add(renderer); } Performance.Stop("Add view"); @@ -116,7 +117,7 @@ namespace Xamarin.Forms.Platform.Android if (element != null) { IVisualElementRenderer r = Platform.GetRenderer(element); - _renderer.ViewGroup.BringChildToFront(r.ViewGroup); + (_renderer.View as ViewGroup)?.BringChildToFront(r.View); } } } @@ -150,7 +151,7 @@ namespace Xamarin.Forms.Platform.Android { IVisualElementRenderer renderer = Platform.GetRenderer(view); _childViews.Remove(renderer); - renderer.ViewGroup.RemoveFromParent(); + renderer.View.RemoveFromParent(); renderer.Dispose(); } diff --git a/Xamarin.Forms.Platform.Android/VisualElementRenderer.cs b/Xamarin.Forms.Platform.Android/VisualElementRenderer.cs index 8f6722c3..fc0d0436 100644 --- a/Xamarin.Forms.Platform.Android/VisualElementRenderer.cs +++ b/Xamarin.Forms.Platform.Android/VisualElementRenderer.cs @@ -161,8 +161,10 @@ namespace Xamarin.Forms.Platform.Android } public ViewGroup ViewGroup => this; + AView IVisualElementRenderer.View => this; public event EventHandler<ElementChangedEventArgs<TElement>> ElementChanged; + public event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged; public void SetElement(TElement element) { @@ -322,6 +324,8 @@ namespace Xamarin.Forms.Platform.Android SetFocusable(); else if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName) UpdateInputTransparent(); + + ElementPropertyChanged?.Invoke(this, e); } protected override void OnLayout(bool changed, int l, int t, int r, int b) diff --git a/Xamarin.Forms.Platform.Android/VisualElementTracker.cs b/Xamarin.Forms.Platform.Android/VisualElementTracker.cs index 77404da8..b02fa0c4 100644 --- a/Xamarin.Forms.Platform.Android/VisualElementTracker.cs +++ b/Xamarin.Forms.Platform.Android/VisualElementTracker.cs @@ -33,15 +33,15 @@ namespace Xamarin.Forms.Platform.Android _propertyChangedHandler = HandlePropertyChanged; _renderer = renderer; - _context = renderer.ViewGroup.Context; + _context = renderer.View.Context; _renderer.ElementChanged += RendererOnElementChanged; VisualElement view = renderer.Element; SetElement(null, view); - renderer.ViewGroup.SetCameraDistance(3600); + renderer.View.SetCameraDistance(3600); - renderer.ViewGroup.AddOnAttachStateChangeListener(AttachTracker.Instance); + renderer.View.AddOnAttachStateChangeListener(AttachTracker.Instance); } public void Dispose() @@ -64,7 +64,7 @@ namespace Xamarin.Forms.Platform.Android if (_renderer != null) { _renderer.ElementChanged -= RendererOnElementChanged; - _renderer.ViewGroup.RemoveOnAttachStateChangeListener(AttachTracker.Instance); + _renderer.View.RemoveOnAttachStateChangeListener(AttachTracker.Instance); _renderer = null; _context = null; } @@ -76,7 +76,7 @@ namespace Xamarin.Forms.Platform.Android Performance.Start(); VisualElement view = _renderer.Element; - AView aview = _renderer.ViewGroup; + AView aview = _renderer.View; var x = (int)_context.ToPixels(view.X); var y = (int)_context.ToPixels(view.Y); @@ -110,6 +110,11 @@ namespace Xamarin.Forms.Platform.Android void HandlePropertyChanged(object sender, PropertyChangedEventArgs e) { + if (_renderer == null) + { + return; + } + if (e.PropertyName == Layout.IsClippedToBoundsProperty.PropertyName) { UpdateClipToBounds(); @@ -183,10 +188,10 @@ namespace Xamarin.Forms.Platform.Android { var isInLayout = false; if ((int)Build.VERSION.SdkInt >= 18) - isInLayout = _renderer.ViewGroup.IsInLayout; + isInLayout = _renderer.View.IsInLayout; - if (!isInLayout && !_renderer.ViewGroup.IsLayoutRequested) - _renderer.ViewGroup.RequestLayout(); + if (!isInLayout && !_renderer.View.IsLayoutRequested) + _renderer.View.RequestLayout(); } void RendererOnElementChanged(object sender, VisualElementChangedEventArgs args) @@ -208,11 +213,11 @@ namespace Xamarin.Forms.Platform.Android { newElement.BatchCommitted += _batchCommittedHandler; newElement.PropertyChanged += _propertyChangedHandler; - _context = _renderer.ViewGroup.Context; + _context = _renderer.View.Context; if (oldElement != null) { - AView view = _renderer.ViewGroup; + AView view = _renderer.View; // ReSharper disable CompareOfFloatsByEqualityOperator if (oldElement.AnchorX != newElement.AnchorX) @@ -243,7 +248,7 @@ namespace Xamarin.Forms.Platform.Android void UpdateAnchorX() { VisualElement view = _renderer.Element; - AView aview = _renderer.ViewGroup; + AView aview = _renderer.View; float currentPivot = aview.PivotX; var target = (float)(view.AnchorX * _context.ToPixels(view.Width)); @@ -254,7 +259,7 @@ namespace Xamarin.Forms.Platform.Android void UpdateAnchorY() { VisualElement view = _renderer.Element; - AView aview = _renderer.ViewGroup; + AView aview = _renderer.View; float currentPivot = aview.PivotY; var target = (float)(view.AnchorY * _context.ToPixels(view.Height)); @@ -265,7 +270,7 @@ namespace Xamarin.Forms.Platform.Android void UpdateClipToBounds() { var layout = _renderer.Element as Layout; - var parent = _renderer.ViewGroup.Parent as ViewGroup; + var parent = _renderer.View.Parent as ViewGroup; if (parent == null || layout == null) return; @@ -282,7 +287,7 @@ namespace Xamarin.Forms.Platform.Android void UpdateIsVisible() { VisualElement view = _renderer.Element; - AView aview = _renderer.ViewGroup; + AView aview = _renderer.View; if (view.IsVisible && aview.Visibility != ViewStates.Visible) aview.Visibility = ViewStates.Visible; @@ -295,7 +300,7 @@ namespace Xamarin.Forms.Platform.Android Performance.Start(); VisualElement view = _renderer.Element; - AView aview = _renderer.ViewGroup; + AView aview = _renderer.View; if (aview is FormsViewGroup) { @@ -306,20 +311,11 @@ namespace Xamarin.Forms.Platform.Android } else { - UpdateAnchorX(); - UpdateAnchorY(); - UpdateIsVisible(); - - if (view.IsEnabled != aview.Enabled) - aview.Enabled = view.IsEnabled; - - UpdateOpacity(); - UpdateRotation(); - UpdateRotationX(); - UpdateRotationY(); - UpdateScale(); - UpdateTranslationX(); - UpdateTranslationY(); + FormsViewGroup.SendViewBatchUpdate(aview, (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)); } Performance.Stop(); @@ -330,7 +326,7 @@ namespace Xamarin.Forms.Platform.Android Performance.Start(); VisualElement view = _renderer.Element; - AView aview = _renderer.ViewGroup; + AView aview = _renderer.View; aview.Alpha = (float)view.Opacity; @@ -340,7 +336,7 @@ namespace Xamarin.Forms.Platform.Android void UpdateRotation() { VisualElement view = _renderer.Element; - AView aview = _renderer.ViewGroup; + AView aview = _renderer.View; aview.Rotation = (float)view.Rotation; } @@ -348,7 +344,7 @@ namespace Xamarin.Forms.Platform.Android void UpdateRotationX() { VisualElement view = _renderer.Element; - AView aview = _renderer.ViewGroup; + AView aview = _renderer.View; aview.RotationX = (float)view.RotationX; } @@ -356,7 +352,7 @@ namespace Xamarin.Forms.Platform.Android void UpdateRotationY() { VisualElement view = _renderer.Element; - AView aview = _renderer.ViewGroup; + AView aview = _renderer.View; aview.RotationY = (float)view.RotationY; } @@ -364,7 +360,7 @@ namespace Xamarin.Forms.Platform.Android void UpdateScale() { VisualElement view = _renderer.Element; - AView aview = _renderer.ViewGroup; + AView aview = _renderer.View; aview.ScaleX = (float)view.Scale; aview.ScaleY = (float)view.Scale; @@ -373,7 +369,7 @@ namespace Xamarin.Forms.Platform.Android void UpdateTranslationX() { VisualElement view = _renderer.Element; - AView aview = _renderer.ViewGroup; + AView aview = _renderer.View; aview.TranslationX = _context.ToPixels(view.TranslationX); } @@ -381,7 +377,7 @@ namespace Xamarin.Forms.Platform.Android void UpdateTranslationY() { VisualElement view = _renderer.Element; - AView aview = _renderer.ViewGroup; + AView aview = _renderer.View; aview.TranslationY = _context.ToPixels(view.TranslationY); } diff --git a/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj b/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj index fd894e47..e57a501d 100644 --- a/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj +++ b/Xamarin.Forms.Platform.Android/Xamarin.Forms.Platform.Android.csproj @@ -20,6 +20,8 @@ <RestorePackages>true</RestorePackages> <NuGetPackageImportStamp> </NuGetPackageImportStamp> + <AndroidTlsProvider> + </AndroidTlsProvider> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> @@ -100,9 +102,13 @@ </Compile> <Compile Include="AndroidApplicationLifecycleState.cs" /> <Compile Include="AndroidTitleBarVisibility.cs" /> + <Compile Include="AppCompat\FrameRenderer.cs" /> + <Compile Include="FastRenderers\AccessibilityProvider.cs" /> + <Compile Include="FastRenderers\ButtonRenderer.cs" /> <Compile Include="AppCompat\FormsViewPager.cs" /> <Compile Include="AppCompat\FragmentContainer.cs" /> - <Compile Include="AppCompat\FrameRenderer.cs" /> + <Compile Include="FastRenderers\EffectControlProvider.cs" /> + <Compile Include="FastRenderers\FrameRenderer.cs" /> <Compile Include="AppCompat\IManageFragments.cs" /> <Compile Include="AppCompat\MasterDetailContainer.cs" /> <Compile Include="AppCompat\Platform.cs" /> @@ -118,6 +124,9 @@ <Compile Include="ExportCellAttribute.cs" /> <Compile Include="ExportImageSourceHandlerAttribute.cs" /> <Compile Include="ExportRendererAttribute.cs" /> + <Compile Include="FastRenderers\GestureManager.cs" /> + <Compile Include="FastRenderers\LabelRenderer.cs" /> + <Compile Include="FastRenderers\VisualElementRenderer.cs" /> <Compile Include="FormsApplicationActivity.cs" /> <Compile Include="AndroidActivity.cs" /> <Compile Include="AndroidTicker.cs" /> @@ -246,6 +255,8 @@ <Compile Include="Extensions\NativeBindingExtensions.cs" /> <Compile Include="NativeValueConverterService.cs" /> <Compile Include="NativeBindingservice.cs" /> + <Compile Include="FastRenderers\ImageRenderer.cs" /> + <Compile Include="Extensions\ImageViewExtensions.cs" /> </ItemGroup> <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. |