2015-02-10 25 views
6

Tôi thấy nó khó khăn để tạo hiệu ứng UIImageView giữa hai trạng thái: khung hình chữ nhật ban đầu của nó và hình dạng mới được tạo bằng UIBezierPath. Có rất nhiều kỹ thuật khác nhau được đề cập, hầu hết trong số đó không làm việc cho tôi.Hoạt hình giữa hai hình dạng đường bezier

Đầu tiên là việc nhận ra rằng sử dụng hoạt ảnh khối UIView sẽ không hoạt động; hiển nhiên, người ta không thể thực hiện hoạt ảnh lớp con trong khối animateWithDuration:. (xem herehere)

Điều đó để lại CAAnimation, với các lớp con cụ thể như CABasicAnimation. Tôi sớm nhận ra rằng người ta không thể sinh động từ một cái nhìn mà không có một số CAShapeLayer cho cái nào (ví dụ: xem here).

Và họ không thể chỉ cần bất kỳ hai con đường shape layer, mà là "Animating con đường của một lớp hình dạng duy nhất là bảo đảm để làm việc khi bạn đang tạo hiệu ứng động từ như thích" (xem here)

Với rằng tại chỗ, đến những vấn đề trần tục hơn, giống như những gì để sử dụng cho fromValuetoValue (họ phải là một CAShapeLayer, hoặc một CGPath?), những gì để thêm các hình ảnh động đến (layer, hoặc mask?) vv

Dường như có quá nhiều biến; sự kết hợp nào sẽ cho tôi hình ảnh động mà tôi đang tìm kiếm?

Trả lời

11

Điểm quan trọng đầu tiên là xây dựng hai đường bezier tương tự nhau, vì vậy hình chữ nhật là một tương tự (tầm thường) tương tự với hình dạng phức tạp hơn.

// the complex bezier path 
let initialPoint = CGPoint(x: 0, y: 0) 
let curveStart = CGPoint(x: 0, y: (rect.size.height) * (0.2)) 
let curveControl = CGPoint(x: (rect.size.width) * (0.6), y: (rect.size.height) * (0.5)) 
let curveEnd = CGPoint(x: 0, y: (rect.size.height) * (0.8)) 
let firstCorner = CGPoint(x: 0, y: rect.size.height) 
let secondCorner = CGPoint(x: rect.size.width, y: rect.size.height) 
let thirdCorner = CGPoint(x: rect.size.width, y: 0) 

var myBezierArc = UIBezierPath() 
myBezierArc.moveToPoint(initialPoint) 
myBezierArc.addLineToPoint(curveStart) 
myBezierArc.addQuadCurveToPoint(curveEnd, controlPoint: curveControl) 
myBezierArc.addLineToPoint(firstCorner) 
myBezierArc.addLineToPoint(secondCorner) 
myBezierArc.addLineToPoint(thirdCorner) 

Các đơn giản hơn đường bezier 'tầm thường', tạo ra một hình chữ nhật, là giống hệt nhau nhưng controlPoint được thiết lập để nó dường như không có mặt ở đó:

let curveControl = CGPoint(x: 0, y: (rect.size.height) * (0.5)) 

(Cố gắng loại bỏ các addQuadCurveToPoint dòng để có được một hình ảnh động rất lạ)

Và cuối cùng, các lệnh hoạt hình:

let myAnimation = CABasicAnimation(keyPath: "path") 

if (isArcVisible == true) { 
    myAnimation.fromValue = myBezierArc.CGPath 
    myAnimation.toValue = myBezierTrivial.CGPath 
} else { 
    myAnimation.fromValue = myBezierTrivial.CGPath 
    myAnimation.toValue = myBezierArc.CGPath 
}  
myAnimation.duration = 0.4 
myAnimation.fillMode = kCAFillModeForwards 
myAnimation.removedOnCompletion = false 

myImageView.layer.mask.addAnimation(myAnimation, forKey: "animatePath") 

Nếu có ai quan tâm, dự án là here.

+0

Cảm ơn ví dụ và tất cả các liên kết trong câu hỏi của bạn. Thật thú vị và có nhiều thông tin để đọc các cuộc thảo luận xung quanh vấn đề này. – rdelmar

