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:
E.Z. Hart 2020-09-24 19:12:10 -06:00 коммит произвёл GitHub
Родитель 7484457a4c
Коммит e8d06f357e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 134 добавлений и 14 удалений

Просмотреть файл

@ -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)