locked
Help needed making a custom frame control with SkiaSharp RRS feed

  • Question

  • User292172 posted

    I want to make a new frame control, that is just like the Xamarin Forms Frame control. I want to use SkiaSharp Canvas as the background so I can draw different things in the background of the frame.

    To start, I just want to replicate the default Frame class but allow a gradient fill. The problem I am having is how to implement the "Frame" part of it, meaning, how to add the content view. I have the gradient working beautifully.

    In xaml, I would use it like this:

        <custom:GradientFrame  VerticalOptions="FillAndExpand"
                            HorizontalOptions="FillAndExpand"
                            OuterBackgroundColor="Transparent"
                            InnerBackgroundColorStart="DarkBlue"
                            InnerBackgroundColorEnd="LightBlue"
                            BorderColor="Black"
                            BorderWidth="2"
                            BorderRadius="25" 
                            FillOrientation="Vertical">
    
            <StackLayout Orientation="Vertical">
    
                <Label Text="Sample Text 1" />
    
                <Label Text="Sample Text 2" />
    
            </StackLayout>
    
        </custom:GradientFrame >
    

    Since SkiaSharp is cross platform, I shouldn't need to write a custom renderer. I can use a SKCanvasView to draw my background frame, but not sure how to add the child view, i.e. the frame content.

    Can someone point me in the right direction?

    Here's my control so far ...

    public class GradientFrame : SKCanvasView
    {
        #region Outer
        public static readonly BindableProperty OuterBackgroundColorProperty =
            BindableProperty.Create("OuterBackgroundColor", 
                typeof(Color), 
                typeof(GradientFrame), 
                Color.Green, 
                propertyChanged: (currentControl, oldValue, newValue) =>
                {
                    var thisControl = currentControl as GradientFrame;
                    thisControl.OuterBackgroundColor = (Color)newValue;
                });
    
        public Color OuterBackgroundColor
        {
            get { return (Color)GetValue(OuterBackgroundColorProperty); }
            set
            {
                SetValue(OuterBackgroundColorProperty, value);
                thisOuterBackgroundColor = value.ToSKColor();
                InvalidateSurface();
            }
        }
    
        private SKColor thisOuterBackgroundColor;
        #endregion
    
        #region Inner Start
        public static readonly BindableProperty InnerBackgroundColorStartProperty =
            BindableProperty.Create("InnerBackgroundColorStart", 
                typeof(Color), 
                typeof(GradientFrame),
                Color.Red,
                propertyChanged: (currentControl, oldValue, newValue) =>
                {
                    var thisControl = currentControl as GradientFrame;
                    thisControl.InnerBackgroundColorStart = (Color)newValue;
                });
    
        public Color InnerBackgroundColorStart
        {
            get { return (Color)GetValue(InnerBackgroundColorStartProperty); }
            set
            {
                SetValue(InnerBackgroundColorStartProperty, value);
                thisInnerBackgroundColorStart = value.ToSKColor();
                InvalidateSurface();
            }
        }
    
        private SKColor thisInnerBackgroundColorStart;
        #endregion
    
        #region Inner End
        public static readonly BindableProperty InnerBackgroundColorEndProperty =
            BindableProperty.Create("InnerBackgroundColorEnd",
                typeof(Color),
                typeof(GradientFrame),
                Color.Red,
                propertyChanged: (currentControl, oldValue, newValue) =>
                {
                    var thisControl = currentControl as GradientFrame;
                    thisControl.InnerBackgroundColorEnd = (Color)newValue;
                });
    
        public Color InnerBackgroundColorEnd
        {
            get { return (Color)GetValue(InnerBackgroundColorEndProperty); }
            set
            {
                SetValue(InnerBackgroundColorEndProperty, value);
                thisInnerBackgroundColorEnd = value.ToSKColor();
                InvalidateSurface();
            }
        }
    
        private SKColor thisInnerBackgroundColorEnd;
        #endregion
    
        #region Border Color
        public static readonly BindableProperty BorderColorProperty =
            BindableProperty.Create("BorderColor",
                typeof(Color),
                typeof(GradientFrame),
                Color.Blue,
                propertyChanged: (currentControl, oldValue, newValue) =>
                {
                    var thisControl = currentControl as GradientFrame;
                    thisControl.BorderColor = (Color)newValue;
                });
    
        public Color BorderColor
        {
            get { return (Color)GetValue(BorderColorProperty); }
            set
            {
                SetValue(BorderColorProperty, value);
                thisBorderColor = value.ToSKColor();
                InvalidateSurface();
            }
        }
    
        private SKColor thisBorderColor;
        #endregion
    
        #region Border Width
        public static readonly BindableProperty BorderWidthProperty =
            BindableProperty.Create("BorderWidth",
                typeof(int),
                typeof(GradientFrame),
                1,
                propertyChanged: (currentControl, oldValue, newValue) =>
                {
                    var thisControl = currentControl as GradientFrame;
                    thisControl.BorderWidth = (int)newValue;
                });
    
        public int BorderWidth
        {
            get { return (int)GetValue(BorderWidthProperty); }
            set
            {
                SetValue(BorderWidthProperty, value);
                InvalidateSurface();
            }
        }
        #endregion
    
        #region BorderRadius
        public static readonly BindableProperty BorderRadiusProperty =
            BindableProperty.Create("BorderRadius",
                typeof(float),
                typeof(GradientFrame),
                25f,
                propertyChanged: (currentControl, oldValue, newValue) =>
                {
                    var thisControl = currentControl as GradientFrame;
                    thisControl.BorderRadius = (float)newValue;
                });
    
        public float BorderRadius
        {
            get { return (float)GetValue(BorderRadiusProperty); }
            set
            {
                SetValue(BorderRadiusProperty, value);
                InvalidateSurface();
            }
        }
        #endregion
    
        #region Fill Orientation
        public enum FillOrientations
        {
            Horizontal,
            Vertical
        }
    
        public static readonly BindableProperty FillOrientationProperty =
            BindableProperty.Create("FillOrientation",
                typeof(FillOrientations),
                typeof(GradientFrame),
                FillOrientations.Horizontal,
                propertyChanged: (currentControl, oldValue, newValue) =>
                {
                    var thisControl = currentControl as GradientFrame;
                    thisControl.FillOrientation = (FillOrientations)newValue;
                });
    
        public FillOrientations FillOrientation
        {
            get { return (FillOrientations)GetValue(FillOrientationProperty); }
            set
            {
                SetValue(FillOrientationProperty, value);
                InvalidateSurface();
            }
        }
        #endregion
    
        public GradientFrame()
        {
        }
    
        protected override void OnPaintSurface(SKPaintSurfaceEventArgs e)
        {
            base.OnPaintSurface(e);
    
            SKImageInfo info = e.Info;
            SKSurface surface = e.Surface;
            SKCanvas canvas = surface.Canvas;
    
            canvas.Clear(thisOuterBackgroundColor);
    
            int width = info.Width;
            int height = info.Height;
    
            SKRect rect = new SKRect
            {
                Left = 1 + BorderWidth,
                Top = 1 + BorderWidth,
                Right = width - 1 - BorderWidth,
                Bottom = height - 1 - BorderWidth
            };
    
            SKPaint paintBorder = new SKPaint
            {
                Style = SKPaintStyle.Stroke,
                StrokeWidth = 3,
                Color = thisBorderColor,
                IsAntialias = true
            };
            canvas.DrawRoundRect(rect, BorderRadius, BorderRadius, paintBorder);
    
            var colors = new SKColor[] { thisInnerBackgroundColorStart, thisInnerBackgroundColorEnd };
            SKShader shader = null;
            if (FillOrientation.Equals(FillOrientations.Vertical))
            {
                shader = SKShader.CreateLinearGradient(
                    new SKPoint(0, 0),
                    new SKPoint(0, 100),
                    colors,
                    null,
                    SKShaderTileMode.Clamp);
            }
            else
            {
                shader = SKShader.CreateLinearGradient(
                    new SKPoint(0, 0),
                    new SKPoint(100, 0),
                    colors,
                    null,
                    SKShaderTileMode.Clamp);
            }
    
            var paintFill = new SKPaint()
            { 
                Shader = shader,
                Style = SKPaintStyle.Fill,
                IsAntialias = true
            };
    
            canvas.DrawRoundRect(rect, BorderRadius, BorderRadius, paintFill);
        }       
    
    }
    
    Saturday, September 30, 2017 12:44 AM

