I'm tryin' to set shadow on an Image view on Xamarin.Forms (targeting the Android platform) and I got some examples on the internet.
The PCL code is quite simple, and the platform seemed pretty easy too. The recipe available at the official xamarin developer site is something like this:
[assembly: ResolutionGroupName("MyGroupName")]
[assembly: ExportEffect(typeof(LabelShadowEffect), "ShadowEffect")]
namespace MyWorkspace
{
public class LabelShadowEffect : PlatformEffect
{
protected override void OnAttached()
{
try
{
var control = (Control as TextView); // TextView have the SetShadowLayer method, but others views don't
var effect = (ShadowEffect)Element.Effects.FirstOrDefault(e => e is ShadowEffect);
if (effect != null)
{
float radius = effect.Radius;
float distanceX = effect.DistanceX;
float distanceY = effect.DistanceY;
Android.Graphics.Color color = effect.Color.ToAndroid();
control?.SetShadowLayer(radius, distanceX, distanceY, color);
}
}
catch (Exception)
{
}
}
protected override void OnDetached()
{
}
}
}
So I've noticed that this recipe works only for components that render with TextView (that's the only class with the SetShadowLayer
method). In other sources I saw something more generic like:
public class ShadowEffect : PlatformEffect
{
protected override void OnAttached ()
{
Container.Layer.ShadowOpacity = 1;
Container.Layer.ShadowColor = UIColor.Black.ToCGColor;
Container.Layer.ShadowRadius = 6;
}
protected override void OnDetached ()
{
Container.Layer.ShadowOpacity = 0;
}
}
By the use of UIColor
I get that it's targeting iOS platform. No such a thing like this on Android views. I took a look at the XF FrameRenderer source code but I was not able to understand how they made the shadow effect works.
Someone can help me with it?
There is no such attribute in Android, to show a shadow. But possible ways to do it are: Add a plain LinearLayout with grey color, over which add your actual layout, with margin at the bottom and right equal to 1 or 2 dp.
Lollipop's elevation system doesn't support colored shadows. But, if you need colored shadows, it's possible to get them using Carbon. It's a kind-of support library for Material Design and in the most recent version there is an option to change shadow color.
Sadly there is no straight-forward way to get this working for android. But there are some options that you can try.
There are several unsupported drawing operations for hardware accelerated layers, that includes SetShadowLayer
for non-text views.
So, in order to get the SetShadowLayer
render for a non-text view, you need to set the LayerType
rendering as SOFTWARE
as explained in this solution.
SetLayerType(LayerType.Software, null);
But the major drawback of course is that it can be a performance issue.
Second option is to use an radial gradient to emulate the shadow. I had implemented it as a renderer (but you should be able to implement it as an effect too). The result of course is not as great as a blurred shadow effect. You will also have to set the right Padding
to let some space for the shadow to render, and be visible under the image.
protected override void DispatchDraw(global::Android.Graphics.Canvas canvas)
{
try
{
var nativeCtrl = Control;
var formsElement = Element;
if (nativeCtrl == null || formsElement == null)
{
base.DispatchDraw(canvas);
return;
}
//convert from logical to native metrics if need be
var shadowDistanceX = 10f;
var shadowDistanceY = 10f;
var shadowRadius = 5f;
var shadowOpacity = .5f;
var shadowColor = Color.Black;
var cornerRadius = 0.2f;
var bounds = formsElement.Bounds;
var left = shadowDistanceX;
var top = shadowDistanceY;
var right = Width + shadowDistanceX;
var bottom = Height + shadowDistanceY;
var rect = new Android.Graphics.RectF(left, top, right, bottom);
canvas.Save();
using (var paint = new Android.Graphics.Paint { AntiAlias = true })
{
paint.SetStyle(Android.Graphics.Paint.Style.Fill);
var nativeShadowColor = shadowColor.MultiplyAlpha(shadowOpacity * 0.75f).ToAndroid();
paint.Color = nativeShadowColor;
var gradient = new Android.Graphics.RadialGradient(
0.5f, 0.5f,
shadowRadius,
shadowColor.ToAndroid(),
nativeShadowColor,
Android.Graphics.Shader.TileMode.Clamp
);
paint.SetShader(gradient);
//convert from logical to native metrics if need be
var nativeRadius = cornerRadius;
canvas.DrawRoundRect(rect, nativeRadius, nativeRadius, paint);
var clipPath = new Android.Graphics.Path();
clipPath.AddRoundRect(new Android.Graphics.RectF(0f, 0f, Width, Height), nativeRadius, nativeRadius, Android.Graphics.Path.Direction.Cw);
canvas.ClipPath(clipPath);
}
canvas.Restore();
}
catch (Exception ex)
{
//log exception
}
base.DispatchDraw(canvas);
}
Another option would be to use SkiaSharp for Forms - i.e. create a container (or layered) view that renders the shadow around the child view (image). You can either have SkiaSharp render the image too, or embed a XF based image control inside the layout.
protected override void OnPaintSurface(SKPaintSurfaceEventArgs args)
{
var imgInfo = args.Info;
var surface = args.Surface;
var canvas = surface.Canvas;
var drawBounds = imgInfo.Rect;
var path = new SKPath();
var cornerRadius = 5f;
if (cornerRadius > 0)
{
path.AddRoundedRect(drawBounds, cornerRadius, cornerRadius);
}
else
{
path.AddRect(drawBounds);
}
using (var paint = new SKPaint()
{
ImageFilter = SKImageFilter.CreateDropShadow(
offsetX,
offsetY,
blurX,
blurY,
color,
SKDropShadowImageFilterShadowMode.DrawShadowOnly),
})
{
canvas.DrawPath(path, paint);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With