Last active
October 27, 2021 09:07
-
-
Save roubachof/a70abf139fcdd55935ab74779dfbc639 to your computer and use it in GitHub Desktop.
RatingView in SkiaSharp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// **************************************************************************** | |
// Copyright (c) 2017 Clovis Nicolas, fixed by JM Alfonsi | |
// https://github.com/clovisnicolas/SkiaRate | |
// **************************************************************************** | |
// This file isn't generated, but this comment is necessary to exclude it from StyleCop analysis. | |
// <auto-generated/> | |
using System; | |
using SkiaSharp; | |
using SkiaSharp.Views.Forms; | |
using Xamarin.Forms; | |
namespace SkiaRate | |
{ | |
public static class MaterialColors | |
{ | |
public static SKColor Red => SKColor.Parse("F44336"); | |
public static SKColor Pink => SKColor.Parse("E91E63"); | |
public static SKColor Purple => SKColor.Parse("9C27B0"); | |
public static SKColor DeepPurple => SKColor.Parse("673AB7"); | |
public static SKColor Indigo => SKColor.Parse("3F51B5"); | |
public static SKColor Blue => SKColor.Parse("2196F3"); | |
public static SKColor LightBlue => SKColor.Parse("03A9F4"); | |
public static SKColor Cyan => SKColor.Parse("00BCD4"); | |
public static SKColor Teal => SKColor.Parse("009688"); | |
public static SKColor Green => SKColor.Parse("4CAF50"); | |
public static SKColor LightGreen => SKColor.Parse("8BC34A"); | |
public static SKColor Lime => SKColor.Parse("CDDC39"); | |
public static SKColor Yellow => SKColor.Parse("FFEB3B"); | |
public static SKColor Amber => SKColor.Parse("FFC107"); | |
public static SKColor Orange => SKColor.Parse("FF9800"); | |
public static SKColor DeepOrange => SKColor.Parse("FF5722"); | |
public static SKColor Brown => SKColor.Parse("795548"); | |
public static SKColor Grey => SKColor.Parse("9E9E9E"); | |
public static SKColor BlueGrey => SKColor.Parse("607D8B"); | |
public static SKColor Black => SKColor.Parse("000000"); | |
public static SKColor White => SKColor.Parse("FFFFFF"); | |
} | |
public static class PathConstants | |
{ | |
public const string Star = "M9 11.3l3.71 2.7-1.42-4.36L15 7h-4.55L9 2.5 7.55 7H3l3.71 2.64L5.29 14z"; | |
public const string MaterialStar = @"M19.65,9.04l-4.84-0.42l-1.89-4.45c-0.34-0.81-1.5-0.81-1.84,0L9.19,8.63L4.36,9.04c-0.88,0.07-1.24,1.17-0.57,1.7l3.67,3.18l-1.1,4.72c-0.2,0.86,0.73,1.54,1.49,1.08L12,17.27l4.15,2.51c0.76,0.46,1.69-0.22,1.49-1.08l-1.1-4.73l3.67-3.18C20.88,10.21,20.53,9.11,19.65,9.04zM12,15.4l-3.76,2.27l1-4.28l-3.32-2.88l4.38-0.38L12,6.1l1.71,4.04l4.38,0.38l-3.32,2.88l1,4.28L12,15.4z"; | |
public const string Heart = | |
"M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"; | |
public const string Circle = "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"; | |
public const string Bar = "M6 6h36v2H6z"; | |
public const string BatteryAlert = | |
"M15.67 4H14V2h-4v2H8.33C7.6 4 7 4.6 7 5.33v15.33C7 21.4 7.6 22 8.33 22h7.33c.74 0 1.34-.6 1.34-1.33V5.33C17 4.6 16.4 4 15.67 4zM13 18h-2v-2h2v2zm0-4h-2V9h2v5z"; | |
public const string BatteryCharging = | |
"M15.67 4H14V2h-4v2H8.33C7.6 4 7 4.6 7 5.33v15.33C7 21.4 7.6 22 8.33 22h7.33c.74 0 1.34-.6 1.34-1.33V5.33C17 4.6 16.4 4 15.67 4zM11 20v-5.5H9L13 7v5.5h2L11 20z"; | |
public const string Like = | |
"M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-1.91l-.01-.01L23 10z"; | |
public const string Dislike = | |
"M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v1.91l.01.01L1 14c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"; | |
public const string Theaters = | |
"M18 3v2h-2V3H8v2H6V3H4v18h2v-2h2v2h8v-2h2v2h2V3h-2zM8 17H6v-2h2v2zm0-4H6v-2h2v2zm0-4H6V7h2v2zm10 8h-2v-2h2v2zm0-4h-2v-2h2v2zm0-4h-2V7h2v2z"; | |
public const string Problem = "M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"; | |
} | |
public enum RatingType | |
{ | |
Full, | |
Half, | |
Floating | |
} | |
public class RatingView : SKCanvasView | |
{ | |
public static readonly BindableProperty ValueProperty = BindableProperty.Create( | |
nameof(Value), | |
typeof(double), | |
typeof(RatingView), | |
default(double), | |
propertyChanged: OnValueChanged); | |
public static readonly BindableProperty PathProperty = BindableProperty.Create( | |
nameof(Path), | |
typeof(string), | |
typeof(RatingView), | |
PathConstants.Star, | |
propertyChanged: OnPropertyChanged); | |
public static readonly BindableProperty CountProperty = BindableProperty.Create( | |
nameof(Count), | |
typeof(int), | |
typeof(RatingView), | |
5, | |
propertyChanged: OnPropertyChanged); | |
public static readonly BindableProperty ColorOnProperty = BindableProperty.Create( | |
nameof(ColorOn), | |
typeof(Color), | |
typeof(RatingView), | |
MaterialColors.Amber.ToFormsColor(), | |
propertyChanged: ColorOnChanged); | |
public static readonly BindableProperty OutlineOnColorProperty = BindableProperty.Create( | |
nameof(OutlineOnColor), | |
typeof(Color), | |
typeof(RatingView), | |
SKColors.Transparent.ToFormsColor(), | |
propertyChanged: OutlineOnColorChanged); | |
public static readonly BindableProperty OutlineOffColorProperty = BindableProperty.Create( | |
nameof(OutlineOffColor), | |
typeof(Color), | |
typeof(RatingView), | |
MaterialColors.Grey.ToFormsColor(), | |
propertyChanged: OutlineOffColorChanged); | |
public static readonly BindableProperty RatingTypeProperty = BindableProperty.Create( | |
nameof(RatingType), | |
typeof(RatingType), | |
typeof(RatingView), | |
RatingType.Floating, | |
propertyChanged: OnPropertyChanged); | |
public double Value | |
{ | |
get => (double)GetValue(ValueProperty); | |
set => SetValue(ValueProperty, ClampValue(value)); | |
} | |
public string Path | |
{ | |
get => (string)GetValue(PathProperty); | |
set => SetValue(PathProperty, value); | |
} | |
public int Count | |
{ | |
get => (int)GetValue(CountProperty); | |
set => SetValue(CountProperty, value); | |
} | |
public Color ColorOn | |
{ | |
get => (Color)GetValue(ColorOnProperty); | |
set => SetValue(ColorOnProperty, value); | |
} | |
public Color OutlineOnColor | |
{ | |
get => (Color)GetValue(OutlineOnColorProperty); | |
set => SetValue(OutlineOnColorProperty, value); | |
} | |
public Color OutlineOffColor | |
{ | |
get => (Color)GetValue(OutlineOffColorProperty); | |
set => SetValue(OutlineOffColorProperty, value); | |
} | |
public RatingType RatingType | |
{ | |
get => (RatingType)GetValue(RatingTypeProperty); | |
set => SetValue(RatingTypeProperty, value); | |
} | |
private static void OnPropertyChanged(BindableObject bindable, object oldValue, object newValue) | |
{ | |
var view = bindable as RatingView; | |
view.InvalidateSurface(); | |
} | |
private static void OnValueChanged(BindableObject bindable, object oldValue, object newValue) | |
{ | |
OnPropertyChanged(bindable, oldValue, newValue); | |
} | |
private static void ColorOnChanged(BindableObject bindable, object oldValue, object newValue) | |
{ | |
var view = bindable as RatingView; | |
view.SKColorOn = ((Color)newValue).ToSKColor(); | |
OnPropertyChanged(bindable, oldValue, newValue); | |
} | |
private static void OutlineOffColorChanged(BindableObject bindable, object oldValue, object newValue) | |
{ | |
var view = bindable as RatingView; | |
view.SKOutlineOffColor = ((Color)newValue).ToSKColor(); | |
OnPropertyChanged(bindable, oldValue, newValue); | |
} | |
private static void OutlineOnColorChanged(BindableObject bindable, object oldValue, object newValue) | |
{ | |
var view = bindable as RatingView; | |
view.SKOutlineOnColor = ((Color)newValue).ToSKColor(); | |
OnPropertyChanged(bindable, oldValue, newValue); | |
} | |
private SKPoint ConvertToPixel(Point pt) | |
{ | |
return new SKPoint((float)(CanvasSize.Width * pt.X / Width), (float)(CanvasSize.Height * pt.Y / Height)); | |
} | |
#region properties | |
/// <summary> | |
/// Gets or sets the spacing between two rating elements | |
/// </summary> | |
public float Spacing { get; set; } = 8; | |
/// <summary> | |
/// Gets or sets the color of the canvas background. | |
/// </summary> | |
/// <value>The color of the canvas background.</value> | |
public SKColor CanvasBackgroundColor { get; set; } = SKColors.Transparent; | |
/// <summary> | |
/// Gets or sets the width of the stroke. | |
/// </summary> | |
/// <value>The width of the stroke.</value> | |
public float StrokeWidth { get; set; } = 0.1f; | |
#endregion | |
#region public methods | |
/// <summary> | |
/// Clamps the value between 0 and the number of items. | |
/// </summary> | |
/// <returns>The value.</returns> | |
/// <param name="val">Value.</param> | |
public double ClampValue(double val) | |
{ | |
if (val < 0) | |
{ | |
return 0; | |
} | |
if (val > Count) | |
{ | |
return Count; | |
} | |
return val; | |
} | |
/// <summary> | |
/// Sets the Rating value | |
/// </summary> | |
/// <param name="x"></param> | |
/// <param name="y"></param> | |
public void SetValue(double x, double y) | |
{ | |
var val = CalculateValue(x); | |
switch (RatingType) | |
{ | |
case RatingType.Full: | |
Value = ClampValue(Math.Ceiling(val)); | |
break; | |
case RatingType.Half: | |
Value = ClampValue(Math.Round(val * 2) / 2); | |
break; | |
case RatingType.Floating: | |
Value = ClampValue(val); | |
break; | |
} | |
} | |
protected override void OnPaintSurface(SKPaintSurfaceEventArgs e) | |
{ | |
base.OnPaintSurface(e); | |
Draw(e.Surface.Canvas, e.Info.Width, e.Info.Height); | |
} | |
/// <summary> | |
/// Draws the rating view | |
/// </summary> | |
/// <param name="canvas"></param> | |
/// <param name="width"></param> | |
/// <param name="height"></param> | |
public void Draw(SKCanvas canvas, int width, int height) | |
{ | |
canvas.Clear(CanvasBackgroundColor); | |
var path = SKPath.ParseSvgPathData(Path); | |
var itemWidth = (width - (Count - 1) * Spacing) / Count; | |
var scaleX = itemWidth / path.Bounds.Width; | |
scaleX = (itemWidth - scaleX * StrokeWidth) / path.Bounds.Width; | |
ItemHeight = height; | |
var scaleY = ItemHeight / path.Bounds.Height; | |
scaleY = (ItemHeight - scaleY * StrokeWidth) / path.Bounds.Height; | |
CanvasScale = Math.Min(scaleX, scaleY); | |
ItemWidth = path.Bounds.Width * CanvasScale; | |
canvas.Scale(CanvasScale); | |
canvas.Translate(StrokeWidth / 2, StrokeWidth / 2); | |
canvas.Translate(-path.Bounds.Left, 0); | |
canvas.Translate(0, -path.Bounds.Top); | |
using (var strokePaint = new SKPaint | |
{ | |
Style = SKPaintStyle.Stroke, | |
Color = SKOutlineOnColor, | |
StrokeWidth = StrokeWidth, | |
StrokeJoin = SKStrokeJoin.Round, | |
IsAntialias = true, | |
}) | |
using (var fillPaint = new SKPaint { Style = SKPaintStyle.Fill, Color = SKColorOn, IsAntialias = true, }) | |
{ | |
for (var i = 0; i < Count; i++) | |
{ | |
if (i <= Value - 1) // Full | |
{ | |
canvas.DrawPath(path, fillPaint); | |
canvas.DrawPath(path, strokePaint); | |
} | |
else if (i < Value) //Partial | |
{ | |
var filledPercentage = (float)(Value - Math.Truncate(Value)); | |
strokePaint.Color = SKOutlineOffColor; | |
canvas.DrawPath(path, strokePaint); | |
using (var rectPath = new SKPath()) | |
{ | |
var rect = SKRect.Create( | |
path.Bounds.Left + path.Bounds.Width * filledPercentage, | |
path.Bounds.Top, | |
path.Bounds.Width * (1 - filledPercentage), | |
ItemHeight); | |
rectPath.AddRect(rect); | |
canvas.ClipPath(rectPath, SKClipOperation.Difference); | |
canvas.DrawPath(path, fillPaint); | |
} | |
} | |
else //Empty | |
{ | |
strokePaint.Color = SKOutlineOffColor; | |
canvas.DrawPath(path, strokePaint); | |
} | |
canvas.Translate((ItemWidth + Spacing) / CanvasScale, 0); | |
} | |
} | |
} | |
#endregion | |
#region private | |
private float ItemWidth { get; set; } | |
private float ItemHeight { get; set; } | |
private float CanvasScale { get; set; } | |
private SKColor SKColorOn { get; set; } = MaterialColors.Amber; | |
private SKColor SKOutlineOnColor { get; set; } = SKColors.Transparent; | |
private SKColor SKOutlineOffColor { get; set; } = MaterialColors.Grey; | |
private double CalculateValue(double x) | |
{ | |
if (x < ItemWidth) | |
{ | |
return x / ItemWidth; | |
} | |
if (x < ItemWidth + Spacing) | |
{ | |
return 1; | |
} | |
return 1 + CalculateValue(x - (ItemWidth + Spacing)); | |
} | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment