Skip to content
This repository was archived by the owner on May 1, 2024. It is now read-only.

Commit 5dd6a85

Browse files
ShadowEffect (#725)
* #461 * Added mac support * Added sample * Updated shadows * Fixed iOS crash * removed pragma * Remove effect when color if default * cleaned code
1 parent 21931d9 commit 5dd6a85

File tree

7 files changed

+404
-0
lines changed

7 files changed

+404
-0
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<pages:BasePage xmlns="http://xamarin.com/schemas/2014/forms"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
4+
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
5+
xmlns:pages="clr-namespace:Xamarin.CommunityToolkit.Sample.Pages"
6+
x:Class="Xamarin.CommunityToolkit.Sample.Pages.Effects.ShadowEffectPage"
7+
BackgroundColor="White"
8+
x:Name="Page">
9+
10+
<pages:BasePage.Resources>
11+
<Style x:Key="ShadowLabel" TargetType="Label">
12+
<Setter Property="FontSize" Value="22" />
13+
<Setter Property="FontAttributes" Value="Bold" />
14+
<Setter Property="TextColor" Value="Black" />
15+
</Style>
16+
</pages:BasePage.Resources>
17+
18+
<ScrollView Padding="{StaticResource ContentPadding}">
19+
<StackLayout Spacing="30"
20+
Padding="15, 0">
21+
22+
<StackLayout Spacing="10">
23+
<Label Text="Labels:"/>
24+
25+
<Label Text="Label With Black Shadow"
26+
Style="{StaticResource ShadowLabel}"
27+
xct:ShadowEffect.Color="Black"/>
28+
29+
<Label Text="Label With Shifted Red Shadow"
30+
Style="{StaticResource ShadowLabel}"
31+
xct:ShadowEffect.Color="Red"
32+
xct:ShadowEffect.OffsetX="10"
33+
xct:ShadowEffect.OffsetY="10" />
34+
35+
<Label Text="Label With Gold Shadow"
36+
Style="{StaticResource ShadowLabel}"
37+
xct:ShadowEffect.Color="Gold"
38+
xct:ShadowEffect.Radius="20"/>
39+
</StackLayout>
40+
41+
<StackLayout Spacing="10">
42+
<Label Text="Stack Layout with Shadow" />
43+
<StackLayout xct:ShadowEffect.Color="Black">
44+
<BoxView Color="White" />
45+
<BoxView Color="Red" />
46+
<BoxView Color="White" />
47+
</StackLayout>
48+
</StackLayout>
49+
50+
<StackLayout Spacing="10">
51+
<Label Text="Box Views With Colored Shadows" />
52+
<Grid RowDefinitions="*,*"
53+
ColumnDefinitions="*,*,*,*,*"
54+
RowSpacing="10"
55+
ColumnSpacing="10">
56+
57+
<Grid.Behaviors>
58+
<xct:ImpliedOrderGridBehavior/>
59+
</Grid.Behaviors>
60+
61+
<BoxView Color="White" xct:ShadowEffect.Color="Blue" />
62+
<BoxView Color="White" xct:ShadowEffect.Color="Gold" />
63+
<BoxView Color="White" xct:ShadowEffect.Color="Purple" />
64+
<BoxView Color="White" xct:ShadowEffect.Color="HotPink" />
65+
<BoxView Color="White" xct:ShadowEffect.Color="Orange" />
66+
<BoxView Color="White" xct:ShadowEffect.Color="Brown" />
67+
<BoxView Color="White" xct:ShadowEffect.Color="Lime" />
68+
<BoxView Color="White" xct:ShadowEffect.Color="Yellow" />
69+
<BoxView Color="White" xct:ShadowEffect.Color="DarkRed" />
70+
<BoxView Color="White" xct:ShadowEffect.Color="LightSalmon" />
71+
72+
</Grid>
73+
</StackLayout>
74+
75+
</StackLayout>
76+
</ScrollView>
77+
</pages:BasePage>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+

2+
namespace Xamarin.CommunityToolkit.Sample.Pages.Effects
3+
{
4+
public partial class ShadowEffectPage
5+
{
6+
public ShadowEffectPage()
7+
{
8+
InitializeComponent();
9+
}
10+
}
11+
}

samples/XCT.Sample/ViewModels/Effects/EffectsGalleryViewModel.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ public class EffectsGalleryViewModel : BaseGalleryViewModel
3333
typeof(TouchEffectPage),
3434
nameof(TouchEffect),
3535
"The TouchEffect is an effect that allows changing the view's appearance depending on the touch state (normal, pressed, hovered). Also, it allows to handle long presses."),
36+
37+
new SectionModel(
38+
typeof(ShadowEffectPage),
39+
nameof(ShadowEffect),
40+
"The ShadowEffect allows all views to display shadow."),
3641
};
3742
}
3843
}

