Xamarin.forms: Frame HasShadow Property has no effect

Created on 11 Apr 2018  路  47Comments  路  Source: xamarin/Xamarin.Forms

Bug report best practices: https://github.com/xamarin/Xamarin.Forms/wiki/Submitting-Issues

Description

I installed the stable Version of Xamarin.Forms 2.5.1.444934 where the Frame Shadow is working.
In the Preview of Xamarin.Forms 3.0.0.354232-pre3 the HasShadow Property of the frame has no function. Only Tested in Android.

Steps to Reproduce

  1. Create a frame
  2. Set the HasShadow Property to true

Expected Behavior

A shadow around the control should appear.

Actual Behavior

No shadow is apearing.

Basic Information

  • Version with issue: 3.0.0.354232-pre3
  • Last known good version: 2.5.1.444934
  • IDE: Visual Studio 2017
  • Platform Target Frameworks:

    • Android: 8.1

3.0.0 3 regression high impact Android bug

Most helpful comment

Solved the problem with the following:
api 19 (and i guess below) clipping ok, but forgetting about shadows with elevation, it's not available at this api level
api 20 not tested
api 21 and above shadows ok, clipping children ok:

shadows: used @BrunoMoureau renderer, added to avoid crash:

 private void UpdateElevation()
        {
            if (Build.VERSION.SdkInt >= (BuildVersionCodes)21) 
                this.Elevation = Element.HasShadow ? 5 : 0;
        }

clipping frame children with rounded corners:

  1. very important the child must have background color set or we get black corners
  2. clipping children:
        protected override bool DrawChild(Canvas canvas, Android.Views.View child, long drawingTime)
        {

            try
            {

                var radius = Element.CornerRadius;
                var borderThickness = 1f;
                float strokeWidth = 0f;
                if (borderThickness > 0)
                {
                    var logicalDensity = Xamarin.Forms.Forms.Context.Resources.DisplayMetrics.Density;
                    strokeWidth = (float)Math.Ceiling(borderThickness * logicalDensity + .5f);
                }

                radius -= strokeWidth / 2f;

                var path = new Path();

                var rect = new RectF(0, 0, Width, Height);
                float rx = Forms.Context.ToPixels(Element.CornerRadius);
                float ry = Forms.Context.ToPixels(Element.CornerRadius);
                path.AddRoundRect(rect, rx, ry, Path.Direction.Ccw);

                canvas.Save();
                canvas.ClipPath(path);

                //can add code for filling canvas - frame background here, gradient whatever

                //clip children
                var result = base.DrawChild(canvas, child, drawingTime);

                rect.Dispose();
                path.Dispose();
                canvas.Restore();

                 //can add code to stroke frame border here, look as image circle plugin renderer for more info

                path.Dispose();
                return result;
            }
            catch (Exception ex)
            {               
            }
            return base.DrawChild(canvas, child, drawingTime);
        }

All 47 comments

I m able to reproduce this on emulator API19, but it seems to works fine on my Google Pixel 2 Android 8.1 , can you confirm on a device @jonkas2211 ?!

screen shot 2018-04-13 at 17 45 18

Seems even emulator API25 It renderes the Shadow, but api19 it doesn't. Can you please upload some screenshots and provide a test project so we can reproduce @jonkas2211 .

Thanks

Hello @rmarinho ,
i didint have time to test it on other devices.
The bug occurred on a Zebra TC70 Scanner Device with KitKat API 19 .
But in the project I had select 8.1 as Target Framework, if I may find some time I can provide a example project.

Maybe it's a bug for this API Level or lower?

Here I have an example project.
As expected: If you run it on a newer Android OS it just works fine, but on KitKat the bug is noticeable.

Here the example project:
Bug-2423.zip

Confirmed that this is a regression on 3.0.0, only on API 19, as far as I can tell.

Now i got the same bug on earlier versions of Xamarin.Forms and on different OS versions. Unfortunately I cant share the project.

