Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Resolve Layout changes during native measure/arrange pass #12017

Merged
merged 3 commits into from
Sep 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Xamarin.Forms.Core/Device.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ public static IPlatformServices PlatformServices
set { s_platformServices = value; }
}

public static IPlatformInvalidate PlatformInvalidator { get; set; }

[EditorBrowsable(EditorBrowsableState.Never)]
public static IReadOnlyList<string> Flags { get; private set; }

Expand Down Expand Up @@ -283,5 +285,10 @@ public static class Styles

public static readonly Style CaptionStyle = new Style(typeof(Label)) { BaseResourceKey = CaptionStyleKey };
}

public static void Invalidate(VisualElement visualElement)
{
PlatformInvalidator?.Invalidate(visualElement);
}
}
}
8 changes: 8 additions & 0 deletions Xamarin.Forms.Core/IPlatformInvalidate.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Xamarin.Forms.Internals
{
public interface IPlatformInvalidate

{
void Invalidate(VisualElement visualElement);
}
}
28 changes: 20 additions & 8 deletions Xamarin.Forms.Core/Layout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -326,32 +326,44 @@ internal virtual void OnChildMeasureInvalidated(VisualElement child, Invalidatio
}

s_resolutionList.Add(new KeyValuePair<Layout, int>(this, GetElementDepth(this)));
if (!s_relayoutInProgress)
{
s_relayoutInProgress = true;

// Rather than recomputing the layout for each change as it happens, we accumulate them in
if (Device.PlatformInvalidator == null && !s_relayoutInProgress)
{
// Rather than recomputing the layout for each change as it happens, we accumulate them in
// s_resolutionList and schedule a single layout update operation to handle them all at once.
// This avoids a lot of unnecessary layout operations if something is triggering many property
// changes at once (e.g., a BindingContext change)

s_relayoutInProgress = true;

if (Dispatcher != null)
{
Dispatcher.BeginInvokeOnMainThread(ResolveLayoutChanges);
}
else
{
Device.BeginInvokeOnMainThread(ResolveLayoutChanges);
}
}
}
else
{
// If the platform supports PlatformServices2, queueing is unnecessary; the layout changes
// will be handled during the Layout's next Measure/Arrange pass
Device.Invalidate(this);
}
}

internal void ResolveLayoutChanges()
public void ResolveLayoutChanges()
{
// if thread safety mattered we would need to lock this and compareexchange above
s_relayoutInProgress = false;

if (s_resolutionList.Count == 0)
{
return;
}

IList<KeyValuePair<Layout, int>> copy = s_resolutionList;
s_resolutionList = new List<KeyValuePair<Layout, int>>();
s_relayoutInProgress = false;

foreach (KeyValuePair<Layout, int> kvp in copy)
{
Expand Down
3 changes: 3 additions & 0 deletions Xamarin.Forms.Core/VisualElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,10 @@ public void BatchCommit()
{
_batched = Math.Max(0, _batched - 1);
if (!Batched)
{
BatchCommitted?.Invoke(this, new EventArg<VisualElement>(this));
Device.Invalidate(this);
}
}

ResourceDictionary _resources;
Expand Down
20 changes: 18 additions & 2 deletions Xamarin.Forms.Platform.Android/Forms.cs
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,11 @@ static void SetupInit(
// We want this to be updated when we have a new activity (e.g. on a configuration change)
// because AndroidPlatformServices needs a current activity to launch URIs from
Profile.FramePartition("Device.PlatformServices");
Device.PlatformServices = new AndroidPlatformServices(activity);

var androidServices = new AndroidPlatformServices(activity);

Device.PlatformServices = androidServices;
Device.PlatformInvalidator = androidServices;

// use field and not property to avoid exception in getter
if (Device.info != null)
Expand Down Expand Up @@ -610,7 +614,7 @@ protected override Expression VisitMember(MemberExpression node)
}
}

class AndroidPlatformServices : IPlatformServices
class AndroidPlatformServices : IPlatformServices, IPlatformInvalidate
{
double _buttonDefaultSize;
double _editTextDefaultSize;
Expand Down Expand Up @@ -917,6 +921,18 @@ public SizeRequest GetNativeSize(VisualElement view, double widthConstraint, dou
return Platform.Android.Platform.GetNativeSize(view, widthConstraint, heightConstraint);
}

public void Invalidate(VisualElement visualElement)
{
var renderer = visualElement.GetRenderer();
if (renderer == null || renderer.View.IsDisposed())
{
return;
}

renderer.View.Invalidate();
renderer.View.RequestLayout();
}

public OSAppTheme RequestedTheme
{
get
Expand Down
10 changes: 10 additions & 0 deletions Xamarin.Forms.Platform.Android/Platform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1312,6 +1312,16 @@ protected override void Dispose(bool disposing)

bool ILayoutChanges.HasLayoutOccurred => _hasLayoutOccurred;

protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
if (Element is Layout layout)
{
layout.ResolveLayoutChanges();
}

base.OnMeasure(widthMeasureSpec, heightMeasureSpec);
}

protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
{
base.OnLayout(changed, left, top, right, bottom);
Expand Down
3 changes: 3 additions & 0 deletions Xamarin.Forms.Platform.Android/VisualElementTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,9 @@ void UpdateIsVisible()
aview.Visibility = ViewStates.Visible;
if (!view.IsVisible && aview.Visibility != ViewStates.Gone)
aview.Visibility = ViewStates.Gone;

aview.Invalidate();
aview.RequestLayout();
}

void UpdateNativeView(object sender, EventArgs e)
Expand Down
7 changes: 6 additions & 1 deletion Xamarin.Forms.Platform.UAP/Forms.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,12 @@ public static void Init(IActivatedEventArgs launchActivatedEventArgs, IEnumerabl

Device.SetIdiom(TargetIdiom.Tablet);
Device.SetFlowDirection(GetFlowDirection());
Device.PlatformServices = new WindowsPlatformServices(Window.Current.Dispatcher);

var platformServices = new WindowsPlatformServices(Window.Current.Dispatcher);

Device.PlatformServices = platformServices;
Device.PlatformInvalidator = platformServices;

Device.SetFlags(s_flags);
Device.Info = new WindowsDeviceInfo();

Expand Down
6 changes: 6 additions & 0 deletions Xamarin.Forms.Platform.UAP/LayoutRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,11 @@ void UpdateClipToBounds()
Clip = new RectangleGeometry { Rect = new WRect(0, 0, ActualWidth, ActualHeight) };
}
}

protected override Windows.Foundation.Size MeasureOverride(Windows.Foundation.Size availableSize)
{
Element?.ResolveLayoutChanges();
return base.MeasureOverride(availableSize);
}
}
}
13 changes: 12 additions & 1 deletion Xamarin.Forms.Platform.UAP/WindowsBasePlatformServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

