A little while ago I had to include a type of gradient effect I hadn’t heard of before, a gradient with an inset. Essentially this meant having the gradient stay a solid colour for say 20% of it and then fade to almost nothing at the end.
The designers point of this was to make some text clearer to the user while still showing a nice background image and maintaining the apps main brand colours. They also wanted the image to be updatable remotely without designer input, so the effect had to be built into the app itself.
Thankfully already having SkiaSharp for Xamarin.Forms included in the project this turned out to be a reasonably simple thing to include and I wrapped it up into a control that can simply be added above an image to achieve the desired effect.
Adding the gradient effect once it was bundled up in a control was as simple as:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
<?xml version="1.0" encoding="utf-8"?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:d="http://xamarin.com/schemas/2014/forms/design" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:controls="clr-namespace:GradientOverlayWithInset.Controls" mc:Ignorable="d" x:Class="GradientOverlayWithInset.MainPage"> <Grid CompressedLayout.IsHeadless="true" RowSpacing="0"> <Grid.RowDefinitions> <RowDefinition Height="240" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Image Grid.Row="0" Source="bgimage.jpg" Aspect="AspectFill" /> <controls:GradientOverlayView Grid.Row="0" BindingContext="{x:Reference ShowGradientSwitch}" IsVisible="{Binding IsToggled}" HasGradientStartInset="true" GradientStartInsetPercent="0.20" /> <StackLayout Grid.Row="0" HorizontalOptions="Start" VerticalOptions="Start" Padding="16" TranslationY="50" Spacing="15"> <Label Text="Hello" TextColor="#6746da" /> <Label Text="Matt Crombie" TextColor="CadetBlue" /> <Label Text="Dark Ice Interactive" TextColor="DarkGreen" /> </StackLayout> <Grid Grid.Row="1" CompressedLayout.IsHeadless="true" Padding="16"> <Label VerticalOptions="Start" Text="Show gradient" /> <Switch x:Name="ShowGradientSwitch" IsToggled="True" VerticalOptions="Start" HorizontalOptions="End" /> </Grid> </Grid> </ContentPage> |
The code for the control itself is also pretty simple, with half of it just adding in stock standard property bindings:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
using System; using SkiaSharp; using SkiaSharp.Views.Forms; using Xamarin.Forms; using Color = Xamarin.Forms.Color; namespace GradientOverlayWithInset.Controls { public class GradientOverlayView : ContentView { private static readonly Color _defaultStartColor = Color.White.MultiplyAlpha(0.95); // White @ 95% opacity private static readonly Color _defaultEndColor = Color.White.MultiplyAlpha(0.0); // White @ 0% opacity public static readonly BindableProperty StartColorProperty = BindableProperty.Create(nameof(StartColor), typeof(Color), typeof(GradientOverlayView), _defaultStartColor, Xamarin.Forms.BindingMode.OneWay); public Color StartColor { get { return (Color)GetValue(StartColorProperty); } set { SetValue(StartColorProperty, value); OnPropertyChanged(nameof(StartColor)); } } public static readonly BindableProperty EndColorProperty = BindableProperty.Create(nameof(EndColor), typeof(Color), typeof(GradientOverlayView), _defaultEndColor, Xamarin.Forms.BindingMode.OneWay); public Color EndColor { get { return (Color)GetValue(EndColorProperty); } set { SetValue(EndColorProperty, value); OnPropertyChanged(nameof(EndColor)); } } /// <summary> /// If true, the first part of the gradient percentage specified in GradientStartHeavyPercent will be constant instead of gradient using the StartColor property. /// This is useful when text or other things overlayed at the top need to be displayed more clearly. /// </summary> public static readonly BindableProperty HasGradientStartInsetProperty = BindableProperty.Create(nameof(HasGradientStartInset), typeof(bool), typeof(GradientOverlayView), false, Xamarin.Forms.BindingMode.OneWay); public bool HasGradientStartInset { get { return (bool)GetValue(HasGradientStartInsetProperty); } set { SetValue(HasGradientStartInsetProperty, value); OnPropertyChanged(nameof(HasGradientStartInset)); } } /// <summary> /// Specifies the heavy gradient percentage 0.0 - 1.0, essentially at what point in the diagonal line of the gradient that the start color stops being constant and turns to the gradient. Default is 0.2f (20%). /// </summary> public static readonly BindableProperty GradientStartInsetPercentProperty = BindableProperty.Create(nameof(GradientStartInsetPercent), typeof(float), typeof(GradientOverlayView), 0.20f, Xamarin.Forms.BindingMode.OneWay); public float GradientStartInsetPercent { get { return (float)GetValue(GradientStartInsetPercentProperty); } set { SetValue(GradientStartInsetPercentProperty, value); OnPropertyChanged(nameof(GradientStartInsetPercent)); } } public GradientOverlayView() { try { var canvasView = new SKCanvasView(); canvasView.PaintSurface += OnCanvasViewPaintSurface; canvasView.BackgroundColor = Color.Transparent; Content = canvasView; } catch (Exception ex) { // Don't crash for a pretty effect System.Diagnostics.Debug.WriteLine(ex); } } private void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) { try { SKImageInfo info = args.Info; SKSurface surface = args.Surface; SKCanvas canvas = surface.Canvas; canvas.Clear(); var startPoint = new SKPoint(0, 0); var endPoint = new SKPoint(info.Width, info.Height); SKColor[] colors; SKShader shader; if (HasGradientStartInset) { colors = new SKColor[] { StartColor.ToSKColor(), StartColor.ToSKColor(), EndColor.ToSKColor() }; shader = SKShader.CreateLinearGradient(startPoint, endPoint, colors, new float[] { 0, GradientStartInsetPercent, 1 }, SKShaderTileMode.Clamp); } else { colors = new SKColor[] { StartColor.ToSKColor(), EndColor.ToSKColor() }; shader = SKShader.CreateLinearGradient(startPoint, endPoint, colors, null, SKShaderTileMode.Clamp); } var mainPaint = new SKPaint { Style = SKPaintStyle.Fill, Shader = shader }; canvas.DrawRect(new SKRect(startPoint.X, startPoint.Y, endPoint.X, endPoint.Y), mainPaint); } catch (Exception ex) { // Don't crash for a pretty effect System.Diagnostics.Debug.WriteLine(ex); } } } } |
And that’s it! So if you find yourself with a designer who wants to take advantage of this sort of approach, or are inspired enough by this very crude example, this is a quick and easy way to keep the colours and imagery you want in your app.
As usual, if you want the entire working example, please have a look here on GitHub.
This is very timely. I needed to do this on my current project. Works like a charm. Thanks!
Great! Glad this was useful for you :).