Please check #2666

Any workaround for now?

@andreschardong Try to target an other android api version. I am targetting API 27 and it works fine

Are you sure @jonkas2211? I'm targeting API 27 and shadow doesn't show. Works fine with iIOS.

image

@andreschardong Well then:
a. You could use a device with an other API Version (i dont know if you are forced to use explicit this version)
or
b. You could try by writting your own renderer

Same here Android 5.0.1

Same here,
Android 6.0.1
Xamarin.Forms 3.0.0.561731

Adding
BorderColor="White" BackgroundColor="White"
makes shadow appear

Also here
Android 6.0
XF 3.1.0.561732-pre4

Adding
BorderColor="White" BackgroundColor="White" makes shadow appear

I tried upgrading to latest XF (3.1.0.583944) and set BorderColor and BackgroundColor to White in XAML and also from a custom renderer, it didn't make the shadow appear though.

I'm also facing the same problem:
Android 7.0
Target Version: 8.1
Minimum Version: 5.1
XF 3.1.0.583944

Adding BorderColor="White" BackgroundColor="White" did not help.
HasShadow works fine on iOS

Is there no CustomRenderer for Android to achieve a shadow effect?

@XamHans this post describes how to accomplish that by using the elevation property on Android, and a custom ShadowPath on iOS

Ok so this only fails on API19 right? on API25 and above 3.0.0 and 3.1.0 show just fine the shadow.

@rmarinho lvl 21 also has it (Lollipop 5.0.1), my device is an acer iconia b1-770

@rraallvv was it working before? what version ?

the original report of this issue is with Android: 8.1 and that was the elevation issue that I think is fixed.
Seems this might be a issue with older versions? But I wonder if it worked before.

@rmarinho , yes, it's working for XF 3.0.0.446417
I'll try to figure out what is the latest version that doesn't have the issue and report back.

@rmarinho I was able to update XF all the way up to version 3.0.0.482510 without the issue popping up , version 3.0.0.530893 does have the problem with the missing shadows though.

@rraallvv Like I wrote the latest working XF Version was 2.5.1.444934. I targetet Android 8.1, but I used a device with API-Level 19. I updated the device to API-Level 21 and it worked fine. Now I am using XF Version 3.1 with API-Level device 21 and it working fine as expected, even if the shadow now looks different. I hope that can help.

Please tell me when this will be working? I can't update xamarin forms on my project because of this Bug. Already 3 month this bug could not be resolved

Any news on this?

Is there any update on this?

Anyone found a workaround?..
This is really annoying how come there are so many upgrades for xamarin.forms and all they leave behind the fact we cannot produce apps with shadowed frames for android api 19, 21 (and maybe more)? This doesn't look like a low-priority issue.
Along with this degradation effect, on same android api's the frame with 0 padding now fails to clip some images, leaving black 90deg corners outside frame. Some = with transparency and pure white.

At the same time, notice the frame border width has changed too for theses apis and latest xamarin: it looked okay before, now it's just pure thick only on lower api's.

I also faced this issue recently on Android (Android 6.0 - API 23). I found a workaround for that.
I set the background and the elevation of the frame control in my custom frame renderer to make the shadow appear on my device.

I don't know which Drawable to use for a Frame so here is a working example (at least in my case) with a GradientDrawable. I hope it will help someone :

In view

<Frame HasShadow="True" BackgroundColor="White">
...
</Frame>

My frame renderer

using Android.Content;
using Android.Graphics.Drawables;
using OscilloProtect.Droid.Renderers;
using System.ComponentModel;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(Frame), typeof(CustomFrameRenderer))]
namespace Sandbox.Droid.Renderers
{
    public class CustomFrameRenderer : FrameRenderer
    {
        public CustomFrameRenderer(Context context) : base(context)
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
        {
            base.OnElementChanged(e);

            if (Element == null)
            {
                return;
            }

            UpdateBackground();
            UpdateElevation();
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);