src/CommunityToolkit/Xamarin.CommunityToolkit/Effects/EffectIds.shared.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,10 @@ sealed class EffectIds
4040
/// Effect Id for <see cref="TouchEffect"/>
4141
/// </summary>
4242
public static string TouchEffect => $"{effectResolutionGroupName}.{nameof(TouchEffect)}";
43+
44+
/// <summary>
45+
/// Effect Id for <see cref="ShadowEffect"/>
46+
/// </summary>
47+
public static string ShadowEffect => $"{effectResolutionGroupName}.{nameof(ShadowEffect)}";
4348
}
4449
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using Xamarin.Forms.Platform.Android;
2+
using Xamarin.Forms;
3+
using Android.Views;
4+
using AView = Android.Views.View;
5+
using Android.OS;
6+
using System.ComponentModel;
7+
using Xamarin.CommunityToolkit.Effects;
8+
using Xamarin.CommunityToolkit.Android.Effects;
9+
using Android.Widget;
10+
11+
[assembly: ExportEffect(typeof(PlatformShadowEffect), nameof(ShadowEffect))]
12+
13+
namespace Xamarin.CommunityToolkit.Android.Effects
14+
{
15+
public class PlatformShadowEffect : PlatformEffect
16+
{
17+
const float defaultRadius = 10f;
18+
19+
const float defaultOpacity = 1f;
20+
21+
AView View => Control ?? Container;
22+
23+
protected override void OnAttached()
24+
=> Update();
25+
26+
protected override void OnDetached()
27+
{
28+
if (View == null)
29+
return;
30+
31+
View.Elevation = 0;
32+
}
33+
34+
protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)
35+
{
36+
base.OnElementPropertyChanged(args);
37+
38+
if (View == null)
39+
return;
40+
41+
switch (args.PropertyName)
42+
{
43+
case nameof(ShadowEffect.ColorPropertyName):
44+
case nameof(ShadowEffect.OpacityPropertyName):
45+
case nameof(ShadowEffect.RadiusPropertyName):
46+
case nameof(ShadowEffect.OffsetXPropertyName):
47+
case nameof(ShadowEffect.OffsetYPropertyName):
48+
View.Invalidate();
49+
Update();
50+
break;
51+
}
52+
}
53+
54+
void Update()
55+
{
56+
if (View == null || Build.VERSION.SdkInt < BuildVersionCodes.Lollipop)
57+
return;
58+
59+
var radius = (float)ShadowEffect.GetRadius(Element);
60+
if (radius < 0)
61+
radius = defaultRadius;
62+
63+
var opacity = ShadowEffect.GetOpacity(Element);
64+
if (opacity < 0)
65+
opacity = defaultOpacity;
66+
67+
var androidColor = ShadowEffect.GetColor(Element).MultiplyAlpha(opacity).ToAndroid();
68+
69+
if (View is TextView textView)
70+
{
71+
var offsetX = (float)ShadowEffect.GetOffsetX(Element);
72+
var offsetY = (float)ShadowEffect.GetOffsetY(Element);
73+
textView.SetShadowLayer(radius, offsetX, offsetY, androidColor);
74+
return;
75+
}
76+
77+
View.OutlineProvider = (Element as VisualElement)?.BackgroundColor.A > 0
78+
? ViewOutlineProvider.PaddedBounds
79+
: ViewOutlineProvider.Bounds;
80+
81+
View.Elevation = View.Context.ToPixels(radius);
82+
83+
if (Build.VERSION.SdkInt < BuildVersionCodes.P)
84+
return;
85+
86+
View.SetOutlineAmbientShadowColor(androidColor);
87+
View.SetOutlineSpotShadowColor(androidColor);
88+
}
89+
}
90+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using System;
2+
using System.ComponentModel;
3+
using CoreGraphics;
4+
using Xamarin.CommunityToolkit.Effects;
5+
using Xamarin.Forms;
6+
7+
#if __IOS__
8+
using NativeView = UIKit.UIView;
9+
using Xamarin.Forms.Platform.iOS;
10+
using Xamarin.CommunityToolkit.iOS.Effects;
11+
#elif __MACOS__
12+
using NativeView = AppKit.NSView;
13+
using Xamarin.Forms.Platform.MacOS;
14+
using Xamarin.CommunityToolkit.macOS.Effects;
15+
#endif
16+
17+
[assembly: ExportEffect(typeof(PlatformShadowEffect), nameof(ShadowEffect))]
18+
19+
#if __IOS__
20+
namespace Xamarin.CommunityToolkit.iOS.Effects
21+
#elif __MACOS__
22+
namespace Xamarin.CommunityToolkit.macOS.Effects
23+
#endif
24+
{
25+
public class PlatformShadowEffect : PlatformEffect
26+
{
27+
const float defaultRadius = 10f;
28+
29+
const float defaultOpacity = .5f;
30+
31+
NativeView View => Control ?? Container;
32+
33+
protected override void OnAttached()
34+
{
35+
if (View == null)
36+
return;
37+
38+
UpdateColor();
39+
UpdateOpacity();
40+
UpdateRadius();
41+
UpdateOffset();
42+
}
43+
44+
protected override void OnDetached()
45+
{
46+
if (View?.Layer == null)
47+
return;
48+
49+
View.Layer.ShadowOpacity = 0;
50+
}
51+
52+
protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)
53+
{
54+
base.OnElementPropertyChanged(args);
55+
56+
if (View == null)
57+
return;
58+
59+
switch (args.PropertyName)
60+
{
61+
case nameof(ShadowEffect.ColorPropertyName):
62+
UpdateColor();
63+
break;
64+
case nameof(ShadowEffect.OpacityPropertyName):
65+
UpdateOpacity();
66+
break;
67+
case nameof(ShadowEffect.RadiusPropertyName):
68+
UpdateRadius();
69+
break;
70+
case nameof(ShadowEffect.OffsetXPropertyName):
71+
case nameof(ShadowEffect.OffsetYPropertyName):
72+
UpdateOffset();
73+
break;
74+
}
75+
}
76+
77+
void UpdateColor()
78+
=> View.Layer.ShadowColor = ShadowEffect.GetColor(Element).ToCGColor();
79+
80+
void UpdateOpacity()
81+
{
82+
var opacity = (float)ShadowEffect.GetOpacity(Element);
83+
View.Layer.ShadowOpacity = opacity < 0
84+
? defaultOpacity
85+
: opacity;
86+
}
87+
88+
void UpdateRadius()
89+
{
90+
var radius = (nfloat)ShadowEffect.GetRadius(Element);
91+
View.Layer.ShadowRadius = radius < 0
92+
? defaultRadius
93+
: radius;
94+
}
95+
96+
void UpdateOffset()
97+
=> View.Layer.ShadowOffset = new CGSize((double)ShadowEffect.GetOffsetX(Element), (double)ShadowEffect.GetOffsetY(Element));
98+
}
99+
}

0 commit comments

Comments
 (0)