+0

cách đặt tovalue (myBezierTrivial.CGPath) trong lệnh hoạt ảnh? – ABJ

3

Tôi đã lấy cảm hứng từ ví dụ của bạn để thử một vòng tròn thành hoạt hình vuông sử dụng các kỹ thuật được đề cập trong câu trả lời của bạn và một số liên kết. Tôi dự định mở rộng này để trở thành một vòng tròn tổng quát hơn cho hoạt hình đa giác, nhưng hiện tại nó chỉ hoạt động cho các ô vuông. Tôi có một lớp được gọi là RDPolyCircle (một lớp con của CAShapeLayer) mà làm việc nặng nhọc. Đây là mã của nó,

@interface RDPolyCircle() 
@property (strong,nonatomic) UIBezierPath *polyPath; 
@property (strong,nonatomic) UIBezierPath *circlePath; 
@end 

@implementation RDPolyCircle { 
    double cpDelta; 
    double cosR; 
} 

-(instancetype)initWithFrame:(CGRect) frame numberOfSides:(NSInteger)sides isPointUp:(BOOL) isUp isInitiallyCircle:(BOOL) isCircle { 
    if (self = [super init]) { 
     self.frame = frame; 
     _isPointUp = isUp; 
     _isExpandedPolygon = !isCircle; 

     double radius = (frame.size.width/2.0); 
     cosR = sin(45 * M_PI/180.0) * radius; 
     double fractionAlongTangent = 4.0*(sqrt(2)-1)/3.0; 
     cpDelta = fractionAlongTangent * radius * sin(45 * M_PI/180.0); 

     _circlePath = [self createCirclePathForFrame:frame]; 
     _polyPath = [self createPolygonPathForFrame:frame numberOfSides:sides]; 
     self.path = (isCircle)? self.circlePath.CGPath : self.polyPath.CGPath; 
     self.fillColor = [UIColor clearColor].CGColor; 
     self.strokeColor = [UIColor blueColor].CGColor; 
     self.lineWidth = 6.0; 
    } 
    return self; 
} 


-(UIBezierPath *)createCirclePathForFrame:(CGRect) frame { 

    CGPoint ctr = CGPointMake(frame.origin.x + frame.size.width/2.0, frame.origin.y + frame.size.height/2.0); 

    // create a circle using 4 arcs, with the first one symmetrically spanning the y-axis 
    CGPoint leftUpper = CGPointMake(ctr.x - cosR, ctr.y - cosR); 
    CGPoint cp1 = CGPointMake(leftUpper.x + cpDelta, leftUpper.y - cpDelta); 

    CGPoint rightUpper = CGPointMake(ctr.x + cosR, ctr.y - cosR); 
    CGPoint cp2 = CGPointMake(rightUpper.x - cpDelta, rightUpper.y - cpDelta); 
    CGPoint cp3 = CGPointMake(rightUpper.x + cpDelta, rightUpper.y + cpDelta); 

    CGPoint rightLower = CGPointMake(ctr.x + cosR, ctr.y + cosR); 
    CGPoint cp4 = CGPointMake(rightLower.x + cpDelta, rightLower.y - cpDelta); 
    CGPoint cp5 = CGPointMake(rightLower.x - cpDelta, rightLower.y + cpDelta); 

    CGPoint leftLower = CGPointMake(ctr.x - cosR, ctr.y + cosR); 
    CGPoint cp6 = CGPointMake(leftLower.x + cpDelta, leftLower.y + cpDelta); 
    CGPoint cp7 = CGPointMake(leftLower.x - cpDelta, leftLower.y - cpDelta); 
    CGPoint cp8 = CGPointMake(leftUpper.x - cpDelta, leftUpper.y + cpDelta); 

    UIBezierPath *circle = [UIBezierPath bezierPath]; 
    [circle moveToPoint:leftUpper]; 
    [circle addCurveToPoint:rightUpper controlPoint1:cp1 controlPoint2:cp2]; 
    [circle addCurveToPoint:rightLower controlPoint1:cp3 controlPoint2:cp4]; 
    [circle addCurveToPoint:leftLower controlPoint1:cp5 controlPoint2:cp6]; 
    [circle addCurveToPoint:leftUpper controlPoint1:cp7 controlPoint2:cp8]; 
    [circle closePath]; 
    circle.lineCapStyle = kCGLineCapRound; 
    return circle; 
} 


