File: Android\Renderers\LabelRenderer.cs
Web Access
Project: src\src\Compatibility\Core\src\Compatibility.csproj (Microsoft.Maui.Controls.Compatibility)
using System;
using System.ComponentModel;
using Android.Content;
using Android.Content.Res;
using Android.Graphics;
using Android.Text;
using Android.Util;
using Android.Views;
using Android.Widget;
using Microsoft.Maui.Controls.Platform;
using Color = Microsoft.Maui.Graphics.Color;
using Size = Microsoft.Maui.Graphics.Size;
 
namespace Microsoft.Maui.Controls.Compatibility.Platform.Android
{
	[System.Obsolete(Compatibility.Hosting.MauiAppBuilderExtensions.UseMapperInstead)]
	public class LabelRenderer : ViewRenderer<Label, TextView>
	{
		ColorStateList _labelTextColorDefault;
		float _lineSpacingExtraDefault;
		float _lineSpacingMultiplierDefault;
		int _lastConstraintHeight;
		int _lastConstraintWidth;
 
		SizeRequest? _lastSizeRequest;
		float _lastTextSize = -1f;
		Typeface _lastTypeface;
 
		Color _lastUpdateColor = null;
		FormsTextView _view;
		bool _wasFormatted;
 
		readonly MotionEventHelper _motionEventHelper = new MotionEventHelper();
 
		SpannableString _spannableString;
 
		public LabelRenderer(Context context) : base(context)
		{
			AutoPackage = false;
		}
 
		public override SizeRequest 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;
			}
 
			//We need to clear the Hint or else it will interfere with the sizing of the Label
			var hint = Control.Hint;
			if (!string.IsNullOrEmpty(hint))
				Control.Hint = string.Empty;
 
			SizeRequest result = base.GetDesiredSize(widthConstraint, heightConstraint);
 
			//Set Hint back after sizing
			Control.Hint = hint;
 
			result.Minimum = new Size(Math.Min(Context.ToPixels(10), result.Request.Width), result.Request.Height);
 
			_lastConstraintWidth = widthConstraint;
			_lastConstraintHeight = heightConstraint;
			_lastSizeRequest = result;
 
