shuhelohelo’s blog

Xamarin.Forms多めです.

Xamarin.FormsでFrameに調整可能な影をつける方法

こちらの記事がまさにピッタリのものだった.素晴らしい.ありがとうございます.

alexdunn.org

現時点ではこれがベストかな. カスタムレンダラーを使う.

Frameを継承したShadowFrameクラスを作る.

Elevationプロパティを追加する.バインディング可能にしておいたけれど,用途的にバインディングで影を調節するということは考えにくいので,必要はないと思う.

Elevationプロパティの値が高いほど,影がぼやけてコントロールの位置が高いように見える.

    public class ShadowFrame:Frame
    {
        public float Elevation
        {
            get { return (float)GetValue(ElevationProperty); }
            set { SetValue(ElevationProperty, value); }
        }
        public static readonly BindableProperty ElevationProperty = BindableProperty.Create(
               propertyName: nameof(Elevation),
               returnType: typeof(float),
               declaringType: typeof(ShadowFrame),
               defaultValue: 4.0f,
               defaultBindingMode: BindingMode.OneWay,
               propertyChanged: ElevationPropertyChanged
            );
        private static void ElevationPropertyChanged(BindableObject bindable, object oldValue, object newValue)
        {
        }
    }

ShadowFrameに影をつけるレンダラー

カスタムレンダラーの方は以下のとおり.

using Android.Content;
using Android.Support.V4.View;
using CustomRenderer.Controlls;
using CustomRenderer.Droid.Renderers;
using System.ComponentModel;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(ShadowFrame), typeof(ShadowFrameRenderer))]
namespace CustomRenderer.Droid.Renderers
{
    public class ShadowFrameRenderer : Xamarin.Forms.Platform.Android.AppCompat.FrameRenderer
    {
        public ShadowFrameRenderer(Context context) : base(context)
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
        {
            base.OnElementChanged(e);
            if(e.NewElement==null)
            {
                return;
            }

            UpdateElevation();
        }

        private void UpdateElevation()
        {
            var shadowFrame = (ShadowFrame)Element;

            Control.StateListAnimator = new Android.Animation.StateListAnimator();

            ViewCompat.SetElevation(this, shadowFrame.Elevation);
            ViewCompat.SetElevation(Control, shadowFrame.Elevation);
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
            if(e.PropertyName=="Elevation")
            {
                UpdateElevation();
            }
        }
    }
}

UpdateElevationメソッドでElevationプロパティの値を更新している.

            ViewCompat.SetElevation(this, shadowFrame.Elevation);
            ViewCompat.SetElevation(Control, shadowFrame.Elevation);

使ってみる

以下のようにShadowFrameを使ってみる.

        <controls:ShadowFrame Elevation="50" />

こんな感じにFrameに影がつき,Elevationの値を変更すると影の様子が異なる.

Elevation=50 f:id:shuhelohelo:20200112022351p:plain

Elevation=10 f:id:shuhelohelo:20200112022445p:plain

影付きのボタンを作る

このShadowFrameの中にButtonを入れて,どちらも円形にすると影付きのButtonになる.

        <controls:ShadowFrame
            Padding="0"
            CornerRadius="40"
            Elevation="50"
            HorizontalOptions="Center"
            IsClippedToBounds="True"
            VerticalOptions="Center">
            <Button
                BackgroundColor="White"
                CornerRadius="40"
                HeightRequest="70"
                HorizontalOptions="Center"
                Text="A"
                VerticalOptions="Center"
                WidthRequest="70" />
        </controls:ShadowFrame>

f:id:shuhelohelo:20200112023506p:plain

ボタンに色を付けると,影があまり目立たなくなるけれども立体感は感じられる.

f:id:shuhelohelo:20200112023658p:plain

FrameのHasShadowプロパティはだいたいElevation=10ぐらいの感覚.

参考にしたサイトはiOSのレンダラーも書いてある.

iOS側レンダラー

public class MaterialFrameRenderer : FrameRenderer
{
    public static void Initialize()
    {
        // empty, but used for beating the linker
    }
    protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
    {
        base.OnElementChanged(e);
 
        if (e.NewElement == null)
            return;
        UpdateShadow();
    }
 
    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);
        if(e.PropertyName == "Elevation")
        {
            UpdateShadow();
        }
    }
 
    private void UpdateShadow()
    {
        var materialFrame = (MaterialFrame)Element;
 
        // Update shadow to match better material design standards of elevation
        Layer.ShadowRadius = materialFrame.Elevation;
        Layer.ShadowColor = UIColor.Gray.CGColor;
        Layer.ShadowOffset = new CGSize(2, 2);
        Layer.ShadowOpacity = 0.80f;
        Layer.ShadowPath = UIBezierPath.FromRect(Layer.Bounds).CGPath;
        Layer.MasksToBounds = false; 
    }
}

iOS側は色々設定できるみたいでいいなぁ.

参考:

qiita.com