10

我正在尝试创建一个 UIView,它显示一个半透明的圆圈,其边界内有一个不透明的边框。我希望能够以两种方式更改边界 - 在-[UIView animateWithDuration:animations:]块内和在每秒触发几次的捏手势识别器动作中。我已经根据 SO 其他地方的答案尝试了三种方法,但没有一种是合适的。

  1. 设置视图层的圆角半径可以layoutSubviews提供平滑的平移,但视图在动画期间不会保持圆形;似乎cornerRadius 是不可动画的。

  2. 绘制圆圈drawRect:会产生一致的圆形视图,但如果圆圈变得太大,则在捏合手势中调整大小会变得不稳定,因为设备花费了太多时间来重绘圆圈。

  3. 添加一个 CAShapeLayer 并将其 path 属性设置为layoutSublayersOfLayer,它不会在 UIView 动画中进行动画处理,因为 path 不是隐式可动画的。

有没有办法让我创建一个始终圆形且可平滑调整大小的视图?我可以使用其他类型的层来利用硬件加速吗?

更新

当我说我想更改-[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].

4

4 回答 4

17

正如“View Programming Guide for iOS”中关于动画的部分所说

UIKit 和 Core Animation 都提供了对动画的支持,但每种技术提供的支持程度各不相同。在 UIKit 中,动画是使用 UIView 对象执行的

您可以使用旧版本设置动画的完整属性列表

[UIView beginAnimations:context:];
[UIView setAnimationDuration:];
// Change properties here...
[UIView commitAnimations];

或更新的

[UIView animateWithDuration:animations:];

(您正在使用的)是:

  • 框架
  • 界限
  • 中央
  • 变换(CGAffineTransform,不是 CATransform3D)
  • α
  • 背景颜色
  • 内容拉伸

让人迷惑的是,你还可以在 UIView 动画块内部的图层上设置相同的属性,即 frame、bounds、position、opacity、backgroundColor。

同一部分继续说:

在你想要执行更复杂的动画,或者 UIView 类不支持的动画的地方,你可以使用 Core Animation 和视图的底层来创建动画。因为视图和图层对象错综复杂地链接在一起,所以对视图图层的更改会影响视图本身。

几行之后,您可以阅读 Core Animation 可动画属性列表,您可以在其中看到:

  • 图层的边框(包括图层的角是否圆角)

至少有两个不错的选择可以达到您所追求的效果:

  • 动画圆角半径
  • 使用 CAShapeLayer 并为路径设置动画

这两者都要求您使用 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...
                 }]; 
于 2012-05-20T14:02:11.670 回答
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 动画的完成块不会运行。

半径动画几乎在所有情况下都遵循边界动画 - 我正在尝试消除一些边缘情况,但它基本上就在那里。还值得一提的是,捏合手势有时仍然很生涩——我想即使是加速绘图也很昂贵。

于 2012-06-05T17:33:17.590 回答
1

从 iOS 11 开始,cornerRadius如果您在动画块内更改 UIKit,它就会动画。

于 2017-06-22T19:59:24.823 回答
0

CAShapeLayer 的 path 属性不是隐式可动画的,但它是可动画的。创建一个改变圆路径大小的 CABasicAnimation 应该很容易。只需确保路径具有相同数量的控制点(例如更改整圆弧的半径)。如果您更改控制点的数量,事情会变得非常奇怪。根据文档,“结果未定义”。

于 2012-05-19T22:16:00.187 回答