使用 Xamarin 的 CALayer 自定义属性动画
Posted
技术标签:
【中文标题】使用 Xamarin 的 CALayer 自定义属性动画【英文标题】:CALayer Custom Property Animation with Xamarin 【发布时间】:2016-10-02 09:16:05 【问题描述】:我真的很沮丧,因为自上周以来,我一直在尝试使用 CoreAnimation 在 ios 上为饼图(带有透明孔的弧段)制作动画。
目前,我正在使用带有路径属性的 CAShapeLayer 绘制 ArcSegment。它看起来很棒,但我无法为这个属性设置动画。 我想使用 CABasicAnimation 为半径、段等图层属性设置动画。
这里有没有人可以告诉我如何解决这个问题? 谢谢。
问候 罗尼
public class ArcSegmentLayer : CAShapeLayer
private const string StartAngleProperty = "StartAngle";
private const string EndAngleProperty = "EndAngle";
public static void RegisterProperties()
ObjCProperties.RegisterDynamicProperty(typeof(ArcSegmentLayer), StartAngleProperty, typeof(float));
ObjCProperties.RegisterDynamicProperty(typeof(ArcSegmentLayer), EndAngleProperty, typeof(float));
public ArcSegmentLayer()
[Export("initWithLayer:")]
public ArcSegmentLayer(ArcSegmentLayer layer)
this.LineWidth = layer.LineWidth;
this.Frame = layer.Frame;
this.FillColor = layer.FillColor;
this.StrokeColor = layer.StrokeColor;
this.Segments = layer.Segments;
this.Margin = layer.Margin;
#region Properties
public float StartAngle
get return ObjCProperties.GetFloatProperty(Handle, StartAngleProperty);
set
ObjCProperties.SetFloatProperty(Handle, StartAngleProperty, value);
public float EndAngle
get return ObjCProperties.GetFloatProperty(Handle, EndAngleProperty);
set
ObjCProperties.SetFloatProperty(Handle, EndAngleProperty, value);
public nint Segments
get return segments;
set
if (segments != value)
segments = value;
this.SetNeedsDisplay();
public nfloat Margin
get
return margin;
set
if (margin != value)
margin = value;
this.SetNeedsDisplay();
#endregion
[Export("needsDisplayForKey:")]
public static bool NeedsDisplayForKey(NSString key)
return key == StartAngleProperty
|| key == EndAngleProperty
|| key == "Margin"
|| key == "Segments"
|| key == "LineWidth"
|| key == "StrokeColor"
|| CALayer.NeedsDisplayForKey(key);
[Export("display")]
public override void Display()
base.Display();
Console.WriteLine(this.EndAngle);
this.Path = CreateSegments().CGPath;
[Export("actionForKey:")]
public override NSObject ActionForKey(string eventKey)
/*
if (eventKey == EndAngleProperty)
CABasicAnimation animation = CABasicAnimation.FromKeyPath(eventKey);
animation.TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.Linear);
animation.From = new NSNumber(this.EndAngle); //PresentationLayer.ValueForKey(new NSString(eventKey));
//animation.Duration = CATransition. 1;
animation.Duration = 0;
return animation;
else if (eventKey == StartAngleProperty)
CABasicAnimation animation = CABasicAnimation.FromKeyPath(eventKey);
animation.TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.Linear);
animation.From = new NSNumber(this.StartAngle);
animation.Duration = 0;
return animation;
*/
return base.ActionForKey(eventKey);
private UIBezierPath CreateSegments()
var path = new UIBezierPath();
nfloat segmentSize = (nfloat)(360.0 / (nfloat)this.Segments);
nfloat startSegAngle = 0;
nfloat endSegAngle = startSegAngle + segmentSize;
if (this.Segments > 1)
var fromSeg = (nint)((((double)this.Segments) * this.StartAngle) / 360.0);
var toSeg = (nint)((((double)this.Segments) * this.EndAngle) / 360.0);
for (var seg = 0; seg < this.Segments; seg++)
var hiddenLayer = !(seg >= fromSeg && seg < toSeg);
if (!hiddenLayer)
path.AppendPath(
this.CreateSegmentPath(
startSegAngle, endSegAngle - this.Margin));
startSegAngle += segmentSize;
endSegAngle += segmentSize;
else if (this.Segments == 1)
path.AppendPath(this.CreateSegmentPath(this.StartAngle, this.EndAngle));
return path;
private UIBezierPath CreateSegmentPath(nfloat startSegAngle, nfloat endSegAngle)
var center = new CGPoint(x: this.Bounds.Width / 2f, y: this.Bounds.Height / 2f);
var radius = (nfloat)Math.Max(this.Bounds.Width, this.Bounds.Height) / 2f - this.LineWidth / 2f;
var path = UIBezierPath.FromArc(
center,
radius,
Deg2Rad(startSegAngle - 90f),
Deg2Rad(endSegAngle - 90f),
true);
path.MoveTo(center);
path.ClosePath();
path.Stroke();
return path;
private static nfloat Deg2Rad(nfloat value)
return (nfloat)(floatPI / 180.0 * value);
private static readonly nfloat floatPI = (nfloat)Math.PI;
private nint segments;
private nfloat margin;
[DesignTimeVisible(true)]
public partial class ArcSegmentView : UIView
public ArcSegmentView(IntPtr handle) : base(handle)
this.strokeColor = UIColor.Black.CGColor;
#region Properties
[Export("StartAngle"), Browsable(true)]
public nfloat StartAngle
get return startAngle;
set
if (startAngle != value)
startAngle = value;
((ArcSegmentLayer)this.Layer).StartAngle = (float)value;
this.SetNeedsDisplay();
[Export("EndAngle"), Browsable(true)]
public nfloat EndAngle
get return endAngle;
set
if (endAngle != value)
endAngle = value;
((ArcSegmentLayer)this.Layer).EndAngle = (float)value;
this.SetNeedsDisplay();
[Export("Segments"), Browsable(true)]
public nint Segments
get return segments;
set
if (segments != value)
segments = value;
((ArcSegmentLayer)this.Layer).Segments = value;
this.SetNeedsDisplay();
[Export("Margin"), Browsable(true)]
public nfloat Margin
get return margin;
set
if (margin != value)
margin = value;
((ArcSegmentLayer)this.Layer).Margin = value;
this.SetNeedsDisplay();
[Export("LineWidth"), Browsable(true)]
public nfloat LineWidth
get return lineWidth;
set
if (lineWidth != value)
lineWidth = value;
((ArcSegmentLayer)this.Layer).LineWidth = value;
this.SetNeedsDisplay();
[Export("StrokeColor"), Browsable(true)]
public CGColor StrokeColor
get return strokeColor;
set
if (StrokeColor != value)
strokeColor = value;
((ArcSegmentLayer)this.Layer).StrokeColor = value;
//this.SetNeedsDisplay();
#endregion
[Export("layerClass")]
static Class LayerClass()
return new Class(typeof(ArcSegmentLayer));
private nfloat lineWidth;
private nfloat margin;
private nint segments;
private nfloat startAngle;
private nfloat endAngle;
private CGColor strokeColor;
public partial class ViewController : UIViewController
protected ViewController(IntPtr handle) : base(handle)
public override void ViewDidLoad()
base.ViewDidLoad();
arcSegment.StartAngle = 45;
arcSegment.EndAngle = 90;
arcSegment.Margin = 2;
arcSegment.StrokeColor = UIColor.Red.CGColor;
arcSegment.Segments = 70;
arcSegment.LineWidth = 10;
CABasicAnimation animation = CABasicAnimation.FromKeyPath("EndAngle");
animation.TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.Linear);
animation.From = new NSNumber(45);
animation.To = new NSNumber(360);
animation.Duration = 10;
arcSegment.Layer.AddAnimation(animation, "EndAngle");
【问题讨论】:
这不适用于Xamarin.iOS
,因为不支持@dynamic
属性:bugzilla.xamarin.com/show_bug.cgi?id=38823
【参考方案1】:
我们有一个示例说明如何执行此操作:
https://github.com/xamarin/ios-samples/tree/master/CustomPropertyAnimation
特别是:
https://github.com/xamarin/ios-samples/blob/master/CustomPropertyAnimation/AppDelegate.cs
【讨论】:
以上是关于使用 Xamarin 的 CALayer 自定义属性动画的主要内容,如果未能解决你的问题,请参考以下文章