Как установить эффект тени на ImageView

Я пытаюсь установить тень на представление изображения на Xamarin.Формы (таргетинг на платформу Android), и я получил несколько примеров в интернете.

код PCL довольно прост, и платформа тоже казалась довольно простой. рецепт доступен на официальном сайте разработчика xamarin что-то вроде этого:

[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() 
        { 
        }
    }
}

поэтому я заметил, что этот рецепт работает только для компонентов, которые визуализируются с TextView (это единственный класс с SetShadowLayer метод). В другие источники Я видел что-то более общее типа:

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;
    }
}

С помощью UIColor Я понимаю, что он нацелен на платформу iOS. Нет такой вещи, как это на Android просмотров. Я взглянул на исходный код XF FrameRenderer, но я не смог понять, как они заставили теневой эффект работать.

кто-нибудь может мне помочь?

3 ответов


к сожалению, нет прямого способа заставить это работать для android. Но есть несколько вариантов, которые вы можете попробовать.

#1

существует несколько неподдерживаемых операций рисования для аппаратных ускоренных слоев, которая включает SetShadowLayer для нетекстового представления.

Итак, чтобы получить SetShadowLayer render для нетекстового представления, вам нужно установить LayerType рендеринг как SOFTWARE как поясняется в решение.

SetLayerType(LayerType.Software, null);

но основным недостатком, конечно, является то, что это может быть проблема производительности.

#2

второй вариант-использовать радиальный градиент для имитации тени. Я реализовал его как визуализатор (но вы также должны иметь возможность реализовать его как эффект). Результат, конечно, не так велик, как эффект размытой тени. Вам также придется установить правильный Padding позволить некоторому космосу для тени сделать, и быть видимым под изображение.

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);
}   
#3

другой вариант - использовать SkiaSharp для форм - т. е. создайте контейнерное (или слоистое) представление, которое отображает тень вокруг дочернего представления (изображения). Вы можете также отобразить изображение SkiaSharp или встроить элемент управления изображением на основе XF внутри макета.

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);
    }
}

пожалуйста, попробуйте это ниже код будет работать

<Frame OutlineColor="Transparent" Padding="0" CornerRadius="0" VerticalOptions="Center" BackgroundColor="Teal">
    <Image Source="Imagename" Aspect="Fill" />
</Frame>

Я рекомендую использовать Elevation собственность вместо ShadowLayer; Android добавит правильную тень на основе высоты, которую вы даете, и будет придерживаться материального дизайна.

хотя вам придется использовать пользовательский рендерер для этого подхода, и вам нужно будет создать другой рендерер для iOS/UWP.