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:
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.
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
LikeLiked by 1 person
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.
LikeLike
Execute the later update on layout change or will layout children
LikeLike
Wouldn’t it update by itself if you call the UpdateShadow() method in the propertychanged of the height property ?
LikeLike
Height isn’t bindable and doesn’t invoke property change I believe
LikeLike
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.
LikeLike
Copy paste error. Should be typeof(MaterialFrame)
LikeLike
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.
LikeLike
Can you share the Draw method?
LikeLiked by 1 person
I tried the above custom renderer for xamarin forms 3.4 but not getting the Elevation.
LikeLike
on which platform?
LikeLiked by 1 person
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.
LikeLike
This doesn’t seem to be working for android & XF 4?
LikeLike
looking for UWP frame renderer for same purpose. Any sample code?
LikeLike