Fix Bug 7823 - Frame corner radius (#8032)
authorFelipe Baltazar <felipe.dasilvabaltazar@gmail.com>
Tue, 19 Nov 2019 10:09:16 +0000 (07:09 -0300)
committerGerald Versluis <gerald.versluis@microsoft.com>
Tue, 19 Nov 2019 10:09:16 +0000 (11:09 +0100)
* Fix Bug 7823

* Code review

Co-Authored-By: Gerald Versluis <github@geraldversluis.nl>
* Code review ♻

* Code review ♻

* revert 7f93346a3d1d3261d05843084d1934d5201b0c75

* Revert d9ad8f31507e8373a2db1f4b9eb737b256dcf6ad

* Update Xamarin.Forms.Controls.Issues.Shared.projitems

* Update Xamarin.Forms.Controls.Issues.Shared.projitems

* rebase 4.4.0 fixes

* fix rebase 4.4.0

* fix rebase 4.4.0

* Fix test 7823

* Fixes api 19 test

Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7823.cs [new file with mode: 0644]
Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
Xamarin.Forms.Core.UITests.Shared/Utilities/AppExtensions.cs
Xamarin.Forms.Platform.Android/FastRenderers/FrameRenderer.cs
Xamarin.Forms.Platform.Android/ViewExtensions.cs
Xamarin.Forms.Platform.Android/VisualElementTracker.cs

diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7823.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue7823.cs
new file mode 100644 (file)
index 0000000..d28fd38
--- /dev/null
@@ -0,0 +1,106 @@
+using Xamarin.Forms.CustomAttributes;
+using Xamarin.Forms.Internals;
+
+#if UITEST
+using NUnit.Framework;
+using Xamarin.UITest;
+using Xamarin.Forms.Core.UITests;
+using Xamarin.UITest.Queries;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+       [Preserve(AllMembers = true)]
+       [Issue(IssueTracker.Github, 7823, "[Bug] Frame corner radius.", PlatformAffected.Android)]
+#if UITEST
+       [Category(UITestCategories.Frame)]
+#endif
+       public class Issue7823 : TestContentPage
+       {
+               const string GetClipToOutline = "getClipToOutline";
+               const string GetClipChildren = "getClipChildren";
+               const string GetClipBounds = "getClipBounds";
+               const string SetClipBounds = "SetClipBounds";
+               const string SecondaryFrame = "Secondary Frame";
+               const string RootFrame = "Root Frame";
+               const string BoxView = "Box View";
+
+               protected override void Init()
+               {
+                       var frameClippedToBouds = new Frame
+                       {
+                               AutomationId = SecondaryFrame,
+                               CornerRadius = 10,
+                               BackgroundColor = Color.Blue,
+                               Padding = 0,
+                               Content = new BoxView
+                               {
+                                       AutomationId = BoxView,
+                                       BackgroundColor = Color.Green,
+                                       WidthRequest = 100,
+                                       HeightRequest = 100
+                               }
+                       };
+
+                       Content = new StackLayout()
+                       {
+                               Children =
+                               {
+                                       new ApiLabel(),
+                                       new Frame
+                                       {
+                                               AutomationId = RootFrame,
+                                               CornerRadius = 5,
+                                               BackgroundColor = Color.Red,
+                                               Padding = 10,
+                                               Content = frameClippedToBouds
+                                       },
+                                       new Button
+                                       {
+                                               AutomationId = SetClipBounds,
+                                               Text = "Manually set Frame.IsClippedToBounds = false",
+                                               Command = new Command(()=>
+                                               {
+                                                       frameClippedToBouds.IsClippedToBounds = false;
+                                                       frameClippedToBouds.CornerRadius = 11;
+                                               })
+                                       }
+                               }
+                       };
+               }
+
+#if UITEST && __ANDROID__
+               [Test]
+               [UiTest(typeof(Frame))]
+               public void Issue7823TestIsClippedIssue()
+               {
+                       RunningApp.WaitForElement(RootFrame);
+                       AssertIsClipped(true);
+                       RunningApp.Tap(SetClipBounds);
+                       AssertIsClipped(false);
+               }
+
+               void AssertIsClipped(bool expected)
+               {
+                       if (RunningApp.IsApiHigherThan(21))
+                       {
+                               var cliptoOutlineValue = RunningApp.InvokeFromElement<bool>(SecondaryFrame, GetClipToOutline)[0];
+                               Assert.AreEqual(expected, cliptoOutlineValue);
+                       }
+                       else if (RunningApp.IsApiHigherThan(19))
+                       {
+                               var clipBounds = RunningApp.InvokeFromElement<object>(SecondaryFrame, GetClipBounds)[0];
+                               if (expected)
+                                       Assert.IsNotNull(clipBounds);
+                               else
+                                       Assert.IsNull(clipBounds);
+                       }
+                       else
+                       {
+                               var clipChildrenValue = RunningApp.InvokeFromElement<bool>(SecondaryFrame, GetClipChildren)[0];
+                               Assert.AreEqual(expected, clipChildrenValue);
+                       }
+               }
+#endif
+       }
+}
index 9b0ab12..a4cc51d 100644 (file)
@@ -92,6 +92,7 @@
     <Compile Include="$(MSBuildThisFileDirectory)Issue7817.xaml.cs">
       <SubType>Code</SubType>
     </Compile>