-(UIBezierPath *)createPolygonPathForFrame:(CGRect) frame numberOfSides:(NSInteger) sides { 
    CGPoint leftUpper = CGPointMake(self.frame.origin.x, self.frame.origin.y); 
    CGPoint cp1 = CGPointMake(leftUpper.x + cpDelta, leftUpper.y); 

    CGPoint rightUpper = CGPointMake(self.frame.origin.x + self.frame.size.width, self.frame.origin.y); 
    CGPoint cp2 = CGPointMake(rightUpper.x - cpDelta, rightUpper.y); 
    CGPoint cp3 = CGPointMake(rightUpper.x, rightUpper.y + cpDelta); 

    CGPoint rightLower = CGPointMake(self.frame.origin.x + self.frame.size.width, self.frame.origin.y + self.frame.size.height); 
    CGPoint cp4 = CGPointMake(rightLower.x , rightLower.y - cpDelta); 
    CGPoint cp5 = CGPointMake(rightLower.x - cpDelta, rightLower.y); 

    CGPoint leftLower = CGPointMake(self.frame.origin.x, self.frame.origin.y + self.frame.size.height); 
    CGPoint cp6 = CGPointMake(leftLower.x + cpDelta, leftLower.y); 
    CGPoint cp7 = CGPointMake(leftLower.x, leftLower.y - cpDelta); 
    CGPoint cp8 = CGPointMake(leftUpper.x, leftUpper.y + cpDelta); 

    UIBezierPath *square = [UIBezierPath bezierPath]; 
    [square moveToPoint:leftUpper]; 
    [square addCurveToPoint:rightUpper controlPoint1:cp1 controlPoint2:cp2]; 
    [square addCurveToPoint:rightLower controlPoint1:cp3 controlPoint2:cp4]; 
    [square addCurveToPoint:leftLower controlPoint1:cp5 controlPoint2:cp6]; 
    [square addCurveToPoint:leftUpper controlPoint1:cp7 controlPoint2:cp8]; 
    [square closePath]; 
    square.lineCapStyle = kCGLineCapRound; 
    return square; 
} 


-(void)toggleShape { 
    if (self.isExpandedPolygon) { 
     [self restore]; 
    }else{ 

     CABasicAnimation *expansionAnimation = [CABasicAnimation animationWithKeyPath:@"path"]; 
     expansionAnimation.fromValue = (__bridge id)(self.circlePath.CGPath); 
     expansionAnimation.toValue = (__bridge id)(self.polyPath.CGPath); 
     expansionAnimation.duration = 0.5; 
     expansionAnimation.fillMode = kCAFillModeForwards; 
     expansionAnimation.removedOnCompletion = NO; 

     [self addAnimation:expansionAnimation forKey:@"Expansion"]; 
     self.isExpandedPolygon = YES; 
    } 
} 


-(void)restore { 
    CABasicAnimation *contractionAnimation = [CABasicAnimation animationWithKeyPath:@"path"]; 
    contractionAnimation.fromValue = (__bridge id)(self.polyPath.CGPath); 
    contractionAnimation.toValue = (__bridge id)(self.circlePath.CGPath); 
    contractionAnimation.duration = 0.5; 
    contractionAnimation.fillMode = kCAFillModeForwards; 
    contractionAnimation.removedOnCompletion = NO; 

    [self addAnimation:contractionAnimation forKey:@"Contraction"]; 
    self.isExpandedPolygon = NO; 
} 

Từ bộ điều khiển xem, tôi có thể tạo một thể hiện của lớp này, và thêm nó vào lớp một cái nhìn đơn giản, sau đó làm hình động bằng nút nhấn,

#import "ViewController.h" 
#import "RDPolyCircle.h" 

@interface ViewController() 
@property (weak, nonatomic) IBOutlet UIView *circleView; // a plain UIView 150 x 150 centered in the superview 
@property (strong,nonatomic) RDPolyCircle *shapeLayer; 
@end 

