A gradient is the gradual blending from one color to another.
_But, hey, why gradients?_
Mobile design trends have changed rapidly in recent years, with some things disappearing for a while and then making a gradual comeback. That’s the case with gradients. Gradients are making a comeback.
A gradient brush paints an area with multiple colors that blend into each other along an axis.
public abstract class Brush : BindableObject
{
}
And the GradientBrush definition:
[ContentProperty(nameof(GradientStops))]
public class GradientBrush : Brush
{
public static readonly BindableProperty GradientStopsProperty = BindableProperty.Create(
nameof(GradientStops), typeof(IList<GradientStop>), typeof(GradientBrush), null);
public IList<GradientStop> GradientStops
{
get => (IList<GradientStop>)GetValue(GradientStopsProperty);
set => SetValue(GradientStopsProperty, value);
}
}
The GradientStop is the basic building block of a gradient brush. A gradient stop specifies a Color at an Offset along the gradient axis.
public class GradientStop : BindableObject
{
public static readonly BindableProperty ColorProperty = BindableProperty.Create(
nameof(Color), typeof(Color), typeof(GradientStop), Color.White);
public Color Color
{
get => (Color)GetValue(ColorProperty);
set => SetValue(ColorProperty, value);
}
public static readonly BindableProperty OffsetProperty = BindableProperty.Create(
nameof(Offset), typeof(float), typeof(GradientStop), 0f);
public float Offset
{
get => (float)GetValue(OffsetProperty);
set => SetValue(OffsetProperty, value);
}
}
We will add a new property to VisualElement
to define the background using Brushes.
public static readonly BindableProperty BackgroundProperty = BindableProperty.Create("Background", typeof(GradientBrush), typeof(VisualElement), default(GradientBrush));
This would allow gradients to be used as background in any control, although the BackgroundColor property would still exist (deprecated).
A LinearGradientBrush paints an area with a gradient that's defined along a line. This line is called the gradient axis.
You specify the gradient's colors and their locations along the gradient axis using GradientStop objects. By default, the gradient axis runs from the upper left corner to the lower right corner of the area that the brush paints, resulting in a diagonal shading.
public class LinearGradientBrush : GradientBrush
{
public static readonly BindableProperty StartPointProperty = BindableProperty.Create(
nameof(StartPoint), typeof(Point), typeof(LinearGradientBrush), default(Point));
public Point StartPoint
{
get => (Point)GetValue(StartPointProperty);
set => SetValue(StartPointProperty, value);
}
public static readonly BindableProperty EndPointProperty = BindableProperty.Create(
nameof(EndPoint), typeof(Point), typeof(LinearGradientBrush), default(Point));
public Point EndPoint
{
get => (Point)GetValue(EndPointProperty);
set => SetValue(EndPointProperty, value);
}
}
A radial gradient brush paints an area with a radial gradient that has a circle, along with a focal point, to define the gradient behavior. The focal point defines the center of the gradient and has default value 0.0.
public class RadialGradientBrush : GradientBrush
{
public static readonly BindableProperty CenterProperty = BindableProperty.Create(
nameof(Center), typeof(Point), typeof(RadialGradientBrush), default(Point));
public Point Center
{
get => (Point)GetValue(CenterProperty);
set => SetValue(CenterProperty, value);
}
public static readonly BindableProperty GradientOriginProperty = BindableProperty.Create(
nameof(GradientOrigin), typeof(Point), typeof(RadialGradientBrush), default(Point));
public Point GradientOrigin
{
get => (Point)GetValue(GradientOriginProperty);
set => SetValue(GradientOriginProperty, value);
}
public static readonly BindableProperty RadiusXProperty = BindableProperty.Create(
nameof(RadiusX), typeof(double), typeof(RadialGradientBrush), default(Point));
public double RadiusX
{
get => (double)GetValue(RadiusXProperty);
set => SetValue(RadiusXProperty, value);
}
public static readonly BindableProperty RadiusYProperty = BindableProperty.Create(
nameof(RadiusY), typeof(double), typeof(RadialGradientBrush), default(Point));
public double RadiusY
{
get => (double)GetValue(RadiusYProperty);
set => SetValue(RadiusYProperty, value);
}
}
To implement the Spec we can use a PaintDrawable setting a Shader (LinearGradient, etc.) in the case of Android. In iOS, can use a CALayer (DrawLinearGradient, etc.). And with UWP the GradientBrushes.
_Note: Nothing in this specification is guaranteed to be final; all features, implementations, and interfaces are subject to change._
Let's take a look at a simple sample:
<Grid>
<Grid.Background>
<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
<GradientStop Color="Yellow" Offset="0.0" />
<GradientStop Color="Red" Offset="0.25" />
<GradientStop Color="Blue" Offset="0.75" />
<GradientStop Color="LimeGreen" Offset="1.0" />
</LinearGradientBrush>
</Grid.Background>
<Grid>
(see #7374)
#RootContainer{
background: linear-gradient(45deg, rgba(218, 64, 244, 0.26) 0%, rgba(218, 64, 244, 0.26) 3%,rgba(184, 81, 207, 0.26) 3%, rgba(184, 81, 207, 0.26) 26%,rgba(149, 97, 169, 0.26) 26%, rgba(149, 97, 169, 0.26) 27%,rgba(115, 114, 132, 0.26) 27%, rgba(115, 114, 132, 0.26) 46%,rgba(80, 130, 94, 0.26) 46%, rgba(80, 130, 94, 0.26) 87%,rgba(46, 147, 57, 0.26) 87%, rgba(46, 147, 57, 0.26) 100%),linear-gradient(0deg, rgba(247, 80, 105, 0.26) 0%, rgba(247, 80, 105, 0.26) 1%,rgba(223, 84, 119, 0.26) 1%, rgba(223, 84, 119, 0.26) 11%,rgba(199, 88, 133, 0.26) 11%, rgba(199, 88, 133, 0.26) 46%,rgba(174, 91, 147, 0.26) 46%, rgba(174, 91, 147, 0.26) 54%,rgba(150, 95, 161, 0.26) 54%, rgba(150, 95, 161, 0.26) 73%,rgba(126, 99, 175, 0.26) 73%, rgba(126, 99, 175, 0.26) 100%),linear-gradient(90deg, rgb(74, 13, 231) 0%, rgb(74, 13, 231) 18%,rgb(96, 13, 230) 18%, rgb(96, 13, 230) 21%,rgb(119, 13, 229) 21%, rgb(119, 13, 229) 26%,rgb(141, 13, 228) 26%, rgb(141, 13, 228) 32%,rgb(163, 12, 226) 32%, rgb(163, 12, 226) 44%,rgb(185, 12, 225) 44%, rgb(185, 12, 225) 56%,rgb(208, 12, 224) 56%, rgb(208, 12, 224) 64%,rgb(230, 12, 223) 64%, rgb(230, 12, 223) 100%)
}
BackgroundColor
becomes deprecated (it can still be used to avoid breaking third parties) although its replacement will be to use Background
and a SolidColorBrush
.
Before
<Grid BackgroundColor="Red" />
After
<Grid Background="Red" />
The new Background
property as well as BackgroundColor
will be at the same level, in all the views where it is currently possible to use BackgroundColor it will be possible to use Background.
Other options such as BorderColors, etc. are not included in the Spec, etc. (depending on the interest it could come later).
I have reviewed and we have this other similar Spec: https://github.com/xamarin/Xamarin.Forms/issues/1856
@andreinitescu, when you can, please take a look and add your feedback.
I heard that @mattleibow had the Xamarin.Forms.Shapes library that can do this.
@jsuarezruiz Can you also add blur to gradient? In the first picture, the shadow is made from a blurry gradient.
No, I didn't have Blur in mind for this Spec. In this Spec the objective is to have everything related to Brushes, mainly GradientBrushes.
But, currently there is Platform Specific like this https://docs.microsoft.com/es-es/xamarin/xamarin-forms/platform/ios/visualelement-blur
Leave your related feedback!.
Thank you for keeping this spec in sync with WPF and UWP. There's no reason to be different from them when they do the same thing. :+1:
If you have both Background and BackgroundColor properties, what would be the expected behavior if they were both used? And as @dotMorten mentioned, thank you for following the WPF/UWP spec.
As in WPF it would be really perfect
Grid.Background = Brushes.White;
Grid.Background = new SolidColorBrush(Colors.White);
Grid.Background = new GradientBrush (Colors.White,Colorls.Black);
Good question @anotherlab. It is the critical point to analyze and decide in this Spec. If both properties are maintained, one should have priority. For example, if Background is set, it would apply even if BackgroundColor were too.
However, this can bring confusion, for that reason I also mention deprecating BackgroundColor, etc. Although this would have high impact on all existing Apps when updating Xamarin.Forms, etc.
We still have to decide the way to take in this case.
If you have both Background and BackgroundColor properties, what would be the expected behavior if they were both used?
Here's what I would do: Deprecate BackgroundColor. If Background is used, it'll take precedence over BackgroundColor. Introduce SolidColorBrush with a default type converter for the color names/hex-codes.
If you don't deprecate, you get into this confusion of which does what.
Generally all you'd do in Xaml is change BackgroundColor="Red"
to Background="Red"
so not a lot of work (and only if you want to, as the old property still works). It's slightly more in code-behind of course, as you'd have to instantiate the brush, but typically that's not something you do a whole lot.
I'm all for deprecating properties in favor unifying syntax. If I'm updating an app, I'll take the hit on replacing all instances of BackgroundColor with Background.
What does this do for 3rd party component vendors who have controls that use BackgroundColor? Will it break their libraries and will they have to manage two versions of their control sets?
probably worth noting that if you've been using styles rather than inline properties this is probably a lot easier to manage from an update perspective. I would also say deprecation in this case is a good thing.
@anotherlab Deprecating doesn't mean breaking behavior or removing the member. BackgroundColor
continues to work as long as you don't set the Background
brush, which a component vendor wouldn't do until they move up to a newer version and wants to get rid of the obsolete warnings.
Here's one more minor inconsistency with UWP/WPF that we should probably fix:
rename GradientBrush.Gradients
to GradientBrush.GradientStops
@bretjohnson good catch. It's not a collection of gradients (as the brush itself is the gradient) so that name would be misleading. :+1: to changing it to GradientStops
I don't think the Brush should inherit from BO
I don't think the Brush should inherit from BO
It would be good to back that up with a reason ;-)
@jsuarezruiz my comments have all been captured above. Would you pls update the spec to reflect those?
BackgroundColor
What elements will this apply to, all that have BackgroundColor
? What about Label
? BorderColor
? I’m not saying this first iteration should, but let’s also be clear if we are NOT going to do something.
Thanks for the feedback @davidortinau. I have updated the Spec:
BackgroundColor
will be deprecated.Gradients
property, now is GradientStops
. @KSemenenko MagicGradients is a fantastic library although it makes use of SkiaSharp and for this Spec we have in mind to use native APIs directly.
That will be perfect.
But how we can use this in styles? F. e. for theming?
Will OpacityMask be supported with the use of Brushes in future?
See: https://docs.microsoft.com/en-us/dotnet/api/system.windows.uielement.opacitymask?view=netcore-3.1
This has cropped up while attempting to complete the Xamarin port of AdaptiveCards at https://github.com/microsoft/AdaptiveCards/pull/4408
Most helpful comment
Thank you for keeping this spec in sync with WPF and UWP. There's no reason to be different from them when they do the same thing. :+1: