maui-linux/Xamarin.Forms.Platform.Android/VisualElementTracker.cs

458 строки
14 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Views;
using AView = Android.Views.View;
using Object = Java.Lang.Object;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Platform.Android
{
public class VisualElementTracker : IDisposable
{
readonly EventHandler<EventArg<VisualElement>> _batchCommittedHandler;
readonly IList<string> _batchedProperties = new List<string>();
readonly PropertyChangedEventHandler _propertyChangedHandler;
Context _context;
bool _disposed;
VisualElement _element;
bool _initialUpdateNeeded = true;
bool _layoutNeeded;
IVisualElementRenderer _renderer;
public VisualElementTracker(IVisualElementRenderer renderer)
{
if (renderer == null)
throw new ArgumentNullException("renderer");
_batchCommittedHandler = HandleRedrawNeeded;
_propertyChangedHandler = HandlePropertyChanged;
_renderer = renderer;
_context = renderer.View.Context;
_renderer.ElementChanged += RendererOnElementChanged;
VisualElement view = renderer.Element;
SetElement(null, view);
renderer.View.SetCameraDistance(3600);
renderer.View.AddOnAttachStateChangeListener(AttachTracker.Instance);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
_disposed = true;
if (disposing)
{
SetElement(_element, null);
if (_renderer != null)
{
_renderer.ElementChanged -= RendererOnElementChanged;
_renderer.View.RemoveOnAttachStateChangeListener(AttachTracker.Instance);
_renderer = null;
_context = null;
}
}
}
public void UpdateLayout()
{
Performance.Start(out string reference);
VisualElement view = _renderer.Element;
AView aview = _renderer.View;
var headlessOffset = CompressedLayout.GetHeadlessOffset(view);
var x = (int)_context.ToPixels(view.X + headlessOffset.X);
var y = (int)_context.ToPixels(view.Y + headlessOffset.Y);
var width = Math.Max(0, (int)_context.ToPixels(view.Width));
var height = Math.Max(0, (int)_context.ToPixels(view.Height));
var formsViewGroup = aview as FormsViewGroup;
if (formsViewGroup == null)
{
Performance.Start(reference, "Measure");
aview.Measure(MeasureSpecFactory.MakeMeasureSpec(width, MeasureSpecMode.Exactly), MeasureSpecFactory.MakeMeasureSpec(height, MeasureSpecMode.Exactly));
Performance.Stop(reference, "Measure");
Performance.Start(reference, "Layout");
aview.Layout(x, y, x + width, y + height);
Performance.Stop(reference, "Layout");
}
else
{
Performance.Start(reference, "MeasureAndLayout");
formsViewGroup.MeasureAndLayout(MeasureSpecFactory.MakeMeasureSpec(width, MeasureSpecMode.Exactly), MeasureSpecFactory.MakeMeasureSpec(height, MeasureSpecMode.Exactly), x, y, x + width, y + height);
Performance.Stop(reference, "MeasureAndLayout");
}
// If we're running sufficiently new Android, we have to make sure to update the ClipBounds to
// match the new size of the ViewGroup
if ((int)Build.VERSION.SdkInt >= 18)
{
UpdateClipToBounds();
}
Performance.Stop(reference);
//On Width or Height changes, the anchors needs to be updated
UpdateAnchorX();
UpdateAnchorY();
}
void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (_renderer == null)
{
return;
}
if (e.PropertyName == Layout.IsClippedToBoundsProperty.PropertyName)
{
UpdateClipToBounds();
return;
}
if (_renderer.Element.Batched)
{
if (e.PropertyName == VisualElement.XProperty.PropertyName || e.PropertyName == VisualElement.YProperty.PropertyName || e.PropertyName == VisualElement.WidthProperty.PropertyName ||
e.PropertyName == VisualElement.HeightProperty.PropertyName)
_layoutNeeded = true;
else if (e.PropertyName == VisualElement.AnchorXProperty.PropertyName || e.PropertyName == VisualElement.AnchorYProperty.PropertyName || e.PropertyName == VisualElement.ScaleProperty.PropertyName || e.PropertyName == VisualElement.ScaleXProperty.PropertyName || e.PropertyName == VisualElement.ScaleYProperty.PropertyName ||
e.PropertyName == VisualElement.RotationProperty.PropertyName || e.PropertyName == VisualElement.RotationXProperty.PropertyName || e.PropertyName == VisualElement.RotationYProperty.PropertyName ||
e.PropertyName == VisualElement.IsVisibleProperty.PropertyName || e.PropertyName == VisualElement.OpacityProperty.PropertyName ||
e.PropertyName == VisualElement.TranslationXProperty.PropertyName || e.PropertyName == VisualElement.TranslationYProperty.PropertyName)
{
if (!_batchedProperties.Contains(e.PropertyName))
_batchedProperties.Add(e.PropertyName);
}
return;
}
if (e.PropertyName == VisualElement.XProperty.PropertyName || e.PropertyName == VisualElement.YProperty.PropertyName || e.PropertyName == VisualElement.WidthProperty.PropertyName ||
e.PropertyName == VisualElement.HeightProperty.PropertyName)
MaybeRequestLayout();
else if (e.PropertyName == VisualElement.AnchorXProperty.PropertyName)
UpdateAnchorX();
else if (e.PropertyName == VisualElement.AnchorYProperty.PropertyName)
UpdateAnchorY();
else if (e.PropertyName == VisualElement.ScaleProperty.PropertyName
|| e.PropertyName == VisualElement.ScaleXProperty.PropertyName
|| e.PropertyName == VisualElement.ScaleYProperty.PropertyName)
UpdateScale();
else if (e.PropertyName == VisualElement.RotationProperty.PropertyName)
UpdateRotation();
else if (e.PropertyName == VisualElement.RotationXProperty.PropertyName)
UpdateRotationX();
else if (e.PropertyName == VisualElement.RotationYProperty.PropertyName)
UpdateRotationY();
else if (e.PropertyName == VisualElement.IsVisibleProperty.PropertyName)
UpdateIsVisible();
else if (e.PropertyName == VisualElement.OpacityProperty.PropertyName)
UpdateOpacity();
else if (e.PropertyName == VisualElement.TranslationXProperty.PropertyName)
UpdateTranslationX();
else if (e.PropertyName == VisualElement.TranslationYProperty.PropertyName)
UpdateTranslationY();
else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
UpdateIsEnabled();
}
void HandleRedrawNeeded(object sender, EventArg<VisualElement> e)
{
foreach (string propertyName in _batchedProperties)
HandlePropertyChanged(this, new PropertyChangedEventArgs(propertyName));
_batchedProperties.Clear();
if (_layoutNeeded)
MaybeRequestLayout();
_layoutNeeded = false;
}
void HandleViewAttachedToWindow()
{
if (_initialUpdateNeeded)
{
UpdateNativeView(this, EventArgs.Empty);
_initialUpdateNeeded = false;
}
UpdateClipToBounds();
}
void MaybeRequestLayout()
{
var isInLayout = false;
if ((int)Build.VERSION.SdkInt >= 18)
isInLayout = _renderer.View.IsInLayout;
if (!isInLayout && !_renderer.View.IsLayoutRequested)
_renderer.View.RequestLayout();
}
void RendererOnElementChanged(object sender, VisualElementChangedEventArgs args)
{
SetElement(args.OldElement, args.NewElement);
}
void SetElement(VisualElement oldElement, VisualElement newElement)
{
if (oldElement != null)
{
oldElement.BatchCommitted -= _batchCommittedHandler;
oldElement.PropertyChanged -= _propertyChangedHandler;
_context = null;
}
_element = newElement;
if (newElement != null)
{
newElement.BatchCommitted += _batchCommittedHandler;
newElement.PropertyChanged += _propertyChangedHandler;
_context = _renderer.View.Context;
if (oldElement != null)
{
AView view = _renderer.View;
// ReSharper disable CompareOfFloatsByEqualityOperator
if (oldElement.AnchorX != newElement.AnchorX)
UpdateAnchorX();
if (oldElement.AnchorY != newElement.AnchorY)
UpdateAnchorY();
if (oldElement.IsVisible != newElement.IsVisible)
UpdateIsVisible();
if (oldElement.IsEnabled != newElement.IsEnabled)
view.Enabled = newElement.IsEnabled;
if (oldElement.Opacity != newElement.Opacity)
UpdateOpacity();
if (oldElement.Rotation != newElement.Rotation)
UpdateRotation();
if (oldElement.RotationX != newElement.RotationX)
UpdateRotationX();
if (oldElement.RotationY != newElement.RotationY)
UpdateRotationY();
if (oldElement.Scale != newElement.Scale || oldElement.ScaleX != newElement.ScaleX || oldElement.ScaleY != newElement.ScaleY)
UpdateScale();
// ReSharper restore CompareOfFloatsByEqualityOperator
_initialUpdateNeeded = false;
}
}
}
void UpdateAnchorX()
{
VisualElement view = _renderer.Element;
AView aview = _renderer.View;
float currentPivot = aview.PivotX;
var target = (float)(view.AnchorX * _context.ToPixels(view.Width));
if (currentPivot != target)
aview.PivotX = target;
}
void UpdateAnchorY()
{
VisualElement view = _renderer.Element;
AView aview = _renderer.View;
float currentPivot = aview.PivotY;
var target = (float)(view.AnchorY * _context.ToPixels(view.Height));
if (currentPivot != target)
aview.PivotY = target;
}
void UpdateClipToBounds()
{
if (!(_renderer.Element is Layout layout))
{
return;
}
bool shouldClip = layout.IsClippedToBounds;
// setClipBounds is only available in API 18 +
if ((int)Build.VERSION.SdkInt >= 18)
{
if (!(_renderer.View is ViewGroup viewGroup))
{
return;
}
// Forms layouts should not impose clipping on their children
viewGroup.SetClipChildren(false);
// But if IsClippedToBounds is true, they _should_ enforce clipping at their own edges
viewGroup.ClipBounds = shouldClip ? new Rect(0, 0, viewGroup.Width, viewGroup.Height) : null;
}
else
{
// For everything in 17 and below, use the setClipChildren method
if (!(_renderer.View.Parent is ViewGroup parent))
return;
if ((int)Build.VERSION.SdkInt >= 18 && parent.ClipChildren == shouldClip)
return;
parent.SetClipChildren(shouldClip);
parent.Invalidate();
}
}
void UpdateIsVisible()
{
VisualElement view = _renderer.Element;
AView aview = _renderer.View;
if (view.IsVisible && aview.Visibility != ViewStates.Visible)
aview.Visibility = ViewStates.Visible;
if (!view.IsVisible && aview.Visibility != ViewStates.Gone)
aview.Visibility = ViewStates.Gone;
}
void UpdateNativeView(object sender, EventArgs e)
{
Performance.Start(out string reference);
VisualElement view = _renderer.Element;
AView aview = _renderer.View;
if (aview is FormsViewGroup formsViewGroup)
{
formsViewGroup.SendBatchUpdate((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 * (float)view.ScaleX,
(float)view.Scale * (float)view.ScaleY,
_context.ToPixels(view.TranslationX),
_context.ToPixels(view.TranslationY));
}
else
{
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 * (float)view.ScaleX,
(float)view.Scale * (float)view.ScaleY,
_context.ToPixels(view.TranslationX),
_context.ToPixels(view.TranslationY));
}
Performance.Stop(reference);
}
void UpdateOpacity()
{
Performance.Start(out string reference);
VisualElement view = _renderer.Element;
AView aview = _renderer.View;
aview.Alpha = (float)view.Opacity;
Performance.Stop(reference);
}
void UpdateRotation()
{
VisualElement view = _renderer.Element;
AView aview = _renderer.View;
aview.Rotation = (float)view.Rotation;
}
void UpdateRotationX()
{
VisualElement view = _renderer.Element;
AView aview = _renderer.View;
aview.RotationX = (float)view.RotationX;
}
void UpdateRotationY()
{
VisualElement view = _renderer.Element;
AView aview = _renderer.View;
aview.RotationY = (float)view.RotationY;
}
void UpdateScale()
{
VisualElement view = _renderer.Element;
AView aview = _renderer.View;
aview.ScaleX = (float)view.Scale * (float)view.ScaleX;
aview.ScaleY = (float)view.Scale * (float)view.ScaleY;
}
void UpdateTranslationX()
{
VisualElement view = _renderer.Element;
AView aview = _renderer.View;
aview.TranslationX = _context.ToPixels(view.TranslationX);
}
void UpdateTranslationY()
{
VisualElement view = _renderer.Element;
AView aview = _renderer.View;
aview.TranslationY = _context.ToPixels(view.TranslationY);
}
void UpdateIsEnabled()
{
_renderer.View.Enabled = _renderer.Element.IsEnabled;
}
class AttachTracker : Object, AView.IOnAttachStateChangeListener
{
public static readonly AttachTracker Instance = new AttachTracker();
public void OnViewAttachedToWindow(AView attachedView)
{
var renderer = attachedView as IVisualElementRenderer;
if (renderer == null || renderer.Tracker == null)
return;
renderer.Tracker.HandleViewAttachedToWindow();
}
public void OnViewDetachedFromWindow(AView detachedView)
{
}
}
}
}