2012-02-27 28 views
11

Tôi đang thử nghiệm với hoạt ảnh Khung hình chính của vị trí của đối tượng UIImageView di chuyển dọc theo đường dẫn bezier. Ảnh này hiển thị trạng thái ban đầu trước hoạt ảnh. Dòng màu xanh là con đường - ban đầu di chuyển thẳng lên, ánh sáng hộp màu xanh lá cây là hộp ban đầu bounding hoặc hình ảnh, và bóng tối màu xanh lá cây "ma" là hình ảnh mà tôi đang di chuyển:iOS CAKeyframeChế độ xoay vòngMode trên UIImageView

Initial state

Khi Tôi khởi động hoạt ảnh với rotationMode được đặt thành nil, hình ảnh giữ cùng hướng theo mọi hướng thông qua đường đi như mong đợi.

Nhưng khi tôi khởi động hoạt ảnh bằng rotationMode được đặt thành kCAAnimationRotateAuto, hình ảnh ngay lập tức quay 90 độ ngược chiều kim đồng hồ và giữ hướng này suốt đường dẫn. Khi nó đạt đến cuối con đường nó vẽ lại theo đúng hướng (cũng nó thực sự cho thấy UIImageView mà tôi thay đổi vị trí ở vị trí chính thức)

enter image description here

tôi đã ngây thơ mong đợi rằng rotationMode sẽ định hướng hình ảnh để đường tiếp tuyến của đường đi và không phải là bình thường, đặc biệt khi tài liệu của Apple cho trạng thái CAKeyframeAnimation rotationMode

Xác định xem các đối tượng có hoạt ảnh dọc theo đường đi hay không để phù hợp với đường tiếp tuyến.

Vậy giải pháp ở đây là gì? Tôi có phải xoay hình ảnh trước 90 độ theo chiều kim đồng hồ không? Hay có điều gì đó mà tôi đang thiếu?

Cảm ơn sự giúp đỡ của bạn.


Chỉnh sửa ngày 02 tháng 3

Tôi đã thêm một bước xoay trước hình ảnh động con đường sử dụng một vòng xoay Affine như:

theImage.transform = CGAffineTransformRotate(theImage.transform,90.0*M_PI/180); 

và sau đó sau khi các hình ảnh động con đường, đặt lại xoay với:

theImage.transform = CGAffineTransformIdentity; 

Điều này làm cho e hình ảnh theo con đường theo cách mong đợi. Tuy nhiên bây giờ tôi đang chạy vào một vấn đề khác nhau của hình ảnh nhấp nháy. Tôi đã tìm kiếm một giải pháp cho vấn đề bập bùng trong SO câu hỏi này:

iOS CAKeyFrameAnimation scaling flickers at animation end

Vì vậy, bây giờ tôi không biết nếu tôi đã làm cho mọi việc tốt hơn hoặc tồi tệ hơn!


Chỉnh sửa ngày 12 tháng 3

Trong khi Caleb đã chỉ ra rằng có, tôi đã phải trước xoay hình ảnh của tôi, Rob cung cấp một awesome gói mã mà gần như hoàn toàn giải quyết vấn đề của tôi. Điều duy nhất mà Rob đã không làm là bù đắp cho tài sản của tôi được rút ra với một định hướng theo chiều dọc chứ không phải là ngang, do đó vẫn cần phải định trước chúng bằng 90 độ trước khi thực hiện hoạt ảnh.Nhưng hey, nó chỉ công bằng mà tôi phải làm một số công việc để có được những thứ đang chạy.

