我有一个自定义UIView来显示平铺图像。

    - (void)drawRect:(CGRect)rect
    {
        ...
        CGContextRef context = UIGraphicsGetCurrentContext();       
        CGContextClipToRect(context,
             CGRectMake(0.0, 0.0, rect.size.width, rect.size.height));      
        CGContextDrawTiledImage(context, imageRect, imageRef);
        ...
    }

现在我正在尝试为该视图调整大小。

[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.3];

// change frame of view

[UIView commitAnimations];

我期望的是平铺区域只是增长,使瓷砖尺寸保持不变。相反,当区域增长时,视图的原始内容将缩放到新大小。至少在动画期间。所以在动画的开头和结尾都很好。在动画期间,瓷砖会扭曲。

为什么CA试图扩展?我怎么能阻止它这样做?我错过了什么?

有帮助吗?

解决方案

如果Core Animation必须为每个动画帧回调代码,它将永远不会像现在这样快,并且自定义属性的动画一直是Mac上CA的长期请求功能和常见问题解答。

使用 UIViewContentModeRedraw 是正确的,也是你从CA获得的最好的。问题是从UIKit的角度来看,框架只有两个值:转换开始时的值和转换结束时的值,这就是你所看到的。如果查看Core Animation体系结构文档,您将看到CA如何拥有所有图层属性的私有表示,并且它们的值随时间而变化。这就是帧插值发生的地方,并且当它们发生时你无法得到通知。

所以唯一的方法是使用 NSTimer (或 performSelector:withObject:afterDelay:)来改变视图帧,这是老式的方式。

其他提示

我在不同的背景下遇到了类似的问题,我偶然发现了这个帖子,发现了Duncan在NSTimer中设置框架的建议。我实现了这个代码来实现相同的目标:

CGFloat kDurationForFullScreenAnimation = 1.0;
CGFloat kNumberOfSteps = 100.0; // (kDurationForFullScreenAnimation / kNumberOfSteps) will be the interval with which NSTimer will be triggered.
int gCurrentStep = 0;
-(void)animateFrameOfView:(UIView*)inView toNewFrame:(CGRect)inNewFrameRect withAnimationDuration:(NSTimeInterval)inAnimationDuration numberOfSteps:(int)inSteps
{
    CGRect originalFrame = [inView frame];

    CGFloat differenceInXOrigin = originalFrame.origin.x - inNewFrameRect.origin.x;
    CGFloat differenceInYOrigin = originalFrame.origin.y - inNewFrameRect.origin.y;
    CGFloat stepValueForXAxis = differenceInXOrigin / inSteps;
    CGFloat stepValueForYAxis = differenceInYOrigin / inSteps;

    CGFloat differenceInWidth = originalFrame.size.width - inNewFrameRect.size.width;
    CGFloat differenceInHeight = originalFrame.size.height - inNewFrameRect.size.height;
    CGFloat stepValueForWidth = differenceInWidth / inSteps;
    CGFloat stepValueForHeight = differenceInHeight / inSteps;

    gCurrentStep = 0;
    NSArray *info = [NSArray arrayWithObjects: inView, [NSNumber numberWithInt:inSteps], [NSNumber numberWithFloat:stepValueForXAxis], [NSNumber numberWithFloat:stepValueForYAxis], [NSNumber numberWithFloat:stepValueForWidth], [NSNumber numberWithFloat:stepValueForHeight], nil];
    NSTimer *aniTimer = [NSTimer timerWithTimeInterval:(kDurationForFullScreenAnimation / kNumberOfSteps)
                                                target:self
                                              selector:@selector(changeFrameWithAnimation:)
                                              userInfo:info
                                               repeats:YES];
    [self setAnimationTimer:aniTimer];
    [[NSRunLoop currentRunLoop] addTimer:aniTimer
                                 forMode:NSDefaultRunLoopMode];
}

