我正在尝试创建一个UIView,它显示一个半透明圆圈,其边界内有一个不透明的边框。我希望能够以两种方式更改边界 - 在-[UIView animateWithDuration:animations:]
块内以及每秒触发几次的捏手势识别器动作。我已经尝试了三种基于SO其他地方的答案的方法,但没有一种适合。
在layoutSubviews
中设置视图图层的圆角半径可以提供平滑的平移,但在动画期间视图不会保持圆形;似乎cornerRadius不可动画。
在drawRect:
中绘制圆圈可以得到一致的圆形视图,但如果圆圈太大,那么在捏合手势中调整大小会变得不稳定,因为设备花费了太多时间重绘圆圈。
添加CAShapeLayer并在layoutSublayersOfLayer
中设置其路径属性,该属性不会在UIView动画内部制作动画,因为路径不可隐式设置动画。
有没有办法让我创建一个始终循环且平滑可调整大小的视图?我可以使用其他类型的层来利用硬件加速吗?
更新
当我说要更改-[UIView animateWithDuration:animations:]
块内的界限时,一位评论者要求我扩展我的意思。在我的代码中,我有一个包含我的圆视图的视图。圆形视图(使用cornerRadius的版本)会覆盖-[setBounds:]
以设置圆角半径:
-(void)setBounds:(CGRect)bounds
{
self.layer.cornerRadius = fminf(bounds.size.width, bounds.size.height) / 2.0;
[super setBounds:bounds];
}
圆圈视图的边界在-[layoutSubviews]
:
-(void)layoutSubviews
{
// some other layout is performed and circleRadius and circleCenter are
// calculated based on the properties and current size of the view.
self.circleView.bounds = CGRectMake(0, 0, circleRadius*2, circleRadius*2);
self.circleView.center = circleCenter;
}
视图有时会在动画中调整大小,如下所示:
[UIView animateWithDuration:0.33 animations:^(void) {
myView.frame = CGRectMake(x, y, w, h);
[myView setNeedsLayout];
[myView layoutIfNeeded];
}];
但是在这些动画中,如果我使用带有cornerRadius的图层绘制圆形视图,它会变成有趣的形状。我无法将动画持续时间传递给layoutSubviews,因此我需要在-[setBounds]
中添加正确的动画。
答案 0 :(得分:17)
正如“查看iOS编程指南”中的section on Animations所说
UIKit和Core Animation都支持动画,但每项技术提供的支持程度各不相同。在UIKit中,使用UIView对象
执行动画
完整的属性列表,您可以使用较旧的
进行动画处理[UIView beginAnimations:context:];
[UIView setAnimationDuration:];
// Change properties here...
[UIView commitAnimations];
或更新的
[UIView animateWithDuration:animations:];
(您正在使用)是:
让人感到困惑的是,您还可以在UIView动画块内的图层上设置相同属性的动画,即框架,边界,位置,不透明度,backgroundColor。
同一节继续说:
在您想要执行更复杂的动画或UIView类不支持的动画的地方,您可以使用Core Animation和视图的底层来创建动画。由于视图和图层对象错综复杂地链接在一起,因此对视图图层的更改会影响视图本身。
您可以在几行内阅读Core Animation动画属性列表,其中您会看到以下内容:
- 图层的边框(包括图层的边角是否圆角)
至少有两个很好的选择可以达到你想要的效果:
这两个都要求您使用Core Animation进行动画制作。如果需要多个动画作为一个动画运行,可以创建CAAnimationGroup并向其添加动画数组。
使用尽可能少的代码更改来修复事物将通过在图层上与其他动画“同时”执行角半径动画来完成。 我在同一时间放置了引号,因为无法保证不在同一组中的动画将在完全相同的时间内完成。根据您正在做的其他动画,它可能更好用只有基本的动画和动画组。如果您要对同一视图动画块中的许多不同视图应用更改,那么您可以查看CATransactions。
下面的代码可以像您描述的那样为框架和角半径设置动画。
UIView *circle = [[UIView alloc] initWithFrame:CGRectMake(30, 30, 100, 100)];
[[circle layer] setCornerRadius:50];
[[circle layer] setBorderColor:[[UIColor orangeColor] CGColor]];
[[circle layer] setBorderWidth:2.0];
[[circle layer] setBackgroundColor:[[[UIColor orangeColor] colorWithAlphaComponent:0.5] CGColor]];
[[self view] addSubview:circle];
CGFloat animationDuration = 4.0; // Your duration
CGFloat animationDelay = 3.0; // Your delay (if any)
CABasicAnimation *cornerRadiusAnimation = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
[cornerRadiusAnimation setFromValue:[NSNumber numberWithFloat:50.0]]; // The current value
[cornerRadiusAnimation setToValue:[NSNumber numberWithFloat:10.0]]; // The new value
[cornerRadiusAnimation setDuration:animationDuration];
[cornerRadiusAnimation setBeginTime:CACurrentMediaTime() + animationDelay];
// If your UIView animation uses a timing funcition then your basic animation needs the same one
[cornerRadiusAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
// This will keep make the animation look as the "from" and "to" values before and after the animation
[cornerRadiusAnimation setFillMode:kCAFillModeBoth];
[[circle layer] addAnimation:cornerRadiusAnimation forKey:@"keepAsCircle"];
[[circle layer] setCornerRadius:10.0]; // Core Animation doesn't change the real value so we have to.
[UIView animateWithDuration:animationDuration
delay:animationDelay
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
[[circle layer] setFrame:CGRectMake(50, 50, 20, 20)]; // Arbitrary frame ...
// You other UIView animations in here...
} completion:^(BOOL finished) {
// Maybe you have your completion in here...
}];
答案 1 :(得分:10)
非常感谢大卫,这是我找到的解决方案。最后结果是使用视图的-[actionForLayer:forKey:]
方法,因为它在UIView块中使用,而不是图层的-[actionForKey]
返回。
@implementation SGBRoundView
-(CGFloat)radiusForBounds:(CGRect)bounds
{
return fminf(bounds.size.width, bounds.size.height) / 2;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
self.opaque = NO;
self.layer.backgroundColor = [[UIColor purpleColor] CGColor];
self.layer.borderColor = [[UIColor greenColor] CGColor];
self.layer.borderWidth = 3;
self.layer.cornerRadius = [self radiusForBounds:self.bounds];
}
return self;
}
-(void)setBounds:(CGRect)bounds
{
self.layer.cornerRadius = [self radiusForBounds:bounds];
[super setBounds:bounds];
}
-(id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
id<CAAction> action = [super actionForLayer:layer forKey:event];
if ([event isEqualToString:@"cornerRadius"])
{
CABasicAnimation *boundsAction = (CABasicAnimation *)[self actionForLayer:layer forKey:@"bounds"];
if ([boundsAction isKindOfClass:[CABasicAnimation class]] && [boundsAction.fromValue isKindOfClass:[NSValue class]])
{
CABasicAnimation *cornerRadiusAction = [CABasicAnimation animationWithKeyPath:@"cornerRadius"];
cornerRadiusAction.delegate = boundsAction.delegate;
cornerRadiusAction.duration = boundsAction.duration;
cornerRadiusAction.fillMode = boundsAction.fillMode;
cornerRadiusAction.timingFunction = boundsAction.timingFunction;
CGRect fromBounds = [(NSValue *)boundsAction.fromValue CGRectValue];
CGFloat fromRadius = [self radiusForBounds:fromBounds];
cornerRadiusAction.fromValue = [NSNumber numberWithFloat:fromRadius];
return cornerRadiusAction;
}
}
return action;
}
@end
通过使用视图为边界提供的操作,我能够获得正确的持续时间,填充模式和计时功能,最重要的是委托 - 没有它,UIView动画的完成块没有运行。 / p>
在几乎所有情况下,半径动画都遵循边界动画 - 有一些我试图解决的边缘情况,但它基本上就存在了。还值得一提的是,捏合手势有时仍然不稳定 - 我猜即使加速绘图仍然很昂贵。
答案 2 :(得分:1)
从iOS 11开始,如果在动画块中更改它,UIKit会动画cornerRadius
。
答案 3 :(得分:0)
CAShapeLayer的path属性不是隐式可动画,但它是可动画的。创建一个改变圆形路径大小的CABasicAnimation应该很容易。只需确保路径具有相同数量的控制点(例如,更改全圆弧的半径)。如果更改控制点的数量,事情就会变得非常奇怪。根据文献报道,“结果未定义”。