File: Android\VisualElementRenderer.cs
Web Access
Project: src\src\Compatibility\Core\src\Compatibility.csproj (Microsoft.Maui.Controls.Compatibility)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using Android.Content;
using Android.Content.Res;
using Android.Graphics;
using Android.Runtime;
using Android.Views;
using AndroidX.Core.View;
using Microsoft.Maui.Controls.Compatibility.Platform.Android.FastRenderers;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Graphics;
using AView = Android.Views.View;
using Color = Microsoft.Maui.Graphics.Color;
 
namespace Microsoft.Maui.Controls.Compatibility.Platform.Android
{
	[Obsolete("Use Microsoft.Maui.Controls.Handlers.Compatibility.VisualElementRenderer instead")]
	public abstract class VisualElementRenderer<TElement> : Microsoft.Maui.MauiViewGroup, IVisualElementRenderer, IDisposedState,
		IEffectControlProvider where TElement : VisualElement
	{
		readonly List<EventHandler<VisualElementChangedEventArgs>> _elementChangedHandlers = new List<EventHandler<VisualElementChangedEventArgs>>();
 
		VisualElementRendererFlags _flags = VisualElementRendererFlags.AutoPackage | VisualElementRendererFlags.AutoTrack;
 
		string _defaultContentDescription;
		string _defaultHint;
		bool _cascadeInputTransparent = true;
		bool _defaultAutomationSet;
		VisualElementPackager _packager;
		PropertyChangedEventHandler _propertyChangeHandler;
 
		protected VisualElementRenderer(Context context) : base(context)
		{
		}
 
		public override bool OnInterceptTouchEvent(MotionEvent ev)
		{
			if (!Enabled)
			{
				// If Enabled is false, prevent all the events from being dispatched to child Views
				// and prevent them from being processed by this View as well
				return true; // IOW, intercepted
			}
 
			return base.OnInterceptTouchEvent(ev);
		}
 
		public override bool DispatchTouchEvent(MotionEvent e)
		{
			if (Element == null || (InputTransparent && _cascadeInputTransparent))
			{
				// If the Element is InputTransparent, this ViewGroup will be marked InputTransparent
				// If we're InputTransparent and our transparency should be applied to our child controls,
				// we return false on all touch events without even bothering to send them to the child Views
 
				return false; // IOW, not handled
			}
 
			return base.DispatchTouchEvent(e);
		}
 
		public TElement Element { get; private set; }
 
		protected bool AutoPackage
		{
			get { return (_flags & VisualElementRendererFlags.AutoPackage) != 0; }
			set
			{
				if (value)
					_flags |= VisualElementRendererFlags.AutoPackage;
				else
					_flags &= ~VisualElementRendererFlags.AutoPackage;
			}
		}
 
		protected bool AutoTrack
		{
			get { return (_flags & VisualElementRendererFlags.AutoTrack) != 0; }
			set
			{
				if (value)
					_flags |= VisualElementRendererFlags.AutoTrack;
				else
					_flags &= ~VisualElementRendererFlags.AutoTrack;
			}
		}
 
		View View => Element as View;
 
		void IEffectControlProvider.RegisterEffect(Effect effect)
		{
			var platformEffect = effect as PlatformEffect;
			if (platformEffect != null)
				OnRegisterEffect(platformEffect);
		}
 
		VisualElement IVisualElementRenderer.Element => Element;
 
		event EventHandler<VisualElementChangedEventArgs> IVisualElementRenderer.ElementChanged
		{
			add { _elementChangedHandlers.Add(value); }
			remove { _elementChangedHandlers.Remove(value); }
		}
 
		public virtual SizeRequest GetDesiredSize(int widthConstraint, int heightConstraint)
		{
			Measure(widthConstraint, heightConstraint);
			return new SizeRequest(new Size(MeasuredWidth, MeasuredHeight), MinimumSize());
		}
 
		void IVisualElementRenderer.SetElement(VisualElement element)
		{
			if (!(element is TElement))
				throw new ArgumentException("element is not of type " + typeof(TElement), nameof(element));
 
			SetElement((TElement)element);
		}
 
		public VisualElementTracker Tracker { get; private set; }
 
		public void UpdateLayout()
		{
			Performance.Start(out string reference);
			Tracker?.UpdateLayout();
			Performance.Stop(reference);
		}
 
		AView IVisualElementRenderer.View => this;
 
		public event EventHandler<ElementChangedEventArgs<TElement>> ElementChanged;
		public event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged;
 
		public void SetElement(TElement element)
		{
			TElement oldElement = Element;
			Element = element ?? throw new ArgumentNullException(nameof(element));
 
			Performance.Start(out string reference);
 
			if (oldElement != null)
			{
				oldElement.PropertyChanged -= _propertyChangeHandler;
			}
 
			Color currentColor = oldElement?.BackgroundColor ?? null;
 
			if (element.BackgroundColor != currentColor)
				UpdateBackgroundColor();
 
			if (element.Background != null)
				UpdateBackground();
 
			if (_propertyChangeHandler == null)
				_propertyChangeHandler = OnElementPropertyChanged;
 
			element.PropertyChanged += _propertyChangeHandler;
 
			if (oldElement == null)
			{
				SoundEffectsEnabled = false;
			}
 
			OnElementChanged(new ElementChangedEventArgs<TElement>(oldElement, element));
 
			if (AutoPackage && _packager == null)
				SetPackager(new VisualElementPackager(this));
 
			if (AutoTrack && Tracker == null)
				SetTracker(new VisualElementTracker(this));
 
			if (oldElement != null)
				Tracker?.UpdateLayout();
 
			if (element != null)
				SendVisualElementInitialized(element, this);
 
			EffectUtilities.RegisterEffectControlProvider(this, oldElement, element);
 
			if (!string.IsNullOrEmpty(element.AutomationId))
				SetAutomationId(element.AutomationId);
 
			SetContentDescription();
			SetImportantForAccessibility();
			UpdateInputTransparent();
			UpdateInputTransparentInherited();
 
			Performance.Stop(reference);
		}
 
		/// <summary>
		/// Determines whether the native control is disposed of when this renderer is disposed
		/// Can be overridden in deriving classes 
		/// </summary>
		protected virtual bool ManageNativeControlLifetime => true;
 
		bool CheckFlagsForDisposed() => (_flags & VisualElementRendererFlags.Disposed) != 0;
		bool IDisposedState.IsDisposed => CheckFlagsForDisposed();
 
		protected override void Dispose(bool disposing)
		{
			if (CheckFlagsForDisposed())
				return;
 
			_flags |= VisualElementRendererFlags.Disposed;
 
			if (disposing)
			{
				SetOnClickListener(null);
				SetOnTouchListener(null);
 
				EffectUtilities.UnregisterEffectControlProvider(this, Element);
 
				if (Element != null)
				{
					Element.PropertyChanged -= _propertyChangeHandler;
				}
 
				if (Tracker != null)
				{
					Tracker.Dispose();
					Tracker = null;
				}
 
				if (_packager != null)
				{
					_packager.Dispose();
					_packager = null;
				}
 
				if (ManageNativeControlLifetime)
				{
					while (ChildCount > 0)
					{
						AView child = GetChildAt(0);
						child.RemoveFromParent();
						child.Dispose();
					}
				}
 
				if (Element != null)
				{
					if (Platform.GetRenderer(Element) == this)
						Platform.SetRenderer(Element, null);
 
					Element = null;
				}
			}
 
			base.Dispose(disposing);
		}
 
		protected override void OnConfigurationChanged(Configuration newConfig)
		{
			base.OnConfigurationChanged(newConfig);
 
			Invalidate();
		}
 
		protected virtual Size MinimumSize()
		{
			return new Size();
		}
 
		protected virtual void OnElementChanged(ElementChangedEventArgs<TElement> e)
		{
			var args = new VisualElementChangedEventArgs(e.OldElement, e.NewElement);
 
			// The list of event handlers can be changed inside the handlers. (ex.: are used CompressedLayout)
			// To avoid an exception, a copy of the handlers is called.
			var handlers = _elementChangedHandlers.ToArray();
			foreach (var handler in handlers)
				handler(this, args);
 
			ElementChanged?.Invoke(this, e);
 
			ElevationHelper.SetElevation(this, e.NewElement);
		}
 
		protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
				UpdateBackgroundColor();
			else if (e.PropertyName == VisualElement.BackgroundProperty.PropertyName)
				UpdateBackground();
			else if (e.PropertyName == AutomationProperties.HelpTextProperty.PropertyName)
				SetContentDescription();
			else if (e.PropertyName == AutomationProperties.NameProperty.PropertyName)
				SetContentDescription();
			else if (e.PropertyName == AutomationProperties.IsInAccessibleTreeProperty.PropertyName)
				SetImportantForAccessibility();
			else if (e.PropertyName == VisualElement.InputTransparentProperty.PropertyName)
				UpdateInputTransparent();
			else if (e.PropertyName == Microsoft.Maui.Controls.Compatibility.Layout.CascadeInputTransparentProperty.PropertyName)
				UpdateInputTransparentInherited();
 
			ElementPropertyChanged?.Invoke(this, e);
		}
 