+    <Compile Include="$(MSBuildThisFileDirectory)Issue7823.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Issue7943.xaml.cs">
       <SubType>Code</SubType>
     </Compile>
index 16758fb..79c8a42 100644 (file)
@@ -73,6 +73,9 @@ namespace Xamarin.UITest
                        return true;
                }
 
+               public static TResult[] InvokeFromElement<TResult>(this IApp app, string element, string methodName) =>
+                       app.Query(c => c.Marked(element).Invoke(methodName).Value<TResult>());
+
 #if __IOS__
                public static void SendAppToBackground(this IApp app, TimeSpan timeSpan)
                {
index 06ec121..7ee88f6 100644 (file)
@@ -130,7 +130,7 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                                        _visualElementPackager.Dispose();
                                        _visualElementPackager = null;
                                }
-                               
+
                                if (_backgroundDrawable != null)
                                {
                                        _backgroundDrawable.Dispose();
@@ -236,13 +236,16 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                                UpdateClippedToBounds();
                }
 
-               void UpdateClippedToBounds() => this.SetClipToOutline(Element.IsClippedToBounds);
+               void UpdateClippedToBounds()
+               {
+                       this.SetClipToOutline(Element.IsClippedToBounds, Element);
+               }
 
                void UpdateBackgroundColor()
                {
                        if (_disposed)
                                return;
-                               
+
                        Color bgColor = Element.BackgroundColor;
                        _backgroundDrawable.SetColor(bgColor.IsDefault ? AColor.White : bgColor.ToAndroid());
                }
@@ -264,7 +267,7 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                {
                        if (_disposed)
                                return;
-                               
+
                        float elevation = _defaultElevation;
 
                        if (elevation == -1f)
@@ -280,7 +283,7 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                {
                        if (_disposed)
                                return;
-                               
+
                        if (_defaultCornerRadius == -1f)
                        {
                                _defaultCornerRadius = Radius;
@@ -294,6 +297,8 @@ namespace Xamarin.Forms.Platform.Android.FastRenderers
                                cornerRadius = Context.ToPixels(cornerRadius);
 
                        _backgroundDrawable.SetCornerRadius(cornerRadius);
+
+                       UpdateClippedToBounds();
                }
        }
 }
index 0c57987..148de57 100644 (file)
@@ -6,6 +6,7 @@ using Android.Util;
 using Android.Views;
 using AView = Android.Views.View;
 using AColor = Android.Graphics.Color;
+using Android.Graphics;
 
 namespace Xamarin.Forms.Platform.Android
 {
@@ -39,7 +40,7 @@ namespace Xamarin.Forms.Platform.Android
                        {
                                view.Background = drawable;
                        }
-                       
+
                }
 
                public static void SetWindowBackground(this AView view)
@@ -94,6 +95,53 @@ namespace Xamarin.Forms.Platform.Android
                        view.ClipToOutline = value;
                }
 
+               public static void SetClipToOutline(this AView view, bool value, VisualElement element)
+               {
+                       if (view.IsDisposed())
+                               return;
+
+                       var shouldClip = value;
+                       if (element is Frame frame)
+                       {
+                               shouldClip = frame.IsSet(Layout.IsClippedToBoundsProperty)
+                                       ? frame.IsClippedToBounds : frame.CornerRadius > 0f;
+                       }
+
+                       if (view is FastRenderers.FrameRenderer && Forms.IsLollipopOrNewer)
+                       {
+                               view.SetClipToOutline(shouldClip);
+                               return;
+                       }
+
+                       // setClipBounds is only available in API 18 +
+                       if ((int)Build.VERSION.SdkInt >= 18)
+                       {
+                               if (!(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 (!(view.Parent is ViewGroup parent))
+                                       return;
+
+                               if ((int)Build.VERSION.SdkInt >= 18 && parent.ClipChildren == shouldClip)
+                                       return;
+
+                               parent.SetClipChildren(shouldClip);
+                               parent.Invalidate();
+                       }
+               }
+
+
                public static bool SetElevation(this AView view, float value)
                {
                        if (view.IsDisposed() || !Forms.IsLollipopOrNewer)
@@ -102,7 +150,7 @@ namespace Xamarin.Forms.Platform.Android
                        view.Elevation = value;
                        return true;
                }
-               
+
                internal static void MaybeRequestLayout(this AView view)
                {
                        var isInLayout = false;
index 733bd03..bbae93e 100644 (file)
@@ -277,34 +277,7 @@ namespace Xamarin.Forms.Platform.Android
                                return;
                        }
 
-                       bool shouldClip = layout.IsClippedToBounds;
-
-                       // setClipBounds is only available in API 18 +
-                       if ((int)Forms.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)Forms.SdkInt >= 18 && parent.ClipChildren == shouldClip)
-                                       return;
-
-                               parent.SetClipChildren(shouldClip);
-                               parent.Invalidate();
-                       }
+                       _renderer.View.SetClipToOutline(layout.IsClippedToBounds, _renderer.Element);
                }
 
                void UpdateIsVisible()