Skip to content

Commit e18c4fc

Browse files
committed
Implement Shadows in Android and iOS
1 parent e9074a9 commit e18c4fc

File tree

22 files changed

+525
-21
lines changed

22 files changed

+525
-21
lines changed

src/Controls/samples/Controls.Sample/Pages/MainPage.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ void SetupMauiLayout()
239239
verticalStack.Add(new Entry { Placeholder = "This is a blue text box", BackgroundColor = Colors.CornflowerBlue });
240240

241241
verticalStack.Add(new GraphicsView { Drawable = new TestDrawable(), HeightRequest = 50, WidthRequest = 200 }); ;
242-
242+
243243
verticalStack.Add(new ProgressBar { Progress = 0.5 });
244244
verticalStack.Add(new ProgressBar { Progress = 0.5, BackgroundColor = Colors.LightCoral });
245245
verticalStack.Add(new ProgressBar { Progress = 0.5, ProgressColor = Colors.Purple });
@@ -307,6 +307,29 @@ void SetupMauiLayout()
307307
ClipShape = new Microsoft.Maui.Controls.Shapes2.Ellipse()
308308
});
309309

310+
verticalStack.Add(new Label { Text = "Shadow" });
311+
verticalStack.Add(new Image
312+
{
313+
HeightRequest = 120,
314+
WidthRequest = 120,
315+
Source = new FontImageSource { FontFamily = "Ionicons", Glyph = "\uf2fe" },
316+
BackgroundColor = Color.FromUint(0xFF512BD4),
317+
Aspect = Aspect.Center,
318+
Shadow = new Shadow { Offset = new Size(10, 10), Opacity = 0.9f, Color = Colors.Purple, Radius = 6 }
319+
});
320+
321+
verticalStack.Add(new Label { Text = "Shadow + ClipShape" });
322+
verticalStack.Add(new Image
323+
{
324+
HeightRequest = 120,
325+
WidthRequest = 120,
326+
Source = new FontImageSource { FontFamily = "Ionicons", Glyph = "\uf2fe" },
327+
BackgroundColor = Color.FromUint(0xFF512BD4),
328+
Aspect = Aspect.Center,
329+
ClipShape = new Microsoft.Maui.Controls.Shapes2.Ellipse(),
330+
Shadow = new Shadow { Offset = new Size(10, 10), Opacity = 0.9f, Color = Colors.Purple, Radius = 6 }
331+
});
332+
310333
Content = new ScrollView
311334
{
312335
Content = verticalStack

src/Controls/src/Core/HandlerImpl/VisualElement.Impl.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ Paint IFrameworkElement.Background
3636

3737
public IShape ClipShape { get; set; }
3838

39+
public Shadow Shadow { get; set; }
40+
3941
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
4042
{
4143
base.OnPropertyChanged(propertyName);

src/Core/src/Core/IFrameworkElement.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ public interface IFrameworkElement : ITransform
3939
/// </summary>
4040
IShape? ClipShape { get; }
4141

42+
/// <summary>
43+
/// Paints a shadow around the target View.
44+
/// </summary>
45+
Shadow Shadow { get; set; }
46+
4247
/// <summary>
4348
/// Gets a value indicating whether this FrameworkElement is enabled in the user interface.
4449
/// </summary>

src/Core/src/Handlers/View/ViewHandler.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public abstract partial class ViewHandler : IViewHandler
1919
{
2020
[nameof(IView.AutomationId)] = MapAutomationId,
2121
[nameof(IView.ClipShape)] = MapClipShape,
22+
[nameof(IView.Shadow)] = MapShadow,
2223
[nameof(IView.Visibility)] = MapVisibility,
2324
[nameof(IView.Background)] = MapBackground,
2425
[nameof(IView.Width)] = MapWidth,
@@ -168,6 +169,26 @@ public static void MapClipShape(IViewHandler handler, IView view)
168169
((WrapperView?)handler.ContainerView)?.UpdateClipShape(view);
169170
#endif
170171
}
172+
173+
public static void MapShadow(IViewHandler handler, IView view)
174+
{
175+
var shadow = view.Shadow;
176+
#if WINDOWS
177+
((NativeView?)handler.ContainerView)?.UpdateShadow(view);
178+
#else
179+
if (!shadow.IsEmpty)
180+
{
181+
handler.HasContainer = true;
182+
}
183+
else
184+
{
185+
if (handler is ViewHandler viewHandler)
186+
handler.HasContainer = viewHandler.NeedsContainer;
187+
}
188+
189+
((NativeView?)handler.ContainerView)?.UpdateShadow(view);
190+
#endif
191+
}
171192

172193
static partial void MappingSemantics(ViewHandler handler, IView view);
173194

src/Core/src/Platform/Android/NativeVersion.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@ public static class NativeApis
2929
public const int PowerSaveMode = 21;
3030
public const int BlendModeColorFilter = 29;
3131
public const int SeekBarSetMin = 26;
32+
public const int OutlineAmbientShadowColor = 28;
3233
}
3334
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using Android.Graphics;
2+
using Android.Views;
3+
using Android.Widget;
4+
using AndroidX.Core.Graphics;
5+
using AView = Android.Views.View;
6+
7+
namespace Microsoft.Maui
8+
{
9+
public static class ShadowExtensions
10+
{
11+
public static void SetShadow(this AView nativeView, Shadow shadow)
12+
{
13+
if (shadow.IsEmpty)
14+
return;
15+
16+
var radius = shadow.Radius;
17+
18+
if (radius < 0)
19+
return;
20+
21+
var opacity = shadow.Opacity;
22+
23+
if (opacity < 0)
24+
return;
25+
26+
var color = shadow.Color.ToNative();
27+
var alphaColor = new Color(ColorUtils.SetAlphaComponent(color, (int)opacity * 255));
28+
29+
if (nativeView is TextView textView)
30+
{
31+
var offsetX = (float)shadow.Offset.Width;
32+
var offsetY = (float)shadow.Offset.Height;
33+
textView.SetShadowLayer(radius, offsetX, offsetY, alphaColor);
34+
return;
35+
}
36+
37+
nativeView.OutlineProvider = ViewOutlineProvider.PaddedBounds;
38+
39+
if (nativeView.Context != null)
40+
nativeView.Elevation = nativeView.Context.ToPixels(radius);
41+
42+
if (!NativeVersion.Supports(NativeApis.OutlineAmbientShadowColor))
43+
return;
44+
45+
nativeView.SetOutlineAmbientShadowColor(alphaColor);
46+
nativeView.SetOutlineSpotShadowColor(alphaColor);
47+
}
48+
49+
public static void ClearShadow(this AView nativeView)
50+
{
51+
nativeView.OutlineProvider = null;
52+
}
53+
}
54+
}

src/Core/src/Platform/Android/ViewExtensions.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,26 @@ public static void UpdateClipShape(this WrapperView nativeView, IView view)
2626
nativeView.ClipShape = view.ClipShape;
2727
}
2828

