Resolve Layout changes during native measure/arrange pass (#12017)
* Process layout changes during measure/arrange rather than queueing on the UI thread * Add disposed check on invalidates * Attempt to fix occasional GroupableItemsViewController disposed crash
This commit is contained in:
Родитель
7484457a4c
Коммит
e8d06f357e
|
@ -90,6 +90,8 @@ namespace Xamarin.Forms
|
|||
set { s_platformServices = value; }
|
||||
}
|
||||
|
||||
public static IPlatformInvalidate PlatformInvalidator { get; set; }
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static IReadOnlyList<string> Flags { get; private set; }
|
||||
|
||||
|
@ -283,5 +285,10 @@ namespace Xamarin.Forms
|
|||
|
||||
public static readonly Style CaptionStyle = new Style(typeof(Label)) { BaseResourceKey = CaptionStyleKey };
|
||||
}
|
||||
|
||||
public static void Invalidate(VisualElement visualElement)
|
||||
{
|
||||
PlatformInvalidator?.Invalidate(visualElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
namespace Xamarin.Forms.Internals
|
||||
{
|
||||
public interface IPlatformInvalidate
|
||||
|
||||
{
|
||||
void Invalidate(VisualElement visualElement);
|
||||
}
|
||||
}
|
|
@ -326,15 +326,16 @@ namespace Xamarin.Forms
|
|||
}
|
||||
|
||||
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);
|
||||
|
@ -342,16 +343,27 @@ namespace Xamarin.Forms
|
|||
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)
|
||||
{
|
||||
|
|
|
@ -663,7 +663,10 @@ namespace Xamarin.Forms
|
|||
{
|
||||
_batched = Math.Max(0, _batched - 1);
|
||||
if (!Batched)
|
||||
{
|
||||
BatchCommitted?.Invoke(this, new EventArg<VisualElement>(this));
|
||||
Device.Invalidate(this);
|
||||
}
|
||||
}
|
||||
|
||||
ResourceDictionary _resources;
|
||||
|
|
|
@ -296,7 +296,11 @@ namespace Xamarin.Forms
|
|||
// 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)
|
||||
|
@ -610,7 +614,7 @@ namespace Xamarin.Forms
|
|||
}
|
||||
}
|
||||
|
||||
class AndroidPlatformServices : IPlatformServices
|
||||
class AndroidPlatformServices : IPlatformServices, IPlatformInvalidate
|
||||
{
|
||||
double _buttonDefaultSize;
|
||||
double _editTextDefaultSize;
|
||||
|
@ -917,6 +921,18 @@ namespace Xamarin.Forms
|
|||
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
|
||||
|
|
|
@ -1312,6 +1312,16 @@ namespace Xamarin.Forms.Platform.Android
|
|||
|
||||
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);
|
||||
|
|
|
@ -336,6 +336,9 @@ namespace Xamarin.Forms.Platform.Android
|
|||
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)
|
||||
|
|
|
@ -49,7 +49,12 @@ namespace Xamarin.Forms
|
|||
|
||||
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();
|
||||
|
||||
|
|
|
@ -80,5 +80,11 @@ namespace Xamarin.Forms.Platform.UWP
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ using IOPath = System.IO.Path;
|
|||
|
||||
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;
|
||||
|
@ -256,6 +256,17 @@ namespace Xamarin.Forms.Platform.UWP
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
|
||||
_emptyUIView?.Dispose();
|
||||
_emptyUIView = null;
|
||||
|
||||
|
||||
_emptyViewFormsElement = null;
|
||||
|
||||
ItemsViewLayout?.Dispose();
|
||||
|
|
|
@ -191,8 +191,12 @@ namespace Xamarin.Forms
|
|||
}
|
||||
#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();
|
||||
|
@ -238,6 +242,9 @@ namespace Xamarin.Forms
|
|||
}
|
||||
|
||||
class IOSPlatformServices : IPlatformServices
|
||||
#if __MOBILE__
|
||||
, IPlatformInvalidate
|
||||
#endif
|
||||
{
|
||||
readonly double _fontScalingFactor = 1;
|
||||
public IOSPlatformServices()
|
||||
|
@ -793,6 +800,18 @@ namespace Xamarin.Forms
|
|||
|
||||
return viewController;
|
||||
}
|
||||
|
||||
public void Invalidate(VisualElement visualElement)
|
||||
{
|
||||
var renderer = Platform.iOS.Platform.GetRenderer(visualElement);
|
||||
|
||||
if (renderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
renderer.NativeView.SetNeedsLayout();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -582,6 +582,26 @@ namespace Xamarin.Forms.Platform.iOS
|
|||
|
||||
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)
|
||||
|
|
Загрузка…
Ссылка в новой задаче