            if (string.Equals(e.PropertyName, "BackgroundColor"))
            {
                UpdateBackground();
            }
            else if (string.Equals(e.PropertyName, "HasShadow"))
            {
                UpdateElevation();
            }
        }

        private void UpdateBackground()
        {
            int[] colors = {Element.BackgroundColor.ToAndroid(), Element.BackgroundColor.ToAndroid()};
            var gradientDrawable = new GradientDrawable(GradientDrawable.Orientation.LeftRight, colors);
            gradientDrawable.SetCornerRadius(Element.CornerRadius * 2); // CornerRadius = HeightRequest in my case

            this.SetBackground(gradientDrawable);
        }

        private void UpdateElevation()
        {
            this.Elevation = Element.HasShadow ? 5 : 0;
        }
    }
}

Thanks everyone for awesome renderers, will give them a try.
Now to show why this really needs to be addressed asap as it looks like a broken xamarin:

buggedframe

Solved the problem with the following:
api 19 (and i guess below) clipping ok, but forgetting about shadows with elevation, it's not available at this api level
api 20 not tested
api 21 and above shadows ok, clipping children ok:

shadows: used @BrunoMoureau renderer, added to avoid crash:

 private void UpdateElevation()
        {
            if (Build.VERSION.SdkInt >= (BuildVersionCodes)21) 
                this.Elevation = Element.HasShadow ? 5 : 0;
        }

clipping frame children with rounded corners:

  1. very important the child must have background color set or we get black corners
  2. clipping children:
        protected override bool DrawChild(Canvas canvas, Android.Views.View child, long drawingTime)
        {

            try
            {

                var radius = Element.CornerRadius;
                var borderThickness = 1f;
                float strokeWidth = 0f;
                if (borderThickness > 0)
                {
                    var logicalDensity = Xamarin.Forms.Forms.Context.Resources.DisplayMetrics.Density;
                    strokeWidth = (float)Math.Ceiling(borderThickness * logicalDensity + .5f);
                }

                radius -= strokeWidth / 2f;

                var path = new Path();

                var rect = new RectF(0, 0, Width, Height);
                float rx = Forms.Context.ToPixels(Element.CornerRadius);
                float ry = Forms.Context.ToPixels(Element.CornerRadius);
                path.AddRoundRect(rect, rx, ry, Path.Direction.Ccw);

                canvas.Save();
                canvas.ClipPath(path);

                //can add code for filling canvas - frame background here, gradient whatever

                //clip children
                var result = base.DrawChild(canvas, child, drawingTime);

                rect.Dispose();
                path.Dispose();
                canvas.Restore();

                 //can add code to stroke frame border here, look as image circle plugin renderer for more info

                path.Dispose();
                return result;
            }
            catch (Exception ex)
            {               
            }
            return base.DrawChild(canvas, child, drawingTime);
        }

Thanks for sharing @taublast

As clipping children on Android with ClipPath never produces an anti-aliased effect had to use this renderer only for lower api and use xamarin unmodified frame for other versions. Just switching control at run-time upon api version.

Shadows still don't work on API 27 with Xamarin Forms 3.5.0.129452, I've attempted the above renderers including the one referenced here -> https://alexdunn.org/2018/06/06/xamarin-tip-dynamic-elevation-frames/ <- with no luck.

Here is the code I'm using