29+
public static void UpdateShadow(this AView nativeView, IView view)
30+
{
31+
var shadow = view.Shadow;
32+
var clipShape = view.ClipShape;
33+
34+
// If there is a clip shape, then the shadow should be applied to the clip layer, not the view layer
35+
if (clipShape == null)
36+
{
37+
if (shadow.IsEmpty)
38+
nativeView.ClearShadow();
39+
else
40+
nativeView.SetShadow(shadow);
41+
}
42+
else
43+
{
44+
if (nativeView is WrapperView wrapperView)
45+
wrapperView.Shadow = view.Shadow;
46+
}
47+
}
48+
2949
public static ViewStates ToNativeVisibility(this Visibility visibility)
3050
{
3151
return visibility switch

src/Core/src/Platform/Android/WrapperView.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,16 @@ protected override void DispatchDraw(Canvas? canvas)
6262

6363
base.DispatchDraw(canvas);
6464
}
65+
66+
partial void ShadowChanged()
67+
{
68+
if (Shadow == null)
69+
return;
70+
71+
if (Shadow.Value.IsEmpty)
72+
this.ClearShadow();
73+
else
74+
this.SetShadow(Shadow.Value);
75+
}
6576
}
6677
}

src/Core/src/Platform/Standard/ViewExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ public static void UpdateAutomationId(this object nativeView, IView view) { }
1212

1313
public static void UpdateClipShape(this object nativeView, IView view) { }
1414

15+
public static void UpdateShadow(this object nativeView, IView view) { }
16+
1517
public static void UpdateOpacity(this object nativeView, IView view) { }
1618

1719
public static void UpdateSemantics(this object nativeView, IView view) { }
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Microsoft.Maui
2+
{
3+
public static class ShadowExtensions
4+
{
5+
6+
}
7+
}

0 commit comments

Comments
 (0)