@implementation ViewController 

- (void)viewDidLoad { 
    [super viewDidLoad]; 

    // currently isPointUp and numberOfSides are not implemented (the shape created has numberOfSides=4 and isPointUp=NO) 
    // isInitiallyCircle is implemented 
    self.shapeLayer = [[RDPolyCircle alloc] initWithFrame:self.circleView.bounds numberOfSides: 4 isPointUp:NO isInitiallyCircle:YES]; 
    [self.circleView.layer addSublayer:self.shapeLayer]; 
} 


- (IBAction)toggleShape:(UIButton *)sender { 
    [self.shapeLayer toggleShape]; 
} 

Dự án có thể tìm thấy ở đây, http://jmp.sh/iK3kuVs.

+0

Tôi có thể chia sẻ đơn giản của tôi 'func bezierPathWithPolygonInRect (rect: CGRect, numberOfSides: Int) -> UIBezierPath {' phương thức – coco

9

Cách tiếp cận khác là sử dụng liên kết hiển thị. Nó giống như một bộ đếm thời gian, ngoại trừ nó được phối hợp với bản cập nhật của màn hình.Sau đó, bạn có trình xử lý của liên kết hiển thị sửa đổi chế độ xem theo nội dung hiển thị ở bất kỳ điểm cụ thể nào của hoạt ảnh. Ví dụ, nếu bạn muốn tạo hiệu ứng làm tròn các góc của mặt nạ từ 0 đến 50 điểm, bạn có thể làm như sau, trong đó percent là giá trị nằm trong khoảng từ 0.0 đến 1.0 cho biết phần trăm của hoạt ảnh là thực hiện:

let path = UIBezierPath(rect: imageView.bounds) 
let mask = CAShapeLayer() 
mask.path = path.CGPath 
imageView.layer.mask = mask 

let animation = AnimationDisplayLink(duration: 0.5) { percent in 
    let cornerRadius = percent * 50.0 
    let path = UIBezierPath(roundedRect: self.imageView.bounds, cornerRadius: cornerRadius) 
    mask.path = path.CGPath 
} 

đâu:

class AnimationDisplayLink : NSObject { 
    var animationDuration: CGFloat 
    var animationHandler: (percent: CGFloat) ->() 
    var completionHandler: (() ->())? 

    private var startTime: CFAbsoluteTime! 
    private var displayLink: CADisplayLink! 

    init(duration: CGFloat, animationHandler: (percent: CGFloat)->(), completionHandler: (()->())? = nil) { 
     animationDuration = duration 
     self.animationHandler = animationHandler 
     self.completionHandler = completionHandler 

     super.init() 

     startDisplayLink() 
    } 

    private func startDisplayLink() { 
     startTime = CFAbsoluteTimeGetCurrent() 
     displayLink = CADisplayLink(target: self, selector: "handleDisplayLink:") 
     displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes) 
    } 

    private func stopDisplayLink() { 
     displayLink.invalidate() 
     displayLink = nil 
    } 

    func handleDisplayLink(displayLink: CADisplayLink) { 
     let elapsed = CFAbsoluteTimeGetCurrent() - startTime 
     var percent = CGFloat(elapsed)/animationDuration 

     if percent >= 1.0 { 
      stopDisplayLink() 
      animationHandler(percent: 1.0) 
      completionHandler?() 
     } else { 
      animationHandler(percent: percent) 
     } 
    } 
} 

các nhân đức của cách tiếp cận liên kết hiển thị là nó có thể được sử dụng để animate một số tài sản đó là khác unanimatable. Nó cũng cho phép bạn đọc chính xác trạng thái tạm thời trong khi hoạt ảnh.

Nếu bạn có thể sử dụng CAAnimation hoặc UIKit hoạt ảnh dựa trên khối, đó có thể là cách để thực hiện. Nhưng liên kết hiển thị đôi khi có thể là một cách tiếp cận dự phòng tốt.

+0

Điều này có thể đến một ngày rất tiện dụng, khi 'CAAnimation' và' UIKit' sẽ không xử lý điều gì đó! – coco

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