Vì vậy, những thay đổi nhỏ của tôi để giải pháp Rob để phù hợp với yêu cầu của tôi là:

  1. Khi tôi thêm các UIView, tôi trước Xoay nó để đối phó với sự quay vốn có thêm bằng cách thiết lập rotationMode:

    theImage.transform = CGAffineTransformMakeRotation (90 * M_PI/180.0);

  2. tôi cần phải tiếp tục quay mà ở phần cuối của phim hoạt hình, vì vậy thay vì chỉ nổ của xem chuyển đổi với một yếu tố quy mô mới sau khi khối hoàn thành được xác định, tôi xây dựng quy mô dựa trên hiện chuyển đổi:

    theImage.transform = CGAffineTransformScale (theImage.transform, scaleFactor, scaleFactor);

Và đó là tất cả những gì tôi phải làm để làm cho hình ảnh của mình theo con đường như tôi mong đợi!


Sửa 22 tháng 3

Tôi vừa tải lên GitHub một dự án bản demo cho thấy ngoài khơi động của một đối tượng dọc theo một con đường Bezier. Mã này có thể được tìm thấy tại PathMove

Tôi cũng đã viết về nó trong blog của tôi tại Moving objects along a bezier path in iOS

+0

Điều này đã giúp tôi rất nhiều! Cảm ơn! – RyanG

Trả lời

8

Vấn đề ở đây là tính năng tự động của Core Animation giữ trục ngang của chế độ xem song song với đường tiếp tuyến của đường dẫn. Đó chỉ là cách nó hoạt động.

Nếu bạn muốn trục dọc của chế độ xem theo đường tiếp tuyến của đường dẫn, hãy xoay nội dung của chế độ xem như bạn hiện đang làm là điều hợp lý để thực hiện.

+0

OK .. vậy đó là cách nó hoạt động. Tôi sẽ đặt một trong đó xuống một vấn đề tài liệu sau đó. Nhưng khi tôi sử dụng xoay vòng Affine, tôi chạy vào vấn đề trên câu hỏi SO khác của tôi được đề cập trong phần chỉnh sửa ở trên. Tôi có nên sử dụng CA để thực hiện phép xoay vòng thay vì xoay vòng Affine không? Hoặc có những điều trong câu hỏi khác mà tôi đang làm sai? –

+0

Tôi chưa bao giờ thử nghiệm liệu việc sử dụng CGAffineTransform có tạo nên sự khác biệt hay không, vì vậy tôi không thể nói điều đó. Trực giác, vì bạn đã suy nghĩ về các lớp với Core Animation, nên có thể sử dụng phép chuyển đổi của Core Animation thay vì: '[theView.layer setValue: [NSNumber numberWithFloat: M_PI/2] forKey: @" transform.rotation " ]; '. Nhưng nó cũng có vẻ như CGAffineTransform nên được thực hiện bằng cách sử dụng khả năng chuyển đổi của CALayer, vì vậy nó có thể không quan trọng. Bây giờ tôi đang thiếu thời gian, nhưng sẽ xem xét câu hỏi khác của bạn khi tôi có cơ hội. – Caleb

+0

Từ một câu trả lời cho câu hỏi khác của tôi, tôi sẽ nói việc quay với hình động Core là cách để đi. Khi tôi nhận được câu trả lời chắc chắn cho câu hỏi đó, tôi cũng sẽ cố gắng xoay vòng theo cách tương tự. –

3

Yow đề cập rằng bạn đá ra khỏi hình ảnh động với "rotationMode thiết lập để YES", nhưng các tài liệu nói rằng rotationMode nên được thiết lập sử dụng một NSString ...

Đặc biệt:

những hằng số được sử dụng bởi các tài sản rotationMode.

NSString * const kCAAnimationRotateAuto

NSString * const kCAAnimationRotateAutoReverse

Bạn đã cố gắng thiết lập:

keyframe.animationMode = kCAAnimationRotateAuto;

Các trạng thái tài liệu:

kCAAnimationRotateAuto: Các đối tượng đi trên một tiếp xúc với con đường.

+0

Đó chỉ là văn bản câu hỏi sai, tôi đang sử dụng 'nil' và' kCAAnimationRotateAuto'. Chỉ cần chỉnh sửa câu hỏi để phản ánh điều đó. –

