代码之家  ›  专栏  ›  技术社区  ›  tcurdt

动画期间的uiview缩放

  •  6
  • tcurdt  · 技术社区  · 17 年前

    我有一个自定义的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要尝试扩展?我怎样才能阻止它这样做呢?我错过了什么?

    6 回复  |  直到 12 年前
        1
  •  15
  •   duncanwilcox    17 年前

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

    使用 UIViewContentModeRedraw 是在正确的轨道上,也是从CA获得最好的。问题是从uikit的角度来看,框架只有两个值:转换开始时的值和转换结束时的值,这就是您看到的。如果您查看核心动画架构文档,您将看到CA如何拥有所有层属性的私有表示,以及它们的值随时间的变化。这就是帧内插发生的地方,当发生更改时,您无法得到通知。

    所以唯一的方法是使用 NSTimer (或) performSelector:withObject:afterDelay: )要随时间改变视图框架,请使用老式方法。

        3
  •  4
  •   Raj Pawan Gumdal    16 年前

    在不同的背景下,我遇到了类似的问题,我偶然发现了这条线索,发现了邓肯关于在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中被动画化到原点的原因。或者,在我的代码中,计时器机制是否存在一些问题,导致性能受到阻碍?

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

        4
  •  2
  •   Brad Larson    17 年前

    正如Duncan所指出的,核心动画不会在每帧调整大小时重新绘制uiview层的内容。你需要自己用计时器来完成。

    Omni的员工发布了一个很好的例子,说明如何根据您自己的自定义属性来制作动画,这些属性可能适用于您的案例。这个例子,连同它如何工作的解释,可以找到 here .

        5
  •  1
  •   onekiloparsec    13 年前

    如果它不是一个大的动画或性能不是动画持续时间的问题,您可以使用cadisplayLink。例如,它可以平滑自定义uiview绘图uibezierPaths缩放的动画。下面是一个示例代码,您可以根据自己的图像进行调整。

    自定义视图的ivars包含作为caDisplayLink的DisplayLink,以及两个cDirect: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;
    }
    

    请注意,我没有测试它的性能,但它工作得很顺利。

        6
  •  1
  •   diadyne    12 年前

    我在尝试在带有边框的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上缩放边框的问题。

    推荐文章