		protected override void OnLayout(bool changed, int l, int t, int r, int b)
		{
			if (Element is IElementController controller)
			{
				UpdateLayout(controller.LogicalChildren);
			}
		}
 
		public override void Draw(Canvas canvas)
		{
			canvas.ClipShape(Context, Element);
 
			base.Draw(canvas);
		}
 
		static void UpdateLayout(IEnumerable<Element> children)
		{
			foreach (Element element in children)
			{
				var visualElement = element as VisualElement;
				if (visualElement == null)
					continue;
 
				IVisualElementRenderer renderer = Platform.GetRenderer(visualElement);
				if (renderer == null && CompressedLayout.GetIsHeadless(visualElement))
					UpdateLayout(((IElementController)visualElement).LogicalChildren);
 
				renderer?.UpdateLayout();
			}
		}
 
		protected virtual void OnRegisterEffect(PlatformEffect effect)
		{
			effect.Container = this;
		}
 
		void SetupAutomationDefaults()
		{
			if (!_defaultAutomationSet)
			{
				_defaultAutomationSet = true;
				Controls.Platform.AutomationPropertiesProvider.SetupDefaults(this, ref _defaultContentDescription, ref _defaultHint);
			}
		}
 
		protected virtual void SetAutomationId(string id)
		{
			SetupAutomationDefaults();
			Controls.Platform.AutomationPropertiesProvider.SetAutomationId(this, Element, id);
		}
 