All replies

  • User292172 posted

    For anyone who is interested, here is my control. No custom renderers needed, just include the SkiaSharp nuget, and then use this for a nice frame!

    public class GradientFrame : Grid
    {
        #region Outer
        public static readonly BindableProperty OuterBackgroundColorProperty =
            BindableProperty.Create("OuterBackgroundColor",
                typeof(Color),
                typeof(GradientFrame),
                Color.Transparent,
                propertyChanged: (currentControl, oldValue, newValue) =>
                {
                    var thisControl = currentControl as GradientFrame;
                    thisControl.OuterBackgroundColor = (Color)newValue;
                });
    
        public Color OuterBackgroundColor
        {
            get { return (Color)GetValue(OuterBackgroundColorProperty); }
            set
            {
                SetValue(OuterBackgroundColorProperty, value);
                thisOuterBackgroundColor = value.ToSKColor();
                InvalidateSurface();
            }
        }
    
        private SKColor thisOuterBackgroundColor;
        #endregion
    
        #region Inner Start
        public static readonly BindableProperty InnerBackgroundColorStartProperty =
            BindableProperty.Create("InnerBackgroundColorStart",
                typeof(Color),
                typeof(GradientFrame),
                Color.Red,
                propertyChanged: (currentControl, oldValue, newValue) =>
                {
                    var thisControl = currentControl as GradientFrame;
                    thisControl.InnerBackgroundColorStart = (Color)newValue;
                });
    
        public Color InnerBackgroundColorStart
        {
            get { return (Color)GetValue(InnerBackgroundColorStartProperty); }
            set
            {
                SetValue(InnerBackgroundColorStartProperty, value);
                thisInnerBackgroundColorStart = value.ToSKColor();
                InvalidateSurface();
            }
        }
    
        private SKColor thisInnerBackgroundColorStart;
        #endregion
    
        #region Inner End
        public static readonly BindableProperty InnerBackgroundColorEndProperty =
            BindableProperty.Create("InnerBackgroundColorEnd",
                typeof(Color),
                typeof(GradientFrame),
                Color.Red,
                propertyChanged: (currentControl, oldValue, newValue) =>
                {
                    var thisControl = currentControl as GradientFrame;
                    thisControl.InnerBackgroundColorEnd = (Color)newValue;
                });
    
        public Color InnerBackgroundColorEnd
        {
            get { return (Color)GetValue(InnerBackgroundColorEndProperty); }
            set
            {
                SetValue(InnerBackgroundColorEndProperty, value);
                thisInnerBackgroundColorEnd = value.ToSKColor();
                InvalidateSurface();
            }
        }
    
        private SKColor thisInnerBackgroundColorEnd;
        #endregion
    
        #region Border Color
        public static readonly BindableProperty BorderColorProperty =
            BindableProperty.Create("BorderColor",
                typeof(Color),
                typeof(GradientFrame),
                Color.Blue,
                propertyChanged: (currentControl, oldValue, newValue) =>
                {
                    var thisControl = currentControl as GradientFrame;
                    thisControl.BorderColor = (Color)newValue;
                });
    
        public Color BorderColor
        {
            get { return (Color)GetValue(BorderColorProperty); }
            set
            {
                SetValue(BorderColorProperty, value);
                thisBorderColor = value.ToSKColor();
                InvalidateSurface();
            }
        }
    
        private SKColor thisBorderColor;
        #endregion
    
        #region Border Width
        public static readonly BindableProperty BorderWidthProperty =
            BindableProperty.Create("BorderWidth",
                typeof(int),
                typeof(GradientFrame),
                1,
                propertyChanged: (currentControl, oldValue, newValue) =>
                {
                    var thisControl = currentControl as GradientFrame;
                    thisControl.BorderWidth = (int)newValue;
                });
    
        public int BorderWidth
        {
            get { return (int)GetValue(BorderWidthProperty); }
            set
            {
                SetValue(BorderWidthProperty, value);
                InvalidateSurface();
            }
        }
        #endregion
    
        #region Border Radius
        public static readonly BindableProperty BorderRadiusProperty =
            BindableProperty.Create("BorderRadius",
                typeof(float),
                typeof(GradientFrame),
                25f,
                propertyChanged: (currentControl, oldValue, newValue) =>
                {
                    var thisControl = currentControl as GradientFrame;
                    thisControl.BorderRadius = (float)newValue;
                });
    
        public float BorderRadius
        {
            get { return (float)GetValue(BorderRadiusProperty); }
            set
            {
                SetValue(BorderRadiusProperty, value);
                InvalidateSurface();
            }
        }
        #endregion
    
        #region Border Margin
        public static readonly BindableProperty BorderMarginProperty =
            BindableProperty.Create("BorderMargin",
                typeof(float),
                typeof(GradientFrame),
                25f,
                propertyChanged: (currentControl, oldValue, newValue) =>
                {
                    var thisControl = currentControl as GradientFrame;
                    thisControl.BorderMargin = (float)newValue;
                });
    
        public float BorderMargin
        {
            get { return (float)GetValue(BorderMarginProperty); }
            set
            {
                SetValue(BorderMarginProperty, value);
                InvalidateSurface();
            }
        }
        #endregion
    
        #region Border Padding
        public static readonly BindableProperty BorderPaddingProperty =
            BindableProperty.Create("BorderPadding",
                typeof(float),
                typeof(GradientFrame),
                25f,
                propertyChanged: (currentControl, oldValue, newValue) =>
                {
                    var thisControl = currentControl as GradientFrame;
                    thisControl.BorderPadding = (float)newValue;
                });
    
        public float BorderPadding
        {
            get { return (float)GetValue(BorderPaddingProperty); }
            set
            {
                SetValue(BorderPaddingProperty, value);
                InvalidateSurface();
            }
        }
        #endregion
    
        #region Fill Orientation
        public enum FillOrientations
        {
            Horizontal,
            Vertical
        }
    
        public static readonly BindableProperty FillOrientationProperty =
            BindableProperty.Create("FillOrientation",
                typeof(FillOrientations),
                typeof(GradientFrame),
                FillOrientations.Horizontal,
                propertyChanged: (currentControl, oldValue, newValue) =>
                {
                    var thisControl = currentControl as GradientFrame;
                    thisControl.FillOrientation = (FillOrientations)newValue;
                });
    
        public FillOrientations FillOrientation
        {
            get { return (FillOrientations)GetValue(FillOrientationProperty); }
            set
            {
                SetValue(FillOrientationProperty, value);
                InvalidateSurface();
            }
        }
        #endregion
    
        public GradientFrame()
        {
            this.RowDefinitions = new RowDefinitionCollection
            {
                new RowDefinition { Height = GridLength.Auto }
            };
            this.ColumnDefinitions = new ColumnDefinitionCollection
            {
                new ColumnDefinition { Width = GridLength.Auto }
            };
    
    
            canvas = new SKCanvasView()
            {
                HorizontalOptions = LayoutOptions.FillAndExpand,
                VerticalOptions = LayoutOptions.FillAndExpand,
    
            };
            canvas.PaintSurface += NewGradientFrame_OnPaintSurface;
            this.Children.Add(canvas,0,0);
        }
    
        protected override void OnChildAdded(Element child)
        {
            if (this.Children.Count > 1)
            {
                SetRow(child, 0);
                SetColumn(child, 0);
                SetRowSpan(child, 1);
                SetColumnSpan(child, 1);
                ((View)child).Margin = BorderMargin + BorderWidth + BorderPadding;
            }
            base.OnChildAdded(child);
        }
    
        private SKCanvasView canvas;
    
        private void InvalidateSurface()
        {
            if (canvas != null) canvas.InvalidateSurface();
        }
    
        protected void NewGradientFrame_OnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
        {
            SKImageInfo info = e.Info;
            SKSurface surface = e.Surface;
            SKCanvas canvas = surface.Canvas;
    
            canvas.Clear(thisOuterBackgroundColor);
    
            int width = info.Width;
            int height = info.Height;
    
            SKRect rect = new SKRect
            {
                Left = BorderMargin,
                Top = BorderMargin,
                Right = width - BorderMargin,
                Bottom = height - BorderMargin
            };
    
            SKPaint paintBorder = new SKPaint
            {
                Style = SKPaintStyle.Stroke,
                StrokeWidth = BorderWidth,
                Color = thisBorderColor,
                IsAntialias = true
            };
            canvas.DrawRoundRect(rect, BorderRadius, BorderRadius, paintBorder);
    
            var colors = new SKColor[] { thisInnerBackgroundColorStart, thisInnerBackgroundColorEnd };
            SKShader shader = null;
            if (FillOrientation.Equals(FillOrientations.Vertical))
            {
                shader = SKShader.CreateLinearGradient(
                    new SKPoint(0, 0),
                    new SKPoint(0, 100),
                    colors,
                    null,
                    SKShaderTileMode.Clamp);
            }
            else
            {
                shader = SKShader.CreateLinearGradient(
                    new SKPoint(0, 0),
                    new SKPoint(100, 0),
                    colors,
                    null,
                    SKShaderTileMode.Clamp);
            }
    
            var paintFill = new SKPaint()
            {
                Shader = shader,
                Style = SKPaintStyle.Fill,
                IsAntialias = true
            };
    
            canvas.DrawRoundRect(rect, BorderRadius, BorderRadius, paintFill);
        }
    }
    
    Saturday, September 30, 2017 4:22 AM
  • User381350 posted

    SKCanvasView isn't available in .Net standard project, need help please.

    I am looking for similar control.

    Wednesday, January 9, 2019 6:50 PM
  • User76049 posted

    @Viswa_007 said: SKCanvasView isn't available in .Net standard project, need help please.

    I am looking for similar control.

    Yes it is. Ensure you have the nuget package correctly installed in your platform specific projects & you .NETStandard/PCL library. We use SkiaSharp with .NETStandard for gradients.

    Wednesday, January 9, 2019 7:40 PM
  • User381350 posted

    @NMackay It worked, had an issue with nuget pakages.

    Thanks.

    Tuesday, January 15, 2019 3:40 PM
  • User388072 posted

    ?

    Wednesday, August 7, 2019 12:41 PM