shuhelohelo’s blog

Xamarin.Forms多めです.

吹き出しコントロールの作り方

github.com

吹き出しコントロールはFrame(吹き出し本体部分)とSkiaSharp(吹き出しの口部分)の組み合わせで作ることができる.

Gridの中にSkiaSharpのキャンバスとFrameを重ね合わせて置くことで,吹き出しの見た目を作っている.

これをカスタムコントロールとしてひとまとめにして,部品として使えるようにしている.

SkiaSharpの描画とFrameの配置は地道に調整していけばよい.

吹き出しの口部分

SKCanvasViewを継承したクラスを作って,これをキャンバスとして図形を描画し,カスタムコントロールと同じように部品として使っている.

また,ここでもBindablePropertyを使っている.

    internal class BalloonMouth : SKCanvasView
    {
        #region Properties

        /// <summary>
        /// 吹き出しの口の向き
        /// </summary>
        public MouthDirection MouthDirection { get; set; }
        public static readonly BindableProperty MouthDirectionProperty = BindableProperty.Create(
                propertyName: "MouthDirection",
                returnType: typeof(MouthDirection),
                declaringType: typeof(Balloon),
                defaultValue: MouthDirection.Right,
                defaultBindingMode: BindingMode.TwoWay,
                propertyChanged: MouthDirectionPropertyChanged
            );
        private static void MouthDirectionPropertyChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var control = (Balloon)bindable;
            control.MouthDirection = (MouthDirection)newValue;
        }

        /// <summary>
        /// 吹き出しの口の色
        /// </summary>
        public Color MouthColor { get; set; }

        #endregion

        /// <summary>
        /// 吹き出しの口の描画
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPaintSurface(SKPaintSurfaceEventArgs e)
        {
            SKImageInfo info = e.Info;
            SKSurface surface = e.Surface;
            SKCanvas canvas = surface.Canvas;

            canvas.Clear();

            SKPath path = new SKPath();
            //吹き出しの口の向きに応じて
            switch (this.MouthDirection)
            {
                case MouthDirection.Left:
                    {
                        #region From Left

                        switch (Device.RuntimePlatform)
                        {
                            case Device.iOS:
                                path.MoveTo(0, 50);
                                path.LineTo(100, 50);
                                path.LineTo(100, 135);
                                path.Close();
                                break;
                            case Device.Android:
                                path.MoveTo(0, 50);
                                path.LineTo(100, 50);
                                path.LineTo(100, 135);
                                path.Close();
                                break;
                            case Device.UWP:
                                path.MoveTo(0, 25);
                                path.LineTo(50, 25);
                                path.LineTo(50,55);
                                path.Close();
                                break;
                        }

                        #endregion

                        break;
                    }
                case MouthDirection.Right:
                    {
                        #region From Right

                        switch (Device.RuntimePlatform)
                        {
                            case Device.iOS:
                                path.MoveTo(info.Width, 50);
                                path.LineTo(info.Width-100, 50);
                                path.LineTo(info.Width-100, 135);
                                path.Close();
                                break;
                            case Device.Android:
                                path.MoveTo(info.Width, 50);
                                path.LineTo(info.Width - 100, 50);
                                path.LineTo(info.Width - 100, 135);
                                path.Close();
                                break;
                            case Device.UWP:
                                path.MoveTo(info.Width, 25);
                                path.LineTo(info.Width-50, 25);
                                path.LineTo(info.Width-50, 55);
                                path.Close();
                                break;
                        }

                        #endregion

                        break;
                    }
            }

            //色
            SKPaint fillPaint = new SKPaint
            {
                Style = SKPaintStyle.Fill,
                Color = SKColor.Parse(this.MouthColor.ToHex())
            };

            //ここで描画
            canvas.DrawPath(path, fillPaint);
        }
    }

バインド可能なプロパティの追加

BindablePropertyでバインド可能なプロパティをカスタムコントロールにつける.これはパターンなので,一つ作ったらあとは同じパターンで作っていけばいいと思う.

        public Color TextColor { get; set; }
        public static readonly BindableProperty TextColorProperty = BindableProperty.Create(
                propertyName: nameof(TextColor),
                returnType: typeof(Color),
                declaringType: typeof(Balloon),
                defaultValue: Color.Default,
                defaultBindingMode: BindingMode.TwoWay,
                propertyChanged: TextColorPropertyChanged
            );
        private static void TextColorPropertyChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var control = (Balloon)bindable;
            var color = (Color)newValue;
            control.label.TextColor = color;
        }

カスタムコントロールの作り方はこちらが参考になる,かも.

shuhelohelo.hatenablog.com

成果物

シンプルだけれど,それっぽい見た目の吹き出しコントロールを作ることができた.

これをListViewにTemplateSelectorを使っていい感じに表示すると,以下のようになる.

f:id:shuhelohelo:20191228175127p:plain