xamarin / Xamarin.Forms

Xamarin.Forms is no longer supported. Migrate your apps to .NET MAUI.
https://aka.ms/xamarin-upgrade
Other
5.63k stars 1.88k forks source link

Frame HasShadow Property has no effect #2423

Open jonkas2211 opened 6 years ago

jonkas2211 commented 6 years ago

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

rmarinho commented 6 years ago

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 ?!

rmarinho commented 6 years ago
screen shot 2018-04-13 at 17 45 18
rmarinho commented 6 years ago

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

jonkas2211 commented 6 years ago

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?

jonkas2211 commented 6 years ago

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

samhouts commented 6 years ago

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

jonkas2211 commented 6 years ago

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

StephaneDelcroix commented 6 years ago

Please check #2666

andreschardong commented 6 years ago

Any workaround for now?

jonkas2211 commented 6 years ago

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

andreschardong commented 6 years ago

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

image

jonkas2211 commented 6 years ago

@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

rraallvv commented 6 years ago

Same here Android 5.0.1

Matiszak commented 6 years ago

Same here, Android 6.0.1 Xamarin.Forms 3.0.0.561731

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

xalikoutis commented 6 years ago

Also here Android 6.0 XF 3.1.0.561732-pre4

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

rraallvv commented 6 years ago

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.

VictorK1902 commented 6 years ago

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

XamHans commented 6 years ago

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

rraallvv commented 6 years ago

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

rmarinho commented 6 years ago

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

rraallvv commented 6 years ago

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

rmarinho commented 6 years ago

@rraallvv was it working before? what version ?

rmarinho commented 6 years ago

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.

rraallvv commented 6 years ago

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

rraallvv commented 6 years ago

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

jonkas2211 commented 6 years ago

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

gokhanikisivri commented 6 years ago

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

JonasSL commented 5 years ago

Any news on this?

ssmsexe commented 5 years ago

Is there any update on this?

taublast commented 5 years ago

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.

taublast commented 5 years ago

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.

XamHans commented 5 years ago

@taublast There is a workaround https://alexdunn.org/2018/06/06/xamarin-tip-dynamic-elevation-frames/

BrunoMoureau commented 5 years ago

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;
        }
    }
}
taublast commented 5 years ago

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

taublast commented 5 years ago

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);
        }
BrunoMoureau commented 5 years ago

Thanks for sharing @taublast

taublast commented 5 years ago

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.

longlostbro commented 5 years ago

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();
      }
    }
  }
}
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);
    }
  }
}
caseyryan commented 5 years ago

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

MeikandaNayanar commented 5 years ago

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

hamid-shaikh commented 5 years ago

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>

jonx commented 4 years ago

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

jonx commented 4 years ago

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.

BrunoMoureau commented 4 years ago

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

jonx commented 4 years ago

@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">
samhouts commented 4 years ago

see also #3532

samhouts commented 4 years ago

see also #4374