A rotary-wheel Xamarin Form Implementation using SkiaSharp 2d Library
Checking out awesomeness.
So I decided to tryout SkiaSharp for the first time and It appears there is no ready-made roulette spinner for Xamarin Forms online. A few sources I explored were dead-ends.
If you're trying to implement this already, it means you must have gone through docs.microsoft.com/en-us/xamarin/xamarin-fo.. a few times too just as I did. if you've not, then it's your chance to read-up.
How SkiaSharp works
You need to have a mental understanding of how the canvas is rendered on the screen. The basic idea is that everything wipes upon every new refresh which is made possible by this method
skiaView.InvalidateSurface();
Components of the control
- Arcs
Just like the arcs, you know from circles and their circumferences, the code basically goes to the centre of the screen and draws an arc towards the circumference.
The radius is in charge of how far this goes and all the movement is contained in a path
which has the ability to closeup. When a path is closed, it marks a region around that path to fill
is possible. This closeup becomes the sector
.
path.MoveTo(center);
path.ArcTo(rect, startAngle, sweepAngle, false);
canvas.DrawPath(path, fill);
canvas.DrawPath(path, outline);
As you can see in the code above, the canvas draws both the fill and the outline of this sector.
- Texts
The texts are generated in positions relative to the mid-angle of the sectors. This happens in a nested forloop; so every texts available is drawn at every iteration of the sectors. The angle of positioning is influenced by the central angle of the arcs so the texts are always re-created in positions relative to their arcs and in the eyes, they rotate with the arcs as one.
Each tech writing begins from the mid-x axis too and extends outward. Spaces are interpolated before the tests for the best result as they have to appear a distance away from 0,0
.
//Writing Actual texts
var test_angle = _degrees + (360 / viewModel.ChartData.Count / 2) - (360 / viewModel.ChartData.Count * 2);
float sweepAngleText = 360f / viewModel.ChartData.Count;
float startAngleText = sweepAngleText - sweepAngleText / 2;
foreach (ChartData itemer in viewModel.ChartData)
{
canvas.Save();
canvas.RotateDegrees(startAngleText + _degrees - 90, xCenter, yCenter);
if (itemer.Text.Trim().Length > 6)
textPaint.TextSize = 30;
else
textPaint.TextSize = 40;
canvas.DrawText(itemer.Text, xCenter, yText, textPaint);
canvas.Restore();
test_angle += 360 / viewModel.ChartData.Count;
if (test_angle > 360)
test_angle = test_angle - 360;
if (startAngleText > 360)
startAngleText = startAngleText - 360;
startAngleText += sweepAngleText;
}
Read more about texts from the documentation.
- Triangle
The triangular marker is there to point to the location of the winning number. I made it face the north (0 degrees) so you can make yours face anywhere or even facing down from the top like this guy's design. forums.xamarin.com/discussion/180428/spin-w..
Triangles can be created in two ways. Using the path-to-close technique (just like the arc) or using a not so straightforward approach. Note: this is not an overloading difference.
The path starts from a location (x0, y0)
and proceeds to next location (x1,y0)
and then goes up/down (up as you can see) to last location
((x1-x0)/2,y1)
//draw triangle
fillMarkTrianglePaint.Style = SKPaintStyle.StrokeAndFill;
fillMarkTrianglePaint.Color = Color.FromHex("#FFF180").ToSKColor();
SKPath trianglePath = new SKPath();
trianglePath.MoveTo((args.Info.Width / 2) - 55, args.Info.Height / 2);
trianglePath.LineTo((args.Info.Width / 2) - 55, args.Info.Height / 2);
trianglePath.LineTo((args.Info.Width / 2) + 55, args.Info.Height / 2);
trianglePath.LineTo(args.Info.Width / 2, (float)(args.Info.Height / 2.5));
trianglePath.Close();
canvas.DrawPath(trianglePath, fillMarkTrianglePaint);
- Circles
I placed 2 circles right there in the centre. One is helping the triangle while the other is the centrepiece with a beautiful gradient. Read Microsoft documentation for the gradient and also circles. Those are pretty straight forward.
Determining the winning number.
You must have guessed right. Angles. I have carefully taken care of many scenarios in terms of odd or even number of wheels. This is the reason for the Math.Round(
.
The sector positioning and the spinning are both clockwise which makes the degree of rotation become relative and inverted.
Basically, it means the first is the last, etc.
var segment = ((prize_degree / 360f) * viewModel.ChartData.Count);
var int_segment2 = Math.Round(segment, MidpointRounding.AwayFromZero);
var realIndex = viewModel.ChartData.Count == viewModel.ChartData.Count - (int)int_segment2 ? 0 : viewModel.ChartData.Count - (int)int_segment2;
viewModel.Prize = viewModel.ChartData[realIndex].Sector; //add back
priceBox.Text = viewModel.Prize?.Price.ToString();
Now, the full source code. github.com/fzany/RotaryWheel
I plan to make it a Nuget control in the coming days so folks can integrate without customizing so much.
PRs and code corrections are welcome.