Skip to content

Commit ccf296c

Browse files
committed
feat: ElevatedView unification on Android
1 parent d7a7472 commit ccf296c

File tree

4 files changed

+153
-12
lines changed

4 files changed

+153
-12
lines changed

src/SamplesApp/UITests.Shared/Toolkit/ElevatedView_Tester.xaml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,19 @@
88
mc:Ignorable="d"
99
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
1010

11-
<Grid RowSpacing="8">
11+
<Grid RowSpacing="8" Padding="20">
1212
<Grid.RowDefinitions>
1313
<RowDefinition Height="Auto" />
1414
<RowDefinition Height="Auto" />
1515
<RowDefinition Height="Auto" />
1616
<RowDefinition Height="*" />
1717
</Grid.RowDefinitions>
1818

19-
<Slider Header="Elevation" Value="{x:Bind Elevation, Mode=TwoWay}" Minimum="0" Maximum="128" />
19+
<TextBlock Margin="4" Text="{Binding ElementName=ElevationSlider, Path=Value}" HorizontalAlignment="Right" />
20+
<Slider x:Name="ElevationSlider" Header="Elevation" Value="{x:Bind Elevation, Mode=TwoWay}" Minimum="0" Maximum="128" />
2021

21-
<Slider Header="Corner radius" Value="{x:Bind Radius, Mode=TwoWay}" Minimum="0" Maximum="40" Grid.Row="1" />
22+
<TextBlock Margin="4" Text="{Binding ElementName=CornerRadiusSlider, Path=Value}" HorizontalAlignment="Right" Grid.Row="1" />
23+
<Slider x:Name="CornerRadiusSlider" Header="Corner radius" Value="{x:Bind Radius, Mode=TwoWay}" Minimum="0" Maximum="40" Grid.Row="1" />
2224

2325
<TextBox Header="Color" Text="{x:Bind ColorString, Mode=TwoWay}" Grid.Row="2" />
2426

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#nullable enable
2+
3+
using AndroidCanvas = Android.Graphics.Canvas;
4+
using AndroidColor = Android.Graphics.Color;
5+
using AndroidPaint = Android.Graphics.Paint;
6+
using AndroidBitmap = Android.Graphics.Bitmap;
7+
using Android.Graphics;
8+
using System;
9+
using Android.App;
10+
11+
namespace Uno.UI.Toolkit;
12+
13+
partial class ElevatedView
14+
{
15+
private const int MaximumRadius = 128;
16+
17+
private AndroidCanvas? _shadowCanvas = null;
18+
private AndroidPaint? _shadowPaint = null;
19+
private AndroidBitmap? _shadowBitmap = null;
20+
private bool _invalidateShadow = true;
21+
22+
protected override void DispatchDraw(Canvas canvas)
23+
{
24+
if (Elevation > 0)
25+
{
26+
DrawShadow(canvas);
27+
}
28+
else
29+
{
30+
DisposeShadow();
31+
}
32+
33+
// Draw children
34+
base.DispatchDraw(canvas);
35+
}
36+
37+
private void DisposeShadow()
38+
{
39+
_shadowCanvas?.Dispose();
40+
_shadowPaint?.Dispose();
41+
_shadowBitmap?.Dispose();
42+
_shadowCanvas = null;
43+
_shadowPaint = null;
44+
_shadowBitmap = null;
45+
}
46+
47+
private void DrawShadow(AndroidCanvas canvas)
48+
{
49+
_shadowCanvas ??= new AndroidCanvas();
50+
51+
_shadowPaint ??= new AndroidPaint
52+
{
53+
AntiAlias = true,
54+
Dither = true,
55+
FilterBitmap = true
56+
};
57+
58+
AndroidColor? solidColor = null;
59+
60+
if (_invalidateShadow)
61+
{
62+
var viewHeight = ActualHeight;
63+
var viewWidth = ActualWidth;
64+
65+
// If bounds is zero
66+
if (viewHeight != 0 && viewWidth != 0)
67+
{
68+
var bitmapHeight = viewHeight + MaximumRadius;
69+
var bitmapWidth = viewWidth + MaximumRadius;
70+
71+
// Reset bitmap to bounds
72+
_shadowBitmap = AndroidBitmap.CreateBitmap(
73+
(int)bitmapWidth,
74+
(int)bitmapHeight,
75+
AndroidBitmap.Config.Argb8888);
76+
77+
// Reset Canvas
78+
_shadowCanvas.SetBitmap(_shadowBitmap);
79+
80+
_invalidateShadow = false;
81+
82+
// Create the local copy of all content to draw bitmap as a
83+
// bottom layer of natural canvas.
84+
base.DispatchDraw(_shadowCanvas);
85+
86+
// Get the alpha bounds of bitmap
87+
var extractAlpha = _shadowBitmap!.ExtractAlpha();
88+
if (extractAlpha is not null)
89+
{
90+
// Clear past content content to draw shadow
91+
_shadowCanvas.DrawColor(AndroidColor.Black, PorterDuff.Mode.Clear);
92+
93+
_shadowPaint.Color = ShadowColor;
94+
const float x = 0.28f;
95+
const float y = 0.90f * 0.5f; // Looks more accurate than the recommended 0.92f.
96+
// Apply the shadow radius
97+
var radius = (float)Math.Round(0.3f * (float)Elevation, 1, MidpointRounding.AwayFromZero);
98+
99+
if (radius > 0)
100+
{
101+
_shadowPaint.SetMaskFilter(new BlurMaskFilter(radius, BlurMaskFilter.Blur.Normal));
102+
}
103+
104+
float shadowOffsetX = (float)Elevation * x;
105+
float shadowOffsetY = (float)Elevation * y;
106+
107+
_shadowCanvas.DrawBitmap(extractAlpha!, (int)shadowOffsetX, (int)shadowOffsetY, _shadowPaint);
108+
109+
extractAlpha?.Recycle();
110+
}
111+
112+
// Reset alpha to draw child with full alpha
113+
if (solidColor != null)
114+
{
115+
#pragma warning disable CA1416 // https://github.com/xamarin/xamarin-android/issues/6962
116+
_shadowPaint.Color = ShadowColor;
117+
#pragma warning restore CA1416
118+
}
119+
120+
// Draw shadow bitmap
121+
if (_shadowCanvas != null && _shadowBitmap != null && !_shadowBitmap.IsRecycled)
122+
{
123+
canvas.DrawBitmap(_shadowBitmap, 0.0F, 0.0F, _shadowPaint);
124+
}
125+
}
126+
else
127+
{
128+
DisposeShadow();
129+
}
130+
}
131+
}
132+
}

