Xamarin.Tip – Dynamic Elevation Frames

How about some more Material Design based controls for Xamarin.Forms?

A while back I wrote about creating more “Material” Frames in your Xamarin.Forms apps using a custom renderer for iOS: Xamarin.Tip – Making Your iOS Frame Shadows More Material

And also wrote about the MaterialButton control I created that added dynamic Elevation properties for both iOS and Android:
Xamarin.Tip – Adding Dynamic Elevation to Your Xamarin.Forms Buttons

And I’ve been getting requests to talk about how to do it with the Frame control in Xamarin.Forms. Spoiler Alert: It’s basically the exact same thing as the MaterialButton…..

Let’s start by creating a custom Frame class in our Xamarin.Forms project:

MaterialFrame.cs

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

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

We add our bindable Elevation property with a default value of 4.

Now we just need simple custom renderers for our iOS and Android implementations.

Starting with iOS:

MaterialFrameRenderer_iOS.cs

public class MaterialFrameRenderer : FrameRenderer
{
    public static void Initialize()
    {
        // empty, but used for beating the linker
    }
    protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
    {
        base.OnElementChanged(e);

        if (e.NewElement == null)
            return;
        UpdateShadow();
    }

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

    private void UpdateShadow()
    {

        var materialFrame = (MaterialFrame)Element;

        // Update shadow to match better material design standards of elevation
        Layer.ShadowRadius = materialFrame.Elevation;
        Layer.ShadowColor = UIColor.Gray.CGColor;
        Layer.ShadowOffset = new CGSize(2, 2);
        Layer.ShadowOpacity = 0.80f;
        Layer.ShadowPath = UIBezierPath.FromRect(Layer.Bounds).CGPath;
        Layer.MasksToBounds = false;

    }
}

And now the simple Android renderer that can use the built in Elevation properties

MaterialFrameRenderer_Android.cs

public class MaterialFrameRenderer : Xamarin.Forms.Platform.Android.AppCompat.FrameRenderer
{
    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();
        }
    }
}

Now with both our renderers, we can reference our MaterialFrame component in our content!

<ContentPage ...>
    <StackLayout>
        <components:MaterialFrame Elevation="6" />
        <components:MaterialFrame Elevation="{Binding SomeNumber}"/>
    </StackLayout>
</ContentPage>

And that’s it! Now you can control the elevation of your frames for both iOS and Android in Xamarin.Forms:
iOSMaterialFrameAndLabel


If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.

16 thoughts on “Xamarin.Tip – Dynamic Elevation Frames”

  1. Well done Alex Dunn, I actually red your articles to check if I could figure out how to get rid off the button elevation in one of my Android project. Anyway I’m considering showing my clients the “material” ported to IOS 🙂 Thank you

    Liked by 1 person

  2. Any ideas on how to update the frame’s shadow if the frame resizes. For example frame’s original shadow is still visible after hiding a boxview inside the frame.

    Like

      1. Wouldn’t it update by itself if you call the UpdateShadow() method in the propertychanged of the height property ?

        Like

  3. Why is it typeof(MaterialButton) at your class MaterialFrame?

    public static BindableProperty ElevationProperty = BindableProperty.Create(nameof(Elevation), typeof(float), typeof(MaterialButton), 4.0f);

    Thank you, for this great tutorial.

    Like

  4. Exact same page on Android and iOS, but for iOS, I’m not sure why the shadow is not showing. It turns out the Layout.Bounds returns 0 for x/y/w/h for some reason. I had to move the logic into public override void Draw in order for this to work.

    Like

      1. I had the same problem, but your code is OK, to view Elevation it’s needed to add a Margin to the control you applied the effect, else the elevation will be hidden.

        Like

Leave a comment