|
using System;
using System.ComponentModel;
using Android.Content;
using Android.Graphics;
using Android.Util;
using Android.Views;
using AndroidX.AppCompat.Widget;
using AndroidX.Core.View;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Controls.PlatformConfiguration.AndroidSpecific;
using AColor = Android.Graphics.Color;
using AView = Android.Views.View;
namespace Microsoft.Maui.Controls.Compatibility.Platform.Android.FastRenderers
{
[System.Obsolete(Compatibility.Hosting.MauiAppBuilderExtensions.UseMapperInstead)]
public class ButtonRenderer : AppCompatButton,
IBorderVisualElementRenderer, IButtonLayoutRenderer, IVisualElementRenderer, IViewRenderer, ITabStop,
AView.IOnAttachStateChangeListener, AView.IOnFocusChangeListener, AView.IOnClickListener, AView.IOnTouchListener
{
float _defaultFontSize;
int? _defaultLabelFor;
Typeface _defaultTypeface;
bool _isDisposed;
bool _inputTransparent;
Lazy<TextColorSwitcher> _textColorSwitcher;
AutomationPropertiesProvider _automationPropertiesProvider;
VisualElementTracker _tracker;
VisualElementRenderer _visualElementRenderer;
BorderBackgroundManager _backgroundTracker;
ButtonLayoutManager _buttonLayoutManager;
IPlatformElementConfiguration<PlatformConfiguration.Android, Button> _platformElementConfiguration;
Button _button;
bool _hasLayoutOccurred;
public event EventHandler<VisualElementChangedEventArgs> ElementChanged;
public event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged;
public ButtonRenderer(Context context) : base(context)
{
Initialize();
}
protected Button Element => Button;
protected AppCompatButton Control => this;
VisualElement IBorderVisualElementRenderer.Element => Element;
VisualElement IVisualElementRenderer.Element => Element;
AView IVisualElementRenderer.View => this;
VisualElementTracker IVisualElementRenderer.Tracker => _tracker;
Button Button
{
get => _button;
set
{
_button = value;
_platformElementConfiguration = null;
}
}
AView ITabStop.TabStop => this;
void IOnClickListener.OnClick(AView v) => ButtonElementManager.OnClick(Button, Button, v);
bool IOnTouchListener.OnTouch(AView v, MotionEvent e) => ButtonElementManager.OnTouch(Button, Button, v, e);
void IOnAttachStateChangeListener.OnViewAttachedToWindow(AView attachedView) =>
_buttonLayoutManager.OnViewAttachedToWindow(attachedView);
void IOnAttachStateChangeListener.OnViewDetachedFromWindow(AView detachedView) =>
_buttonLayoutManager.OnViewDetachedFromWindow(detachedView);
[PortHandler]
void IOnFocusChangeListener.OnFocusChange(AView v, bool hasFocus)
{
((IElementController)Button).SetValueFromRenderer(VisualElement.IsFocusedPropertyKey, hasFocus);
}
SizeRequest IVisualElementRenderer.GetDesiredSize(int widthConstraint, int heightConstraint)
{
if (_isDisposed)
{
return new SizeRequest();
}
var hint = Control.Hint;
bool setHint = Control.LayoutParameters != null;
if (!string.IsNullOrWhiteSpace(hint) && setHint)
{
Control.Hint = string.Empty;
}
var result = _buttonLayoutManager.GetDesiredSize(widthConstraint, heightConstraint);
if (setHint)
Control.Hint = hint;
return result;
}
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(out string reference);
OnElementChanged(new ElementChangedEventArgs<Button>(oldElement as Button, Button));
SendVisualElementInitialized(element, this);
Performance.Stop(reference);
}
void IVisualElementRenderer.SetLabelFor(int? id)
{
if (_defaultLabelFor == null)
{
_defaultLabelFor = ViewCompat.GetLabelFor(this);
}
ViewCompat.SetLabelFor(this, (int)(id ?? _defaultLabelFor));
}
void IVisualElementRenderer.UpdateLayout() => _tracker?.UpdateLayout();
void IViewRenderer.MeasureExactly()
{
ViewRenderer.MeasureExactly(this, Element, Context);
}
public override void Draw(Canvas canvas)
{
canvas.ClipShape(Context, Element);
if (_backgroundTracker?.BackgroundDrawable != null)
_backgroundTracker.BackgroundDrawable.DrawCircle(canvas, canvas.Width, canvas.Height, base.Draw);
else
base.Draw(canvas);
}
protected override void Dispose(bool disposing)
{
if (_isDisposed)
{
return;
}
_isDisposed = true;
if (disposing)
{
SetOnClickListener(null);
SetOnTouchListener(null);
RemoveOnAttachStateChangeListener(this);
OnFocusChangeListener = null;
if (Element != null)
{
Element.PropertyChanged -= OnElementPropertyChanged;
}
_automationPropertiesProvider?.Dispose();
_tracker?.Dispose();
_visualElementRenderer?.Dispose();
_backgroundTracker?.Dispose();
_backgroundTracker = null;
_buttonLayoutManager?.Dispose();
_buttonLayoutManager = null;
if (Element != null)
{
if (Platform.GetRenderer(Element) == this)
Element.ClearValue(Platform.RendererProperty);
}
}
base.Dispose(disposing);
}
public override bool OnTouchEvent(MotionEvent e)
{
if (!Enabled || (_inputTransparent && Enabled))
return false;
return base.OnTouchEvent(e);
}
protected virtual void OnElementChanged(ElementChangedEventArgs<Button> e)
{
ElementChanged?.Invoke(this, new VisualElementChangedEventArgs(e.OldElement, e.NewElement));
if (e.OldElement != null)
{
e.OldElement.PropertyChanged -= OnElementPropertyChanged;
}
if (e.NewElement != null && !_isDisposed)
{
this.EnsureId();
if (_tracker == null)
{
// Can't set up the tracker in the constructor because it access the Element (for now)
SetTracker(new VisualElementTracker(this));
}
e.NewElement.PropertyChanged += OnElementPropertyChanged;
_textColorSwitcher = new Lazy<TextColorSwitcher>(
() => new TextColorSwitcher(TextColors, e.NewElement.UseLegacyColorManagement()));
UpdateFont();
UpdateTextColor();
UpdateInputTransparent();
UpdateBackgroundColor();
UpdateCharacterSpacing();
_buttonLayoutManager?.Update();
ElevationHelper.SetElevation(this, e.NewElement);
}
}
protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (this.IsDisposed())
{
return;
}
if (Control?.LayoutParameters == null && _hasLayoutOccurred)
{
ElementPropertyChanged?.Invoke(this, e);
return;
}
if (e.PropertyName == Button.TextColorProperty.PropertyName)
{
UpdateTextColor();
}
else if (e.PropertyName == FontElement.FontAttributesProperty.PropertyName
|| e.PropertyName == FontElement.FontAutoScalingEnabledProperty.PropertyName
|| e.PropertyName == FontElement.FontFamilyProperty.PropertyName
|| e.PropertyName == FontElement.FontSizeProperty.PropertyName)
{
UpdateFont();
}
else if (e.PropertyName == Button.CharacterSpacingProperty.PropertyName)
{
UpdateCharacterSpacing();
}
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)
{
_buttonLayoutManager?.OnLayout(changed, l, t, r, b);
base.OnLayout(changed, l, t, r, b);
_hasLayoutOccurred = true;
}
void SetTracker(VisualElementTracker tracker)
{
_tracker = tracker;
}
void UpdateBackgroundColor()
{
_backgroundTracker?.UpdateDrawable();
}
internal void OnNativeFocusChanged(bool hasFocus)
{
}
internal void SendVisualElementInitialized(VisualElement element, AView nativeView)
{
element.SendViewInitialized(nativeView);
}
void Initialize()
{
_automationPropertiesProvider = new AutomationPropertiesProvider(this);
_buttonLayoutManager = new ButtonLayoutManager(this);
_backgroundTracker = new BorderBackgroundManager(this);
_visualElementRenderer = new VisualElementRenderer(this);
SoundEffectsEnabled = false;
SetOnClickListener(this);
SetOnTouchListener(this);
AddOnAttachStateChangeListener(this);
OnFocusChangeListener = this;
Tag = this;
}
[PortHandler]
void UpdateFont()
{
if (Element == null || _isDisposed)
{
return;
}
Font font = (Button as ITextStyle).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(Element.RequireFontManager());
SetTextSize(ComplexUnitType.Sp, (float)font.Size);
}
}
void UpdateInputTransparent()
{
if (Element == null || _isDisposed)
{
return;
}
_inputTransparent = Element.InputTransparent;
}
void UpdateTextColor()
{
if (Element == null || _isDisposed || _textColorSwitcher == null)
{
return;
}
_textColorSwitcher.Value.UpdateTextColor(this, Button.TextColor);
}
[PortHandler]
void UpdateCharacterSpacing()
{
LetterSpacing = Button.CharacterSpacing.ToEm();
}
float IBorderVisualElementRenderer.ShadowRadius => ShadowRadius;
float IBorderVisualElementRenderer.ShadowDx => ShadowDx;
float IBorderVisualElementRenderer.ShadowDy => ShadowDy;
AColor IBorderVisualElementRenderer.ShadowColor => ShadowColor;
bool IBorderVisualElementRenderer.UseDefaultPadding() => OnThisPlatform().UseDefaultPadding();
bool IBorderVisualElementRenderer.UseDefaultShadow() => OnThisPlatform().UseDefaultShadow();
bool IBorderVisualElementRenderer.IsShadowEnabled() => true;
AView IBorderVisualElementRenderer.View => this;
IPlatformElementConfiguration<PlatformConfiguration.Android, Button> OnThisPlatform()
{
if (_platformElementConfiguration == null)
_platformElementConfiguration = Button.OnThisPlatform();
return _platformElementConfiguration;
}
AppCompatButton IButtonLayoutRenderer.View => this;
Button IButtonLayoutRenderer.Element => this.Element;
}
}
|