namespace Xamarin.Forms.Platform.UWP
{
internal abstract class WindowsBasePlatformServices : IPlatformServices
internal abstract class WindowsBasePlatformServices : IPlatformServices, IPlatformInvalidate
{
const string WrongThreadError = "RPC_E_WRONG_THREAD";
readonly CoreDispatcher _dispatcher;
Expand Down Expand Up @@ -256,6 +256,17 @@ await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {
return await taskCompletionSource.Task;
}

public void Invalidate(VisualElement visualElement)
{
var renderer = Platform.GetRenderer(visualElement);
if (renderer == null)
{
return;
}

renderer.ContainerElement.InvalidateMeasure();
}

public OSAppTheme RequestedTheme => Windows.UI.Xaml.Application.Current.RequestedTheme == ApplicationTheme.Dark ? OSAppTheme.Dark : OSAppTheme.Light;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ protected override void Dispose(bool disposing)

_emptyUIView?.Dispose();
_emptyUIView = null;

_emptyViewFormsElement = null;

ItemsViewLayout?.Dispose();
Expand Down
21 changes: 20 additions & 1 deletion Xamarin.Forms.Platform.iOS/Forms.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,12 @@ public static void Init()
}
#endif
Device.SetFlags(s_flags);
Device.PlatformServices = new IOSPlatformServices();
var platformServices = new IOSPlatformServices();

Device.PlatformServices = platformServices;

#if __MOBILE__
Device.PlatformInvalidator = platformServices;
Device.Info = new IOSDeviceInfo();
#else
Device.Info = new Platform.macOS.MacDeviceInfo();
Expand Down Expand Up @@ -227,6 +231,9 @@ protected override Expression VisitMember(MemberExpression node)
}

class IOSPlatformServices : IPlatformServices
#if __MOBILE__
, IPlatformInvalidate
#endif
{
readonly double _fontScalingFactor = 1;
public IOSPlatformServices()
Expand Down Expand Up @@ -782,6 +789,18 @@ static UIViewController GetCurrentViewController(bool throwIfNull = true)

return viewController;
}

public void Invalidate(VisualElement visualElement)
{
var renderer = Platform.iOS.Platform.GetRenderer(visualElement);

if (renderer == null)
{
return;
}

renderer.NativeView.SetNeedsLayout();
}
#endif
}
}
Expand Down
20 changes: 20 additions & 0 deletions Xamarin.Forms.Platform.iOS/Platform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,26 @@ public override UIView HitTest(CGPoint point, UIEvent uievent)

return result;
}

void ResolveLayoutChanges()
{
if (Element is Layout layout)
{
layout.ResolveLayoutChanges();
}
}

public override void LayoutSubviews()
{
ResolveLayoutChanges();
base.LayoutSubviews();
}

public override CGSize SizeThatFits(CGSize size)
{
ResolveLayoutChanges();
return base.SizeThatFits(size);
}
}

internal static string ResolveMsAppDataUri(Uri uri)
Expand Down