使用 Xamarin Forms Shapes 的椭圆形进度条
Oval progress bar using Xamarin Forms Shapes
我想使用 Xamarin Forms 绘制椭圆形进度条,如下所示:
我发现 5 岁 ProgressRingPlugin 似乎按预期工作。但是,我试图避免在我的移动应用程序中添加其他包,并且 Visual Studio 显示有关此插件的警告(它与项目不完全兼容)。
请告知如何使用标准 Xamarin Forms 5 绘制此类控件。我相信使用 Arc shape 需要它。很高兴获得有关如何使用 XF 在图像上创建控件的解决方案。
如果ProgressRingPlugin
与XF 5.0不兼容,可以参考这个repo:
https://github.com/jsuarezruiz/TemplateUI#circleprogressbar
在尝试了几种解决方案后,我决定自己实现CircleProgressBar控件。
CircleProgressBar.xaml
<?xml version="1.0" encoding="UTF-8" ?>
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="YourNamespace.Controls.CircleProgressBar">
<Grid >
<Path x:Name="grayPath" Stroke="LightGray"
StrokeThickness="4" HorizontalOptions="Start" VerticalOptions="Start">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure x:Name="grayPathFigure">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment x:Name="grayArcSegment" SweepDirection="Clockwise" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
<Path x:Name="pathRoot" Stroke="Green"
StrokeThickness="4" HorizontalOptions="Start" VerticalOptions="Start">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure x:Name="pathFigure">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment x:Name="arcSegment" SweepDirection="Clockwise" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
</ContentView>
CircleProgressBar.cs
using System;
using System.Collections.Generic;
using Xamarin.Forms;
using Xamarin.Forms.Shapes;
namespace YourNamespace.Controls
{
public partial class CircleProgressBar : ContentView
{
public CircleProgressBar()
{
InitializeComponent();
Angle = (Percentage * 360) / 100;
RenderArc();
}
public int Radius
{
get { return (int)GetValue(RadiusProperty); }
set { SetValue(RadiusProperty, value); }
}
public Brush SegmentColor
{
get { return (Brush)GetValue(SegmentColorProperty); }
set { SetValue(SegmentColorProperty, value); }
}
public int StrokeThickness
{
get { return (int)GetValue(StrokeThicknessProperty); }
set { SetValue(StrokeThicknessProperty, value); }
}
public double Percentage
{
get { return (double)GetValue(PercentageProperty); }
set { SetValue(PercentageProperty, value); }
}
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
// Using a BindableProperty as the backing store for Percentage. This enables animation, styling, binding, etc...
public static readonly BindableProperty PercentageProperty =
BindableProperty.Create("Percentage", typeof(double), typeof(CircleProgressBar),
65d, propertyChanged: OnPercentageChanged);
// Using a BindableProperty as the backing store for StrokeThickness. This enables animation, styling, binding, etc...
public static readonly BindableProperty StrokeThicknessProperty =
BindableProperty.Create("StrokeThickness", typeof(int), typeof(CircleProgressBar),
5, propertyChanged: OnThicknessChanged);
// Using a BindableProperty as the backing store for SegmentColor. This enables animation, styling, binding, etc...
public static readonly BindableProperty SegmentColorProperty =
BindableProperty.Create("SegmentColor", typeof(Brush), typeof(CircleProgressBar),
new SolidColorBrush(Color.Red), propertyChanged: OnColorChanged);
// Using a BindableProperty as the backing store for Radius. This enables animation, styling, binding, etc...
public static readonly BindableProperty RadiusProperty =
BindableProperty.Create("Radius", typeof(int), typeof(CircleProgressBar),
25, propertyChanged: OnPropertyChanged);
// Using a BindableProperty as the backing store for Angle. This enables animation, styling, binding, etc...
public static readonly BindableProperty AngleProperty =
BindableProperty.Create("Angle", typeof(double), typeof(CircleProgressBar),
120d, propertyChanged: OnPropertyChanged);
private static void OnColorChanged(BindableObject sender, object old, object newValue)
{
CircleProgressBar circle = sender as CircleProgressBar;
circle.SetColor((SolidColorBrush)newValue);
}
private static void OnThicknessChanged(BindableObject sender, object old, object newValue)
{
CircleProgressBar circle = sender as CircleProgressBar;
circle.SetThickness((int)newValue);
}
private static void OnPercentageChanged(BindableObject sender, object old, object newValue)
{
CircleProgressBar circle = sender as CircleProgressBar;
if (circle.Percentage > 100) circle.Percentage = 100;
circle.Angle = (circle.Percentage * 360) / 100;
}
private static void OnPropertyChanged(BindableObject sender, object old, object newValue)
{
CircleProgressBar circle = sender as CircleProgressBar;
circle.RenderArc();
}
public void SetThickness(int n)
{
pathRoot.StrokeThickness = n;
}
public void SetColor(SolidColorBrush n)
{
pathRoot.Stroke = n;
}
public void RenderSpecificArc(Path pathRoot, PathFigure pathFigure, ArcSegment arcSegment, double angle)
{
Point startPoint = new Point(Radius, 0);
Point endPoint = ComputeCartesianCoordinate(angle, Radius);
endPoint.X += Radius;
endPoint.Y += Radius;
pathRoot.WidthRequest = Radius * 2 + StrokeThickness;
pathRoot.HeightRequest = Radius * 2 + StrokeThickness;
pathRoot.Margin = new Thickness(StrokeThickness, StrokeThickness, 0, 0);
bool largeArc = Angle > 180.0;
Size outerArcSize = new Size(Radius, Radius);
pathFigure.StartPoint = startPoint;
if (startPoint.X == Math.Round(endPoint.X) && startPoint.Y == Math.Round(endPoint.Y))
endPoint.X -= 0.01;
arcSegment.Point = endPoint;
arcSegment.Size = outerArcSize;
arcSegment.IsLargeArc = largeArc;
}
public void RenderArc()
{
RenderSpecificArc(this.grayPath, this.grayPathFigure, this.grayArcSegment, angle: 360);
RenderSpecificArc(this.pathRoot, this.pathFigure, this.arcSegment, this.Angle);
}
private Point ComputeCartesianCoordinate(double angle, double radius)
{
// convert to radians
double angleRad = (Math.PI / 180.0) * (angle - 90);
double x = radius * Math.Cos(angleRad);
double y = radius * Math.Sin(angleRad);
return new Point(x, y);
}
}
}
您还可以为描边颜色、厚度等添加可绑定属性。
用法:
<Grid>
<controls:CircleProgressBar WidthRequest="100" HeightRequest="100" Percentage="65"
<!-- adjust margin of label depending of Circle size-->
<Label Text="65%" FontSize="14" Margin="18,20,0,0"/>
</Grid>
我想使用 Xamarin Forms 绘制椭圆形进度条,如下所示:
我发现 5 岁 ProgressRingPlugin 似乎按预期工作。但是,我试图避免在我的移动应用程序中添加其他包,并且 Visual Studio 显示有关此插件的警告(它与项目不完全兼容)。
请告知如何使用标准 Xamarin Forms 5 绘制此类控件。我相信使用 Arc shape 需要它。很高兴获得有关如何使用 XF 在图像上创建控件的解决方案。
如果ProgressRingPlugin
与XF 5.0不兼容,可以参考这个repo:
https://github.com/jsuarezruiz/TemplateUI#circleprogressbar
在尝试了几种解决方案后,我决定自己实现CircleProgressBar控件。
CircleProgressBar.xaml
<?xml version="1.0" encoding="UTF-8" ?>
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="YourNamespace.Controls.CircleProgressBar">
<Grid >
<Path x:Name="grayPath" Stroke="LightGray"
StrokeThickness="4" HorizontalOptions="Start" VerticalOptions="Start">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure x:Name="grayPathFigure">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment x:Name="grayArcSegment" SweepDirection="Clockwise" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
<Path x:Name="pathRoot" Stroke="Green"
StrokeThickness="4" HorizontalOptions="Start" VerticalOptions="Start">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure x:Name="pathFigure">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment x:Name="arcSegment" SweepDirection="Clockwise" />
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
</ContentView>
CircleProgressBar.cs
using System;
using System.Collections.Generic;
using Xamarin.Forms;
using Xamarin.Forms.Shapes;
namespace YourNamespace.Controls
{
public partial class CircleProgressBar : ContentView
{
public CircleProgressBar()
{
InitializeComponent();
Angle = (Percentage * 360) / 100;
RenderArc();
}
public int Radius
{
get { return (int)GetValue(RadiusProperty); }
set { SetValue(RadiusProperty, value); }
}
public Brush SegmentColor
{
get { return (Brush)GetValue(SegmentColorProperty); }
set { SetValue(SegmentColorProperty, value); }
}
public int StrokeThickness
{
get { return (int)GetValue(StrokeThicknessProperty); }
set { SetValue(StrokeThicknessProperty, value); }
}
public double Percentage
{
get { return (double)GetValue(PercentageProperty); }
set { SetValue(PercentageProperty, value); }
}
public double Angle
{
get { return (double)GetValue(AngleProperty); }
set { SetValue(AngleProperty, value); }
}
// Using a BindableProperty as the backing store for Percentage. This enables animation, styling, binding, etc...
public static readonly BindableProperty PercentageProperty =
BindableProperty.Create("Percentage", typeof(double), typeof(CircleProgressBar),
65d, propertyChanged: OnPercentageChanged);
// Using a BindableProperty as the backing store for StrokeThickness. This enables animation, styling, binding, etc...
public static readonly BindableProperty StrokeThicknessProperty =
BindableProperty.Create("StrokeThickness", typeof(int), typeof(CircleProgressBar),
5, propertyChanged: OnThicknessChanged);
// Using a BindableProperty as the backing store for SegmentColor. This enables animation, styling, binding, etc...
public static readonly BindableProperty SegmentColorProperty =
BindableProperty.Create("SegmentColor", typeof(Brush), typeof(CircleProgressBar),
new SolidColorBrush(Color.Red), propertyChanged: OnColorChanged);
// Using a BindableProperty as the backing store for Radius. This enables animation, styling, binding, etc...
public static readonly BindableProperty RadiusProperty =
BindableProperty.Create("Radius", typeof(int), typeof(CircleProgressBar),
25, propertyChanged: OnPropertyChanged);
// Using a BindableProperty as the backing store for Angle. This enables animation, styling, binding, etc...
public static readonly BindableProperty AngleProperty =
BindableProperty.Create("Angle", typeof(double), typeof(CircleProgressBar),
120d, propertyChanged: OnPropertyChanged);
private static void OnColorChanged(BindableObject sender, object old, object newValue)
{
CircleProgressBar circle = sender as CircleProgressBar;
circle.SetColor((SolidColorBrush)newValue);
}
private static void OnThicknessChanged(BindableObject sender, object old, object newValue)
{
CircleProgressBar circle = sender as CircleProgressBar;
circle.SetThickness((int)newValue);
}
private static void OnPercentageChanged(BindableObject sender, object old, object newValue)
{
CircleProgressBar circle = sender as CircleProgressBar;
if (circle.Percentage > 100) circle.Percentage = 100;
circle.Angle = (circle.Percentage * 360) / 100;
}
private static void OnPropertyChanged(BindableObject sender, object old, object newValue)
{
CircleProgressBar circle = sender as CircleProgressBar;
circle.RenderArc();
}
public void SetThickness(int n)
{
pathRoot.StrokeThickness = n;
}
public void SetColor(SolidColorBrush n)
{
pathRoot.Stroke = n;
}
public void RenderSpecificArc(Path pathRoot, PathFigure pathFigure, ArcSegment arcSegment, double angle)
{
Point startPoint = new Point(Radius, 0);
Point endPoint = ComputeCartesianCoordinate(angle, Radius);
endPoint.X += Radius;
endPoint.Y += Radius;
pathRoot.WidthRequest = Radius * 2 + StrokeThickness;
pathRoot.HeightRequest = Radius * 2 + StrokeThickness;
pathRoot.Margin = new Thickness(StrokeThickness, StrokeThickness, 0, 0);
bool largeArc = Angle > 180.0;
Size outerArcSize = new Size(Radius, Radius);
pathFigure.StartPoint = startPoint;
if (startPoint.X == Math.Round(endPoint.X) && startPoint.Y == Math.Round(endPoint.Y))
endPoint.X -= 0.01;
arcSegment.Point = endPoint;
arcSegment.Size = outerArcSize;
arcSegment.IsLargeArc = largeArc;
}
public void RenderArc()
{
RenderSpecificArc(this.grayPath, this.grayPathFigure, this.grayArcSegment, angle: 360);
RenderSpecificArc(this.pathRoot, this.pathFigure, this.arcSegment, this.Angle);
}
private Point ComputeCartesianCoordinate(double angle, double radius)
{
// convert to radians
double angleRad = (Math.PI / 180.0) * (angle - 90);
double x = radius * Math.Cos(angleRad);
double y = radius * Math.Sin(angleRad);
return new Point(x, y);
}
}
}
您还可以为描边颜色、厚度等添加可绑定属性。
用法:
<Grid>
<controls:CircleProgressBar WidthRequest="100" HeightRequest="100" Percentage="65"
<!-- adjust margin of label depending of Circle size-->
<Label Text="65%" FontSize="14" Margin="18,20,0,0"/>
</Grid>