		protected virtual void SetContentDescription()
		{
			SetupAutomationDefaults();
			Controls.Platform.AutomationPropertiesProvider.SetContentDescription(this, Element, _defaultContentDescription, _defaultHint);
		}
 
		protected virtual void SetImportantForAccessibility()
			=> Controls.Platform.AutomationPropertiesProvider.SetImportantForAccessibility(this, Element);
 
		void UpdateInputTransparent()
		{
			InputTransparent = Element.InputTransparent;
		}
 
		void UpdateInputTransparentInherited()
		{
			var layout = Element as Layout;
 
			if (layout == null)
			{
				return;
			}
 
			_cascadeInputTransparent = layout.CascadeInputTransparent;
		}
 
		protected void SetPackager(VisualElementPackager packager)
		{
			_packager = packager;
			packager.Load();
		}
 
		protected void SetTracker(VisualElementTracker tracker)
		{
			Tracker = tracker;
		}
 
		protected virtual void UpdateBackgroundColor()
		{
			SetBackgroundColor(Element.BackgroundColor.ToAndroid());
		}
 
		protected virtual void UpdateBackground()
		{
			Brush background = Element.Background;
 
			this.UpdateBackground(background);
		}
 
		internal virtual void SendVisualElementInitialized(VisualElement element, AView nativeView)
		{
			element.SendViewInitialized(nativeView);
		}
 
		void IVisualElementRenderer.SetLabelFor(int? id)
			=> ViewCompat.SetLabelFor(this, id ?? ViewCompat.GetLabelFor(this));
	}
}