			return result;
		}
 
		protected override void OnLayout(bool changed, int l, int t, int r, int b)
		{
			base.OnLayout(changed, l, t, r, b);
			Control.RecalculateSpanPositions(Element, _spannableString, new SizeRequest(new Size(r - l, b - t)));
		}
 
		protected override TextView CreateNativeControl()
		{
			return new FormsTextView(Context);
		}
 
		protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
		{
			base.OnElementChanged(e);
			if (_view == null)
			{
				_view = (FormsTextView)CreateNativeControl();
				_labelTextColorDefault = _view.TextColors;
				_lineSpacingMultiplierDefault = _view.LineSpacingMultiplier;
				_lineSpacingExtraDefault = _view.LineSpacingExtra;
				SetNativeControl(_view);
			}
 
			if (e.OldElement == null)
			{
				UpdateText();
				UpdateLineBreakMode();
				UpdateCharacterSpacing();
				UpdateLineHeight();
				UpdateGravity();
				UpdateMaxLines();
				UpdateFlowDirection();
			}
			else
			{
				UpdateText();
				if (e.OldElement.LineBreakMode != e.NewElement.LineBreakMode)
					UpdateLineBreakMode();
				if (e.OldElement.HorizontalTextAlignment != e.NewElement.HorizontalTextAlignment || e.OldElement.VerticalTextAlignment != e.NewElement.VerticalTextAlignment)
					UpdateGravity();
				if (e.OldElement.MaxLines != e.NewElement.MaxLines)
					UpdateMaxLines();
				if (e.OldElement.CharacterSpacing != e.NewElement.CharacterSpacing)
					UpdateCharacterSpacing();
				if (e.OldElement.FlowDirection != e.NewElement.FlowDirection)
					UpdateFlowDirection();
			}
			UpdateTextDecorations();
			UpdatePadding();
			_motionEventHelper.UpdateElement(e.NewElement);
		}
 
		protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (this.IsDisposed())
			{
				return;
			}
 
			base.OnElementPropertyChanged(sender, e);
 
			if (e.PropertyName == Label.HorizontalTextAlignmentProperty.PropertyName || e.PropertyName == Label.VerticalTextAlignmentProperty.PropertyName)
				UpdateGravity();
			else if (e.IsOneOf(Label.TextColorProperty, Label.TextTransformProperty))
				UpdateText();
			else if (e.IsOneOf(Label.FontAttributesProperty, Label.FontFamilyProperty, Label.FontSizeProperty))
				UpdateText();
			else if (e.PropertyName == Label.CharacterSpacingProperty.PropertyName)
				UpdateCharacterSpacing();
			else if (e.PropertyName == Label.LineBreakModeProperty.PropertyName)
				UpdateLineBreakMode();
			else if (e.PropertyName == Label.TextDecorationsProperty.PropertyName)
				UpdateTextDecorations();
			else if (e.IsOneOf(Label.TextProperty, Label.FormattedTextProperty, Label.TextTypeProperty))
				UpdateText();
			else if (e.PropertyName == Label.LineHeightProperty.PropertyName)
				UpdateLineHeight();
			else if (e.PropertyName == Label.MaxLinesProperty.PropertyName)
				UpdateMaxLines();
			else if (e.PropertyName == Label.PaddingProperty.PropertyName)
				UpdatePadding();
			else if (e.PropertyName == VisualElement.FlowDirectionProperty.PropertyName)
				UpdateFlowDirection();
		}
 
		void UpdateFlowDirection()
		{
			Control.UpdateFlowDirection(Element);
		}
 
		[PortHandler]
		void UpdateColor()
		{
			Color c = Element.TextColor;
			if (c == _lastUpdateColor)
				return;
			_lastUpdateColor = c;
 
			if (c == null)
				_view.SetTextColor(_labelTextColorDefault);
			else
				_view.SetTextColor(c.ToAndroid());
		}
 
		[PortHandler]
		void UpdateFont()
		{
			Font f = Font.OfSize(Element.FontFamily, Element.FontSize).WithAttributes(Element.FontAttributes);
 
			Typeface newTypeface = f.ToTypeface(Element.RequireFontManager());
			if (newTypeface != _lastTypeface)
			{
				_view.Typeface = newTypeface;
				_lastTypeface = newTypeface;
			}
 
			float newTextSize = (float)f.Size;
			if (newTextSize != _lastTextSize)
			{
				_view.SetTextSize(ComplexUnitType.Sp, newTextSize);
				_lastTextSize = newTextSize;
			}
		}
 
		void UpdateTextDecorations()
		{
			if (!Element.IsSet(Label.TextDecorationsProperty))
				return;
 
			var textDecorations = Element.TextDecorations;
 
			if ((textDecorations & TextDecorations.Strikethrough) == 0)
				_view.PaintFlags &= ~PaintFlags.StrikeThruText;
			else
				_view.PaintFlags |= PaintFlags.StrikeThruText;
 
			if ((textDecorations & TextDecorations.Underline) == 0)
				_view.PaintFlags &= ~PaintFlags.UnderlineText;
			else
				_view.PaintFlags |= PaintFlags.UnderlineText;
		}
 
		[PortHandler]
		void UpdateGravity()
		{
			Label label = Element;
 
			_view.Gravity = label.HorizontalTextAlignment.ToHorizontalGravityFlags() | label.VerticalTextAlignment.ToVerticalGravityFlags();
 
			_lastSizeRequest = null;
		}
 
		void UpdateLineBreakMode()
		{
			_view.SetLineBreakMode(Element);
			_lastSizeRequest = null;
		}
		void UpdateCharacterSpacing()
		{
			if (Control is TextView textControl)
			{
				textControl.LetterSpacing = Element.CharacterSpacing.ToEm();
			}
		}
 
		[PortHandler]
		void UpdateLineHeight()
		{
			_lastSizeRequest = null;
			if (Element.LineHeight == -1)
				_view.SetLineSpacing(_lineSpacingExtraDefault, _lineSpacingMultiplierDefault);
			else if (Element.LineHeight >= 0)
				_view.SetLineSpacing(0, (float)Element.LineHeight);
		}
 
		void UpdateMaxLines()
		{
			Control.SetMaxLines(Element);
		}
 
		void UpdateText()
		{
			if (Element.FormattedText != null)
			{
				FormattedString formattedText = Element.FormattedText ?? Element.Text;
 
				Font f = Font.OfSize(Element.FontFamily, Element.FontSize).WithAttributes(Element.FontAttributes);
				_view.TextFormatted = _spannableString = formattedText.ToSpannableString(Element.RequireFontManager());
 
				_wasFormatted = true;
			}
			else
			{
				if (_wasFormatted)
				{
					_view.SetTextColor(_labelTextColorDefault);
					_lastUpdateColor = null;
				}
 
				switch (Element.TextType)
				{
 
					case TextType.Html:
						if (OperatingSystem.IsAndroidVersionAtLeast(24))
							Control.SetText(Html.FromHtml(Element.Text ?? string.Empty, FromHtmlOptions.ModeCompact), TextView.BufferType.Spannable);
						else
#pragma warning disable CS0618 // Type or member is obsolete
							Control.SetText(Html.FromHtml(Element.Text ?? string.Empty), TextView.BufferType.Spannable);
#pragma warning restore CS0618 // Type or member is obsolete
						break;
 
					default:
						_view.Text = Element.UpdateFormsText(Element.Text, Element.TextTransform);
 
						break;
				}
 
				UpdateColor();
				UpdateFont();
 
				_wasFormatted = false;
			}
 
			_lastSizeRequest = null;
		}
 
		void UpdatePadding()
		{
			Control.SetPadding(
				(int)Context.ToPixels(Element.Padding.Left),
				(int)Context.ToPixels(Element.Padding.Top),
				(int)Context.ToPixels(Element.Padding.Right),
				(int)Context.ToPixels(Element.Padding.Bottom));
 
			_lastSizeRequest = null;
		}
 
		public override bool OnTouchEvent(MotionEvent e)
		{
			if (base.OnTouchEvent(e))
				return true;
 
			return _motionEventHelper.HandleMotionEvent(Parent, e);
		}
	}
}