2009-05-03 37 views
6

Tôi có UIView tùy chỉnh để hiển thị hình ảnh xếp kề.UIView mở rộng trong khi hoạt ảnh

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

Bây giờ tôi đang cố gắng tạo hoạt ảnh thay đổi kích thước của chế độ xem đó.

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

// change frame of view 

[UIView commitAnimations]; 

Điều tôi mong đợi đối với khu vực lát gạch chỉ để phát triển, khiến kích thước ô không đổi. Điều gì xảy ra thay vào đó là trong khi khu vực phát triển nội dung gốc của chế độ xem được chia tỷ lệ thành kích thước mới. Ít nhất là trong thời gian hoạt hình. Vì vậy, ở đầu và cuối của tất cả các hình ảnh động là tốt. Trong quá trình hoạt hình, các viên gạch bị bóp méo.

Tại sao CA cố gắng mở rộng? Làm thế nào tôi có thể ngăn chặn nó làm như vậy? Tôi đã bỏ lỡ cái gì?

+0

Có vẻ như nó liên quan đến contentMode của chế độ xem. Nhưng thiết lập nó để UIViewContentModeLeft không thực sự giải quyết nó hoặc. Nó sau đó không còn quy mô nhưng bật lên đến kích thước khung hình mới ngay lập tức. Thiết lập nó để UIViewContentModeRedraw dường như không được gọi ra để drawRect ở tất cả trong quá trình hoạt hình. – tcurdt

Trả lời

15

Nếu Core Animation phải gọi lại mã của bạn cho mọi khung hình động sẽ không bao giờ nhanh như nó, và hoạt ảnh của thuộc tính tùy chỉnh là một tính năng được yêu cầu lâu dài và Câu hỏi thường gặp dành cho CA trên máy Mac.

Sử dụng UIViewContentModeRedraw đang đi đúng hướng và cũng là tốt nhất bạn sẽ nhận được từ CA. Vấn đề là từ quan điểm của UIKit, khung chỉ có hai giá trị: giá trị ở đầu quá trình chuyển đổi và giá trị ở cuối quá trình chuyển đổi và đó là những gì bạn đang thấy. Nếu bạn xem các tài liệu kiến ​​trúc Core Animation, bạn sẽ thấy cách CA có một biểu diễn riêng tư của tất cả các thuộc tính lớp và giá trị của chúng thay đổi theo thời gian. Đó là nơi nội suy khung đang diễn ra và bạn không thể được thông báo về những thay đổi đó khi chúng xảy ra.

Vì vậy, cách duy nhất là sử dụng NSTimer (hoặc performSelector:withObject:afterDelay:) để thay đổi khung xem theo thời gian, theo cách cũ.

+0

Cảm ơn, tôi đã sử dụng một NSTimer để làm điều đó (Gọi drawRect cùng với hoạt ảnh). Nó làm việc cho tôi. – Strong

+0

@ jazou2012 Bạn không nên gọi 'drawRect', bạn nên gọi 'setNeedsDisplay'. –

+0

@ Jean-PhilippePellet Có, bạn đã đúng. 'setNeedsDisplay' sẽ gọi' drawRect'! Và đó cũng là điều tôi muốn nói. – Strong

2

Như Duncan cho biết, Hoạt ảnh chính sẽ không vẽ lại nội dung của lớp UIView của mỗi khung hình khi được thay đổi kích thước. Bạn cần tự làm điều đó bằng bộ đếm thời gian.

Những người ở Omni đã đăng một ví dụ hay về cách tạo hoạt ảnh dựa trên thuộc tính tùy chỉnh của riêng bạn có thể áp dụng cho trường hợp của bạn. Ví dụ đó, cùng với lời giải thích về cách hoạt động, có thể tìm thấy here.

4

Tôi đã đối mặt với vấn đề tương tự trong một ngữ cảnh khác, tôi tình cờ gặp chủ đề này và tìm thấy đề xuất của Duncan về thiết lập khung trong NSTimer. Tôi đã triển khai mã này để đạt được điều này:

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]; 
    } 
} 

Tôi phát hiện ra rằng phải mất một thời gian dài để đặt khung và bộ hẹn giờ hoàn thành hoạt động của nó. Nó có thể phụ thuộc vào cách phân cấp khung nhìn sâu sắc mà bạn gọi -setFrame bằng cách sử dụng phương pháp này. Và có lẽ đây là lý do tại sao chế độ xem lần đầu tiên được kích thước lại và sau đó là hoạt ảnh về nguồn gốc trong sdk. Hoặc có lẽ, có một số vấn đề trong cơ chế hẹn giờ trong mã của tôi do đó hiệu suất đã cản trở?

Tính năng này hoạt động nhưng rất chậm, có thể do phân cấp chế độ xem của tôi quá sâu.

+2

Mã của bạn hoạt động tốt cho chế độ xem của tôi (chế độ xem nền và lớp phủ văn bản). Tuy nhiên, 2 thay đổi là cần thiết: 1. [self.animationTimer invalidate] ở đầu changeFrameWithAnimation và 2. thay đổi thời lượng hoạt ảnh thành 0,3, cũng như số bước. Vì nó là ngay bây giờ, bạn đang gọi các bộ đếm thời gian tại một khoảng thời gian 10ms, nhưng bạn chỉ phải làm điều đó mỗi 16ms cho một tỷ lệ làm mới 60Hz. – Jon

+0

Cảm ơn @Jon, havent đã nghĩ về tỷ lệ làm mới 60Hz, nhưng trong trường hợp của tôi toàn bộ quá trình sẽ làm chậm coz hệ thống phân cấp của tôi là nặng và do đó nó đã nói nhiều thời gian hơn dự kiến. Vì vậy, phải tự thay đổi yêu cầu! –

1

Nếu nó không phải là một phim hoạt hình lớn hoặc thực hiện không phải là một vấn đề trong suốt thời gian hoạt hình, bạn có thể sử dụng một CADisplayLink. Nó làm mịn ra các hình ảnh động của quy mô của một UIView vẽ UIBezierPaths tùy chỉnh ví dụ. Dưới đây là một mã mẫu bạn có thể thích ứng cho hình ảnh của bạn.

Các ivars của chế độ xem tùy chỉnh của tôi chứa displayLink dưới dạng CADisplayLink và hai CGRects: toFrame và fromFrame, cũng như thời lượng và thời gian bắt đầu.

- (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; 
} 

Lưu ý rằng tôi chưa thử nghiệm hiệu suất của nó, nhưng nó hoạt động trơn tru.

1

Tôi tình cờ gặp phải câu hỏi này khi cố gắng tạo hiệu ứng thay đổi kích thước trên UIView với đường viền. Tôi hy vọng rằng những người khác tương tự đến đây có thể hưởng lợi từ câu trả lời này. Ban đầu tôi đã tạo một lớp con UIView tùy chỉnh và ghi đè phương thức drawRect như sau:

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

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

    [super drawRect:rect]; 
} 

Điều này dẫn đến các vấn đề về tỷ lệ khi kết hợp với hoạt ảnh như những người khác đã đề cập. Phần trên cùng và dưới của biên giới sẽ phát triển quá dày hoặc hai mỏng và xuất hiện thu nhỏ. Hiệu ứng này dễ dàng nhận thấy khi chuyển sang chế độ Ảnh động chậm.

Giải pháp là phải từ bỏ các lớp con tùy chỉnh và thay vào đó sử dụng phương pháp này:

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

này cố định vấn đề với mở rộng quy mô một biên giới trên một UIView trong hình ảnh động.

Các vấn đề liên quan