Windows系のあれこれ

港区の SIer で よくわからんことをしている SE みたいな人です。Xamarin 中心です。

Xamarin.Formsでぺこぺこ凹むアイコンボタンを作った

Image だけをボタンにしたいケースが多々あり、TapGestureRecognizer で簡単に作れるのですが、押した感がなくイマイチだし好みに合うヤツが見当たらなかったので作りました。
f:id:ShunsukeKawai:20170106115747g:plain
わかりにくいですが、黒い背景でも微妙にボタン裏の色が変わっています。

凹む処理はここを参考にしました。
app-evolve/FavoriteImage.cs at master · xamarinhq/app-evolve · GitHub

最初は Image コントロールだけを拡張していましたが、それだと凹ますと背景色まで凹んでしまいかっこ悪かったので Content View の中に Image を入れました。

IconButton.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentView
    x:Class="IconButtonSample.Controls.IconButton"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
    <Image x:Name="imgIcon" BindingContextChanged="imgIcon_BindingContextChanged" />
</ContentView>
IconButton.xaml.cs
using System;
using System.Threading.Tasks;

using Xamarin.Forms;

namespace IconButtonSample.Controls
{
    public partial class IconButton : ContentView
    {
        private bool addedAnimation;

        public static readonly BindableProperty IconSourceProperty =
            BindableProperty.Create(
                "IconSource",
                typeof(ImageSource),
                typeof(IconButton),
                null,
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    ((IconButton)bindable).IconSource = (ImageSource)newValue;
                }
            );

        public ImageSource IconSource
        {
            get { return (ImageSource)GetValue(IconSourceProperty); }
            set
            {
                SetValue(IconSourceProperty, value);
                imgIcon.Source = value;
            }
        }

        public static readonly BindableProperty IconMarginProperty =
            BindableProperty.Create(
                "IconMargin",
                typeof(Thickness),
                typeof(IconButton),
                new Thickness(0),
                propertyChanged: (bindable, oldValue, newValue) =>
                {
                    ((IconButton)bindable).IconMargin = (Thickness)newValue;
                }
            );

        public Thickness IconMargin
        {
            get { return (Thickness)GetValue(IconMarginProperty); }
            set
            {
                SetValue(IconMarginProperty, value);
                imgIcon.Margin = value;
            }
        }

        public IconButton()
        {
            InitializeComponent();
        }

        private void imgIcon_BindingContextChanged(object sender, EventArgs e)
        {
            if (addedAnimation || GestureRecognizers.Count == 0)
                return;

            var tapGesture = GestureRecognizers[0] as TapGestureRecognizer;
            if (tapGesture == null)
                return;

            tapGesture.Tapped += (tapsender, tape) =>
            {
                Device.BeginInvokeOnMainThread(async () => await tappedAnimation());
            };

            addedAnimation = true;
        }

        private async Task tappedAnimation()
        {
            BackgroundColor = Color.FromRgba(68, 68, 68, 70);
            await imgIcon.ScaleTo(0.8, 75);
            await imgIcon.ScaleTo(1.0, 75);
            BackgroundColor = Color.Transparent;
        }
    }
}

凹ます処理の前後に Content View 自体の背景色を適当な灰色にしています。
ポイントとして Alpha 値を設定して透過にすることで画面の背景色になじむようにしています。
本来はこの辺も BindableProperty にして公開した方がよかったかもしれませんが、まぁいいかなと。

使う画面.xaml
<control:IconButton
    HeightRequest="50"
    HorizontalOptions="Center"
    IconMargin="10"
    IconSource="{Binding Source=IconButtonSample.Images.Trash.png, Converter={StaticResource ImageSourceConverter}}"
    VerticalOptions="Center">
    <control:IconButton.GestureRecognizers>
        <TapGestureRecognizer Command="{Binding Path=DeleteCommand}" />
    </control:IconButton.GestureRecognizers>
</control:IconButton>

ImageSource を Converter 使っていますが、これは画像を埋め込みリソースにして PCL 側に置いているからです。
その辺も含めてこちらにまるごとサンプルを置いてありますので、ご自由にどうぞー。
Prism 使ってます。
github.com

あとは指が抑えている状態(Hold 時)にも色変えたいけど、とりあえず標準のヤツよりいいから満足です。

更新しました

shunsukekawai.hatenablog.com