src/Uno.UI.Toolkit/ElevatedView.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#elif __MACOS__
1212
using CoreGraphics;
1313
using _View = AppKit.NSView;
14+
#elif __ANDROID__
15+
using Android.Views;
1416
#endif
1517

1618

@@ -46,11 +48,7 @@ public sealed partial class ElevatedView : Control
4648
*
4749
*/
4850

49-
#if __ANDROID__
50-
private static readonly Color DefaultShadowColor = Colors.Black;
51-
#else
5251
private static readonly Color DefaultShadowColor = Color.FromArgb(64, 0, 0, 0);
53-
#endif
5452

5553
private Border _border;
5654
private Panel _shadowHost;
@@ -194,9 +192,13 @@ private void UpdateElevation()
194192
#elif __IOS__ || __MACOS__
195193
this.SetElevationInternal(Elevation, ShadowColor, _border.BoundsPath);
196194
#elif __ANDROID__
197-
// The elevation must be applied on the border, since
198-
// it will get the right shape (with rounded corners)
199-
_border.SetElevationInternal(Elevation, ShadowColor);
195+
#if __ANDROID__
196+
_invalidateShadow = true;
197+
((ViewGroup)this).Invalidate();
198+
#endif
199+
//// The elevation must be applied on the border, since
200+
//// it will get the right shape (with rounded corners)
201+
//_border.SetElevationInternal(Elevation, ShadowColor);
200202
#elif __SKIA__
201203
this.SetElevationInternal(Elevation, ShadowColor);
202204
#elif (NETFX_CORE || NETCOREAPP) && !HAS_UNO
@@ -212,6 +214,11 @@ private void UpdateElevation()
212214

213215
protected override Windows.Foundation.Size ArrangeOverride(Windows.Foundation.Size finalSize)
214216
{
217+
#if __ANDROID__
218+
_invalidateShadow = true;
219+
((ViewGroup)this).Invalidate();
220+
#endif
221+
215222
return base.ArrangeOverride(this.ApplySizeConstraints(finalSize));
216223
}
217224
#endif

src/Uno.UI.Toolkit/UIElementExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ internal static void SetElevationInternal(this DependencyObject element, double
103103
// Values for 1dp elevation according to https://material.io/guidelines/resources/shadows.html#shadows-illustrator
104104
const float x = 0.28f;
105105
const float y = 0.92f * 0.5f; // Looks more accurate than the recommended 0.92f.
106-
const float blur = 0.17f;
106+
const float blur = 0.18f;
107107

108108
#if __MACOS__
109109
view.WantsLayer = true;
@@ -152,7 +152,7 @@ internal static void SetElevationInternal(this DependencyObject element, double
152152
var visual = uiElement.Visual;
153153
const float x = 0.28f;
154154
const float y = 0.92f * 0.5f;
155-
const float blur = 0.17f;
155+
const float blur = 0.18f;
156156

157157
var dx = (float)elevation * x;
158158
var dy = (float)elevation * y;

0 commit comments

Comments
 (0)