-(void)changeFrameWithAnimation:(NSTimer*)inTimer
{
    NSArray *userInfo = (NSArray*)[inTimer userInfo];
    UIView *inView = [userInfo objectAtIndex:0];
    int totalNumberOfSteps = [(NSNumber*)[userInfo objectAtIndex:1] intValue];
    if (gCurrentStep<totalNumberOfSteps)
    {
        CGFloat stepValueOfXAxis = [(NSNumber*)[userInfo objectAtIndex:2] floatValue];
        CGFloat stepValueOfYAxis = [(NSNumber*)[userInfo objectAtIndex:3] floatValue];
        CGFloat stepValueForWidth = [(NSNumber*)[userInfo objectAtIndex:4] floatValue];
        CGFloat stepValueForHeight = [(NSNumber*)[userInfo objectAtIndex:5] floatValue];

        CGRect currentFrame = [inView frame];
        CGRect newFrame;
        newFrame.origin.x = currentFrame.origin.x - stepValueOfXAxis;
        newFrame.origin.y = currentFrame.origin.y - stepValueOfYAxis;
        newFrame.size.width = currentFrame.size.width - stepValueForWidth;
        newFrame.size.height = currentFrame.size.height - stepValueForHeight;

        [inView setFrame:newFrame];

        gCurrentStep++;
    }
    else 
    {
        [[self animationTimer] invalidate];
    }
}

我发现设置帧和计时器需要很长时间才能完成其操作。它可能取决于您使用此方法调用-setFrame的视图层次结构的深度。也许这就是为什么视图首先重新调整大小然后动画到sdk中的原点的原因。或者,我的代码中的计时器机制是否存在一些问题,导致性能受阻?

它有效,但速度很慢,可能是因为我的视图层次太深了。

正如Duncan指出的那样,Core Animation不会在调整大小时每帧重绘UIView的图层内容。你需要自己使用计时器。

Omni的人发布了一个很好的示例,说明如何根据您自己的自定义属性进行动画制作,这些属性可能适用于您的情况。该示例及其工作原理说明可以在中找到。这里

如果它不是一个大动画或性能不是动画持续时间的问题,您可以使用CADisplayLink。它确实平滑了自定义UIView绘图UIBezierPaths的缩放动画。以下是您可以根据图像调整的示例代码。

我的自定义视图的ivars包含displayLink作为CADisplayLink,以及两个CGRect:toFrame和fromFrame,以及duration和startTime。

- (void)yourAnimationMethodCall
{
    toFrame = <get the destination frame>
    fromFrame = self.frame; // current frame.

    [displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; // just in case    
    displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(animateFrame:)];
    startTime = CACurrentMediaTime();
    duration = 0.25;
    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}

- (void)animateFrame:(CADisplayLink *)link
{
    CGFloat dt = ([link timestamp] - startTime) / duration;

    if (dt >= 1.0) {
        self.frame = toFrame;
        [displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
        displayLink = nil;
        return;
    }

    CGRect f = toFrame;
    f.size.height = (toFrame.size.height - fromFrame.size.height) * dt + fromFrame.size.height;
    self.frame = f;
}

请注意,我尚未测试其性能,但它确实可以顺利运行。

在尝试使用边框在UIView上设置尺寸更改动画时,我偶然发现了这个问题。我希望那些同样到达这里的人可能会从这个答案中受益。最初我创建了一个自定义的UIView子类并覆盖了drawRect方法,如下所示:

- (void)drawRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    CGContextSetLineWidth(ctx, 2.0f);
    CGContextSetStrokeColorWithColor(ctx, [UIColor orangeColor].CGColor);
    CGContextStrokeRect(ctx, rect);

    [super drawRect:rect];
}

这与其他人提到的动画结合时会导致缩放问题。边框的顶部和底部将变得太厚或两个薄,并且看起来缩放。在慢动画切换时,这种效果很明显。

解决方案是放弃自定义子类,而是使用这种方法:

[_borderView.layer setBorderWidth:2.0f];
[_borderView.layer setBorderColor:[UIColor orangeColor].CGColor];

这解决了在动画期间在UIView上缩放边框的问题。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top