<ListView ItemsSource="{StaticResource Announcements}" HasUnevenRows="True" IsPullToRefreshEnabled="True" SeparatorVisibility="None">
  <ListView.ItemTemplate>
    <DataTemplate>
      <ViewCell>
        <ContentView>
          <ContentView.Content>
            <controls:MaterialFrame HasShadow="True" Elevation="10" Margin="0,5,0,5">
              <StackLayout BackgroundColor="White">
                <Grid>
                  <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                  </Grid.ColumnDefinitions>
                  <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                  </Grid.RowDefinitions>
                  <Label Grid.Column="0" Grid.Row="0" Text="{Binding Time, StringFormat='{}{0:MM/dd/yyyy}'}"/>
                  <Label Grid.Column="1" Grid.Row="0" Text="{Binding Title}" HorizontalTextAlignment="Center"/>
                </Grid>
                <Label Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="1" Text="{Binding Message}" HorizontalOptions="CenterAndExpand" HorizontalTextAlignment="Start"/>
              </StackLayout>
            </controls:MaterialFrame>
          </ContentView.Content>
        </ContentView>
      </ViewCell>
    </DataTemplate>
  </ListView.ItemTemplate>
</ListView>
[assembly: ExportRenderer(typeof(FrameRenderer), typeof(MaterialFrameRenderer))]
namespace GGApp.Droid.Renderers
{
  public class MaterialFrameRenderer : Xamarin.Forms.Platform.Android.AppCompat.FrameRenderer
  {
    public MaterialFrameRenderer(Context context) : base(context)
    {
    }

    protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
    {
      base.OnElementChanged(e);
      if (e.NewElement == null)
        return;

      UpdateElevation();
    }


    private void UpdateElevation()
    {
      var materialFrame = (MaterialFrame)Element;

      // we need to reset the StateListAnimator to override the setting of Elevation on touch down and release.
      Control.StateListAnimator = new Android.Animation.StateListAnimator();

      // set the elevation manually
      ViewCompat.SetElevation(this, materialFrame.Elevation);
      ViewCompat.SetElevation(Control, materialFrame.Elevation);
    }

    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
      base.OnElementPropertyChanged(sender, e);
      if (e.PropertyName == "Elevation")
      {
        UpdateElevation();
      }
    }
  }
}



md5-0a78a142751a1081c46c48e10b6cba93



public class MaterialFrame : Frame
{
  public static BindableProperty ElevationProperty = BindableProperty.Create(nameof(Elevation), typeof(float), typeof(MaterialFrame), 4.0f);

  public float Elevation
  {
    get
    {
      return (float)GetValue(ElevationProperty);
    }
    set
    {
      SetValue(ElevationProperty, value);
    }
  }
}

That's because you export it to the wrong target, I believe this
[assembly: ExportRenderer(typeof(FrameRenderer), typeof(MaterialFrameRenderer))]
Should be replaced with this
[assembly: ExportRenderer(typeof(MaterialFrame ), typeof(MaterialFrameRenderer))]
in your case

I tested the aforementioned renderers and they work just fine

JNI DETECTED ERROR IN APPLICATION: can't call float android.support.v7.widget.CardView.getCardElevation() on instance

It is quiet strange to see Frame shadow not working on some of the Android Device's, while works as expected on most of the Android Device's. The following small code change made it working as expected :)

Add property

Visual="Material"

<Frame Visual="Material"> //Ur awesome ui code here </Frame>

@hamid-shaikh that doesn't have any effect for me.

Any update on this? I have this on android 9 API level 28. Thanks.

Funny enough, when I add my frame in another frame and both have a shadow, it works more or less but then there is twice the shadow on iOS; not ideal.

BTW while you're there. You have to add CornerRadius="0" on android or you''ll get round corners. On iOS, by default, the corners are square.

@jonx If you are interested in Xamarin.Forms Material Visual, please check installation here

You need to install the NuGet package called Xamarin.Forms.Visual.Material to your project.

@BrunoMoureau thanks a lot, I forgot about that. It's working but I cannot include Material in this project.

My current workaround is now simply to add a margin. And because my shadow is right/botton, I use this as a margin: Margin="0,0,4,4".

<Frame
            Margin="0,0,4,4"
            Padding="0"
            CornerRadius="0"
            HasShadow="True"
            VerticalOptions="FillAndExpand">

see also #3532

see also #4374

Was this page helpful?
0 / 5 - 0 ratings