diff --git a/build/test-scripts/run-netcore-mobile-template-tests.ps1 b/build/test-scripts/run-netcore-mobile-template-tests.ps1 index d223f9b7018c..c80209b45b56 100644 --- a/build/test-scripts/run-netcore-mobile-template-tests.ps1 +++ b/build/test-scripts/run-netcore-mobile-template-tests.ps1 @@ -15,7 +15,7 @@ function CleanupTree() git clean -fdx -e *.binlog } -$default = @('/ds', '/v:m', '/p:UseDotNetNativeToolchain=false', '/p:PackageCertificateKeyFile=') +$default = @('/ds', '/v:m', '/p:UseDotNetNativeToolchain=false', '/p:PackageCertificateKeyFile=', '/p:RunAOTCompilation=false') if ($IsWindows) { diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_UIElement.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_UIElement.cs index fa8acc766dd1..60fa526b05ca 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_UIElement.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml/Given_UIElement.cs @@ -938,7 +938,11 @@ public async Task When_InvalidatingMeasureExplicitly() using var _ = new AssertionScope(); +#if __ANDROID__ + ctl1.MeasureCount.Should().Be(2); +#else ctl1.MeasureCount.Should().Be(1); +#endif ctl2.MeasureCount.Should().Be(2); ctl3.MeasureCount.Should().Be(1); diff --git a/src/Uno.UI/Extensions/ViewExtensions.Android.cs b/src/Uno.UI/Extensions/ViewExtensions.Android.cs index 4ac82ffcda42..c6c2124279c3 100644 --- a/src/Uno.UI/Extensions/ViewExtensions.Android.cs +++ b/src/Uno.UI/Extensions/ViewExtensions.Android.cs @@ -21,6 +21,7 @@ using Android.Views.Animations; using Microsoft.UI.Xaml.Controls; using Uno.UI.Controls; +using Microsoft.UI.Xaml.Media; namespace Uno.UI { @@ -705,6 +706,10 @@ public static string ShowLocalVisualTree(this ViewGroup viewGroup, int fromHeigh { root = parent; } + else if (root is DependencyObject @do && VisualTreeHelper.GetParent(@do) is ViewGroup managedParent) + { + root = managedParent; + } else { break; diff --git a/src/Uno.UI/UI/Xaml/Controls/TextBox/TextBoxView.Android.cs b/src/Uno.UI/UI/Xaml/Controls/TextBox/TextBoxView.Android.cs index 2a1ef473c064..8069abc79a66 100644 --- a/src/Uno.UI/UI/Xaml/Controls/TextBox/TextBoxView.Android.cs +++ b/src/Uno.UI/UI/Xaml/Controls/TextBox/TextBoxView.Android.cs @@ -28,6 +28,7 @@ internal partial class TextBoxView : EditText, DependencyObject internal TextBox? Owner => _ownerRef?.Target as TextBox; private Action? _foregroundChanged; + private bool _isDisposed; public TextBoxView(TextBox owner) : base(ContextHelper.Current) @@ -313,10 +314,23 @@ private void OnForegroundChanged(Brush oldValue, Brush newValue) void ApplyColor() { + if (_isDisposed) + { + // Binding changes may happen after the + // underlying control has been disposed + return; + } + SetTextColor(scb.Color); SetCursorColor(scb.Color); } } } + + protected override void Dispose(bool disposing) + { + _isDisposed = true; + base.Dispose(disposing); + } } } diff --git a/src/Uno.UI/UI/Xaml/ILayouterElement.cs b/src/Uno.UI/UI/Xaml/ILayouterElement.cs index 9d43be9e9e72..68f5f0d7d2fa 100644 --- a/src/Uno.UI/UI/Xaml/ILayouterElement.cs +++ b/src/Uno.UI/UI/Xaml/ILayouterElement.cs @@ -74,6 +74,7 @@ internal static bool DoMeasure(this ILayouterElement element, Size availableSize if (isDirty || frameworkElement is null) { // We must reset the flag **BEFORE** doing the actual measure, so the elements are able to re-invalidate themselves + // TODO: We are not controlling measure dirty path on Android. If we did in future, we must clear it here as well. frameworkElement?.ClearLayoutFlags(UIElement.LayoutFlag.MeasureDirty); // The dirty flag is explicitly set on this element @@ -91,7 +92,9 @@ internal static bool DoMeasure(this ILayouterElement element, Size availableSize LayoutInformation.SetAvailableSize(element, availableSize); } - return true; // end of isDirty processing + // TODO: This is NOT correct. + // We shouldn't return here. Skipping children measure is incorrect but fixing it on Android isn't trivial. + return true; } // The measure dirty flag is set on one of the descendents: @@ -110,6 +113,8 @@ internal static bool DoMeasure(this ILayouterElement element, Size availableSize { var previousDesiredSize = childAsUIElement.m_desiredSize; childAsUIElement.EnsureLayoutStorage(); + + // TODO: This is NOT correct. This should call DoMeasure (the same method we are in currently!) element.Layouter.MeasureChild(child, childAsUIElement.m_previousAvailableSize); var newDesiredSize = childAsUIElement.m_desiredSize; if (newDesiredSize != previousDesiredSize) diff --git a/src/Uno.UI/UI/Xaml/Media/VisualTreeHelper.cs b/src/Uno.UI/UI/Xaml/Media/VisualTreeHelper.cs index ec4af5e05f56..0e74bb600b47 100644 --- a/src/Uno.UI/UI/Xaml/Media/VisualTreeHelper.cs +++ b/src/Uno.UI/UI/Xaml/Media/VisualTreeHelper.cs @@ -393,6 +393,31 @@ internal static void AddChild(UIElement view, UIElement child) { #if __ANDROID__ view.AddView(child); + + // Reset to original (invalidated) state + child.ResetLayoutFlags(); + if (view.IsMeasureDirtyPathDisabled) + { + FrameworkElementHelper.SetUseMeasurePathDisabled(child); // will invalidate too + } + else + { + child.InvalidateMeasure(); + } + + if (view.IsArrangeDirtyPathDisabled) + { + FrameworkElementHelper.SetUseArrangePathDisabled(child); // will invalidate too + } + else + { + child.InvalidateArrange(); + } + + // Force a new measure of this element (the parent of the new child) + view.InvalidateMeasure(); + view.InvalidateArrange(); + #elif __IOS__ || __MACOS__ view.AddSubview(child); #elif __CROSSRUNTIME__ diff --git a/src/Uno.UI/UI/Xaml/UIElement.cs b/src/Uno.UI/UI/Xaml/UIElement.cs index 2753f624f8d1..bd1c88625bae 100644 --- a/src/Uno.UI/UI/Xaml/UIElement.cs +++ b/src/Uno.UI/UI/Xaml/UIElement.cs @@ -1155,6 +1155,25 @@ public void InvalidateMeasure() // Use a non-virtual version of the RequestLayout method, for performance. base.RequestLayout(); SetLayoutFlags(LayoutFlag.MeasureDirty); + + // HACK: Android's implementation of measure/arrange is not accurate. See comments in LayouterElementExtensions.DoMeasure + global::Android.Views.IViewParent parent = this; + parent = parent.Parent; + while (parent is not null) + { + if (parent is UIElement parentAsUIElement) + { + parentAsUIElement.InvalidateMeasure(); + break; + } + else + { + parent.RequestLayout(); + } + + parent = parent.Parent; + } + #elif __IOS__ SetNeedsLayout(); SetLayoutFlags(LayoutFlag.MeasureDirty);