2015-02-10 224 views
6

我發現在兩個狀態之間動畫UIImageView是棘手的:它的原始矩形框和用UIBezierPath創建的新形狀。提到了許多不同的技術,其中大部分都不適合我。兩個貝塞爾路徑形狀之間的動畫

首先是使用UIView塊動畫不能工作的認識;顯然不能在animateWithDuration:塊中執行子圖層動畫。 (見herehere

剩下CAAnimation,具體的子類如CABasicAnimation。我很快就意識到,從不具有CAShapeLayer的視圖(例如參見here)的視圖無法生成動畫。

而且他們不能只是任意兩個形狀圖層路徑,而是「動畫形狀圖層的路徑時,才能保證當你從喜歡動畫喜歡的工作」(見here

隨着在地方,來了更現實的問題,比如如何使用fromValuetoValue(他們應該是一個CAShapeLayerCGPath?)什麼的動畫添加到(在layer,或者mask?)等

似乎有這麼多變數;哪種組合會給我我想要的動畫?

回答

11

第一個重點是構造兩個貝塞爾路徑,所以矩形是一個(平凡)類似於更復雜的形狀。

// 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) 

比較簡單的「小事」貝塞爾路徑,即創建一個矩形,是完全一樣的,但controlPoint被設定,它似乎不應該存在:

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

(嘗試取出addQuadCurveToPoint線得到一個很奇怪的動畫)

最後,動畫命令:

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") 

如果有人有興趣,該項目是here

+0

感謝您的例子,以及您的問題中的所有鏈接。閱讀圍繞這個問題的討論很有意思,也很有幫助。 – rdelmar 2015-02-10 17:12:57

+0

如何在動畫命令中設置tovalue(myBezierTrivial.CGPath)? – ABJ 2018-01-01 06:08:20

3

我的靈感來自於您的示例,嘗試使用您的答案中提及的技術和一些鏈接來圈選動畫。我打算將其擴展爲多邊形動畫的更一般的圓,但目前它只適用於正方形。我有一個名爲RDPolyCircle(CAShapeLayer的子類)的類,它負擔繁重。下面是它的代碼,

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

從視圖控制器,我創建這個層的一個實例,並將其添加到一個簡單視圖的圖層,然後做一個按鈕推動畫,

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

該項目可在這裏找到,http://jmp.sh/iK3kuVs

+0

我可以分享我簡單的'func bezierPathWithPolygonInRect(rect:CGRect,numberOfSides:Int) - > UIBezierPath {'方法 – coco 2015-02-11 15:51:05

9

另一種方法是使用顯示鏈接。它就像一個計時器,除了它與顯示更新協調。然後您可以根據顯示鏈接的處理程序在動畫的任何特定點處根據它應該看起來的樣子來修改視圖。例如,如果要將蒙版邊角的圓角從0到50點進行動畫製作,則可以執行以下操作,其中percent的值介於0.0和1.0之間,表示動畫的百分比是完成:

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 
} 

其中:

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) 
     } 
    } 
} 

美德顯示鏈接的方法之一是,它可以被用來製作動畫的一些屬性,否則unanimatable。它還可以讓你在動畫期間精確地指定臨時狀態。

如果你可以使用CAAnimationUIKit基於塊的動畫,這可能是一條路。但顯示鏈接有時可能是一種很好的後備方法。

+0

當CAAnimation和UIKit不能處理某些事情時,這可能會非常方便! – coco 2015-02-11 15:52:45