6

Dưới đây là những gì bạn cần biết để loại bỏ nhấp nháy:

  • Như Caleb loại chỉ ra, Core Animation xoay lớp của bạn để trục X tích cực của nó nằm dọc theo tang của con đường của bạn. Bạn cần làm cho định hướng "tự nhiên" của hình ảnh của bạn hoạt động với điều đó. Vì vậy, giả sử đó là một tàu vũ trụ màu xanh lục trong hình ảnh ví dụ của bạn, bạn cần tàu vũ trụ để trỏ đến bên phải khi nó không có vòng quay áp dụng cho nó.

  • Đặt biến đổi bao gồm vòng quay cản trở vòng xoay được áp dụng bởi `kCAAnimationRotateAuto '. Bạn cần xóa vòng xoay khỏi biến đổi của mình trước khi áp dụng hoạt ảnh.

  • Tất nhiên điều đó có nghĩa là bạn cần đăng ký lại chuyển đổi khi hoạt ảnh hoàn tất. Và tất nhiên bạn muốn làm điều đó mà không nhìn thấy bất kỳ nhấp nháy trong sự xuất hiện của hình ảnh. Đó không phải là khó khăn, nhưng có một số nước sốt bí mật liên quan, mà tôi giải thích dưới đây.

  • Bạn có lẽ muốn tàu vũ trụ của bạn bắt đầu chỉ dọc theo đường tiếp tuyến của đường dẫn, ngay cả khi tàu vũ trụ đang ngồi vẫn chưa được làm động. Nếu hình ảnh tàu vũ trụ của bạn đang trỏ sang phải, nhưng con đường của bạn tăng lên, thì bạn cần phải thiết lập biến đổi của hình ảnh để bao gồm một vòng quay 90 °. Nhưng có lẽ bạn không muốn hardcode mà quay - thay vào đó bạn muốn nhìn vào con đường và tìm ra bắt đầu của nó tiếp tuyến.

Tôi sẽ hiển thị một số mã quan trọng tại đây. Bạn có thể tìm thấy my test project on github. Bạn có thể tìm thấy một số sử dụng trong việc tải xuống và dùng thử. Chỉ cần nhấn vào "tàu vũ trụ" màu xanh lục để xem hoạt ảnh.

Vì vậy, trong dự án thử nghiệm của mình, tôi đã kết nối UIImageView với một hành động có tên animate:. Khi bạn chạm vào nó, hình ảnh di chuyển dọc theo một nửa của hình 8 và tăng gấp đôi kích thước. Khi bạn chạm vào nó một lần nữa, hình ảnh di chuyển dọc theo nửa kia của hình 8 (trở lại vị trí bắt đầu), và trở về kích thước ban đầu của nó. Cả hai hình động sử dụng kCAAnimationRotateAuto, vì vậy hình ảnh chỉ dọc theo ốp của đường dẫn.

Đây là sự khởi đầu của animate:, nơi tôi tìm ra những gì con đường, quy mô và mục đích rõ hình ảnh nên kết thúc tại địa chỉ:

- (IBAction)animate:(id)sender { 
    UIImageView* theImage = self.imageView; 

    UIBezierPath *path = _isReset ? _path0 : _path1; 
    CGFloat newScale = 3 - _currentScale; 

    CGPoint destination = [path currentPoint]; 

Vì vậy, điều đầu tiên tôi cần làm là loại bỏ bất kỳ luân chuyển từ của hình ảnh chuyển đổi, vì như tôi đã đề cập, nó sẽ gây trở ngại cho kCAAnimationRotateAuto:

// Strip off the image's rotation, because it interferes with `kCAAnimationRotateAuto`. 
    theImage.transform = CGAffineTransformMakeScale(_currentScale, _currentScale); 

Tiếp theo, tôi đi vào một khối UIView hoạt hình để hệ thống sẽ áp dụng hoạt ảnh cho xem hình ảnh:

[UIView animateWithDuration:3 animations:^{ 

tôi tạo ra các hình ảnh động keyframe cho vị trí và thiết lập một vài thuộc tính của nó:

 // Prepare my own keypath animation for the layer position. 
     // The layer position is the same as the view center. 
     CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; 
     positionAnimation.path = path.CGPath; 
     positionAnimation.rotationMode = kCAAnimationRotateAuto; 

Tiếp theo là bí mật sốt để ngăn ngừa nhấp nháy ở phần cuối của phim hoạt hình. Nhớ lại rằng hình động làm không tác dụng các thuộc tính của "lớp mô hình" mà bạn đính kèm chúng vào (theImage.layer trong trường hợp này). Thay vào đó, họ cập nhật các thuộc tính của "lớp trình bày", phản ánh những gì thực sự trên màn hình.

Vì vậy, trước tiên, tôi đặt removedOnCompletion thành NO cho hoạt ảnh của khung hình chính. Điều này có nghĩa là hoạt ảnh sẽ được gắn liền với lớp mô hình khi hoạt ảnh hoàn tất, có nghĩa là tôi có thể truy cập lớp trình bày. Tôi nhận được biến đổi từ lớp trình bày, xóa hoạt ảnh và áp dụng biến đổi cho lớp mô hình. Vì tất cả điều này xảy ra trên luồng chính, những thay đổi thuộc tính này tất cả xảy ra trong một chu kỳ làm mới màn hình, vì vậy không có nhấp nháy.

 positionAnimation.removedOnCompletion = NO; 
     [CATransaction setCompletionBlock:^{ 
      CGAffineTransform finalTransform = [theImage.layer.presentationLayer affineTransform]; 
      [theImage.layer removeAnimationForKey:positionAnimation.keyPath]; 
      theImage.transform = finalTransform; 
     }]; 

Bây giờ tôi đã thiết lập khối hoàn thành, tôi thực sự có thể thay đổi thuộc tính chế độ xem. Hệ thống sẽ tự động đính kèm hình động vào lớp khi tôi làm điều này.

 // UIView will add animations for both of these changes. 
     theImage.transform = CGAffineTransformMakeScale(newScale, newScale); 
     theImage.center = destination; 

tôi sao chép một số tính chất quan trọng từ những hình ảnh động vị trí tự động gia tăng cho hoạt hình keyframe của tôi:

 // Copy properties from UIView's animation. 
     CAAnimation *autoAnimation = [theImage.layer animationForKey:positionAnimation.keyPath]; 
     positionAnimation.duration = autoAnimation.duration; 
     positionAnimation.fillMode = autoAnimation.fillMode; 

và cuối cùng tôi thay thế các hình ảnh động vị trí tự động gia tăng với các hình ảnh động keyframe:

 // Replace UIView's animation with my animation. 
     [theImage.layer addAnimation:positionAnimation forKey:positionAnimation.keyPath]; 
    }]; 

Cuối cùng, tôi cập nhật biến mẫu của mình để phản ánh thay đổi đối với chế độ xem hình ảnh:

_currentScale = newScale; 
    _isReset = !_isReset; 
} 

Đó là cách hiển thị hình ảnh không bị nhấp nháy.

Và bây giờ, như Steve Jobs sẽ nói, Một điều cuối cùng. Khi tôi tải chế độ xem, tôi cần đặt biến đổi của chế độ xem hình ảnh sao cho nó được xoay theo điểm dọc theo đường tiếp tuyến của đường dẫn đầu tiên mà tôi sẽ sử dụng để tạo hiệu ứng đó. Tôi làm điều đó trong một phương thức có tên reset:

- (void)reset { 
    self.imageView.center = _path1.currentPoint; 
    self.imageView.transform = CGAffineTransformMakeRotation(startRadiansForPath(_path0)); 
    _currentScale = 1; 
    _isReset = YES; 
} 

Tất nhiên, chút khéo léo ẩn trong đó startRadiansForPath chức năng. Nó thực sự không phải là khó. Tôi sử dụng hàm CGPathApply để xử lý các phần tử của đường đi, chọn ra hai điểm đầu tiên thực sự tạo thành một đường con, và tôi tính toán góc của đường được hình thành bởi hai điểm đó. (Một phần đường cong là một đường thẳng đứng bậc hai hoặc khối bezier, và những đường thẳng này có đặc tính là tang tại điểm đầu tiên của đường spline là đường từ điểm đầu tiên đến điểm kiểm soát kế tiếp.)

I ' m chỉ cần kết xuất mã ở đây mà không cần giải thích, cho hậu thế:

typedef struct { 
    CGPoint p0; 
    CGPoint p1; 
    CGPoint firstPointOfCurrentSubpath; 
    CGPoint currentPoint; 
    BOOL p0p1AreSet : 1; 
} PathState; 

static inline void updateStateWithMoveElement(PathState *state, CGPathElement const *element) { 
    state->currentPoint = element->points[0]; 
    state->firstPointOfCurrentSubpath = state->currentPoint; 
} 

static inline void updateStateWithPoints(PathState *state, CGPoint p1, CGPoint currentPoint) { 
    if (!state->p0p1AreSet) { 
     state->p0 = state->currentPoint; 
     state->p1 = p1; 
     state->p0p1AreSet = YES; 
    } 
    state->currentPoint = currentPoint; 
} 

static inline void updateStateWithPointsElement(PathState *state, CGPathElement const *element, int newCurrentPointIndex) { 
    updateStateWithPoints(state, element->points[0], element->points[newCurrentPointIndex]); 
} 

static void updateStateWithCloseElement(PathState *state, CGPathElement const *element) { 
    updateStateWithPoints(state, state->firstPointOfCurrentSubpath, state->firstPointOfCurrentSubpath); 
} 

static void updateState(void *info, CGPathElement const *element) { 
    PathState *state = info; 
    switch (element->type) { 
     case kCGPathElementMoveToPoint: return updateStateWithMoveElement(state, element); 
     case kCGPathElementAddLineToPoint: return updateStateWithPointsElement(state, element, 0); 
     case kCGPathElementAddQuadCurveToPoint: return updateStateWithPointsElement(state, element, 1); 
     case kCGPathElementAddCurveToPoint: return updateStateWithPointsElement(state, element, 2); 
     case kCGPathElementCloseSubpath: return updateStateWithCloseElement(state, element); 
    } 
} 

CGFloat startRadiansForPath(UIBezierPath *path) { 
    PathState state; 
    memset(&state, 0, sizeof state); 
    CGPathApply(path.CGPath, &state, updateState); 
    return atan2f(state.p1.y - state.p0.y, state.p1.x - state.p0.x); 
} 
+0

Cảm ơn các chi tiết của câu trả lời này. Tôi rất trân trọng điều này. Tuy nhiên từ những gì tôi nhìn thấy trong dự án của bạn, bạn đã vẽ trước tài sản được chỉ về bên phải, xoay nó thành đường tiếp tuyến trên màn hình và loại bỏ vòng quay đó trên hoạt ảnh. Trong tình huống của tôi, tôi cần phải sử dụng các tài sản được vẽ sẵn có hiệu quả chỉ lên, do đó, trên hình ảnh động họ cần phải được xoay 90 độ và sau đó di chuyển dọc theo con đường. Tôi sẽ chơi với giải pháp của bạn sau để xem những gì tôi có thể tìm ra cho bản thân mình bây giờ mà bạn đã cho tôi một điểm khởi đầu tốt. Cảm ơn một lần nữa. –

+0

Tôi không mất nhiều thời gian vào sáng nay để tìm ra những gì tôi cần làm để đáp ứng các yêu cầu của tôi. Cảm ơn bạn đã nỗ lực giúp tôi! –

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