2015-03-13 50 views
0

我向UIView添加了幾個圖層,現在希望按順序對這些圖層進行動畫處理。Swift:從UIView序列動畫層

我有5層動畫,這些都是圈子。我使用observable字段來獲取值的更改通知,並開始使用needsDisplayForKey()和actionForKey()方法進行動畫處理。

可觀察到的字段:

@NSManaged private var _startAngle: CGFloat 

@NSManaged private var _endAngle: CGFloat 

目前所有的層動畫的同時,當我改變每個層可觀察的領域之一。我希望改變這一點,並希望在2.5秒的時間內依次對所有這些動畫進行動畫處理。所以,當一個動畫完成後,我希望動畫下一層等。

現在我知道動畫已完成的唯一方法是使用animationDidStop()方法。然而,這種方式我無法知道其他動畫的圖層。你們知道這是一個好的解決方案嗎?

我的層:

class HourLayer: CAShapeLayer { 

@NSManaged private var _startAngle: CGFloat 

@NSManaged private var _endAngle: CGFloat 

@NSManaged private var _fillColor: UIColor 

@NSManaged private var _strokeWidth: CGFloat 

@NSManaged private var _strokeColor: UIColor 

@NSManaged private var _duration: CFTimeInterval 

private var _previousEndAngle: CGFloat = 0.0 
private var _previousStartAngle: CGFloat = 0.0 

private let _lenghtOfArc: CGFloat = CGFloat(2 * M_PI) 

internal var StartAngle: CGFloat { 
    get { 
     return _startAngle 
    } 
    set { 
     _startAngle = newValue 
    } 
} 

internal var EndAngle: CGFloat { 
    get { 
     return _endAngle 
    } 
    set { 
     _endAngle = newValue 
    } 
} 

internal var FillColor: UIColor { 
    get { 
     return _fillColor 
    } 
    set { 
     _fillColor = newValue 
    } 
} 

internal var StrokeWidth: CGFloat { 
    get { 
     return _strokeWidth 
    } 
    set { 
     _strokeWidth = newValue 
    } 
} 

internal var StrokeColor: UIColor { 
    get { 
     return _strokeColor 
    } 
    set { 
     _strokeColor = newValue 
    } 
} 

internal var DurationOfAnimation: CFTimeInterval { 
    get { 
     return _duration 
    } 
    set { 
     _duration = newValue 
    } 
} 

required override init!() { 
    super.init() 

    self._startAngle = 0.0 
    self._endAngle = 0.0 
    self._fillColor = UIColor.clearColor() 
    self._strokeWidth = 4.0 
    self._strokeColor = UIColor.blackColor() 
    self._duration = 1.0 
} 

required init(coder aDecoder: NSCoder) { 
    super.init(coder: aDecoder) 
    fatalError("required init(:coder) is missing") 
} 

override init!(layer: AnyObject!) { 
    super.init(layer: layer) 

    if layer.isKindOfClass(HourLayer) { 
     var other: HourLayer = layer as HourLayer 
     self._startAngle = other._startAngle 
     self._endAngle = other._endAngle 
     self._fillColor = other._fillColor 
     self._strokeWidth = other._strokeWidth 
     self._strokeColor = other._strokeColor 
     self._duration = other._duration 
    } 
} 

override var frame: CGRect { 
    didSet { 
     self.setNeedsLayout() 
     self.setNeedsDisplay() 
    } 
} 

override func actionForKey(event: String!) -> CAAction! { 
    if event == "_startAngle" || event == "_endAngle" { 
     return makeAnimationForKey(event) 
    } 
    return super.actionForKey(event) 
} 

private func makeAnimationForKey(event: String) -> CABasicAnimation { 
    // We want to animate the strokeEnd property of the circleLayer 
    let animation: CABasicAnimation = CABasicAnimation(keyPath: event) 
    // Set the animation duration appropriately 
    animation.duration = self._duration 

    // When no animation has been triggered and the presentation layer does not exist yet. 
    if self.presentationLayer() == nil { 
     if event == "_startAngle" { 
      animation.fromValue = self._previousStartAngle 
      self._previousStartAngle = self._startAngle 
     } else if event == "_endAngle" { 
      animation.fromValue = self._previousEndAngle 
      self._previousEndAngle = self._endAngle 
     } 
    } else { 
     animation.fromValue = self.presentationLayer()!.valueForKey(event) 
    } 
    animation.removedOnCompletion = false 
    animation.delegate = self 
    // Do a linear animation (i.e. the speed of the animation stays the same) 
    animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) 
    return animation 
} 

override class func needsDisplayForKey(key: String) -> Bool { 
    if key == "_startAngle" || key == "_endAngle" { 
     return true 
    } 
    return super.needsDisplayForKey(key) 
} 

override func drawInContext(ctx: CGContext!) { 
    var center: CGPoint = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2) 
    var radius: CGFloat = CGFloat((CGFloat(self.bounds.width) - CGFloat(_strokeWidth))/2) 

    // Set the stroke color 
    CGContextSetStrokeColorWithColor(ctx, _strokeColor.CGColor) 

    // Set the line width 
    CGContextSetLineWidth(ctx, _strokeWidth) 

    // Set the fill color (if you are filling the circle) 
    CGContextSetFillColorWithColor(ctx, UIColor.clearColor().CGColor) 

    // Draw the arc around the circle 
    CGContextAddArc(ctx, center.x, center.y, radius, _startAngle, _lenghtOfArc * _endAngle, 0) 

    // Draw the arc 
    CGContextDrawPath(ctx, kCGPathStroke) // or kCGPathFillStroke to fill and stroke the circle 

    super.drawInContext(ctx) 
} 

override func animationDidStop(anim: CAAnimation!, finished flag: Bool) { 
    // Trigger animation for other layer by setting their values for _startAngle and _endAngle. 
} 
} 
+0

製作動畫時請分享代碼 – Arbitur 2015-03-13 09:43:06

回答

0

蘋果的文檔指出以下幾點:https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreAnimation_guide/CreatingBasicAnimations/CreatingBasicAnimations.html

如果你想鏈兩個動畫在一起,這樣一個啓動時的其他結束,不要使用動畫通知。相反,使用動畫對象的beginTime屬性在所需的時間啓動每個動畫對象。要將兩個動畫鏈接在一起,請將第二個動畫的開始時間設置爲第一個動畫的結束時間。有關動畫和計時值的更多信息,請參閱自定義動畫的計時。

我有一些東西在工作,但我不覺得它是最理想的解決方案。我寧願認爲通知是一個更好的方式,但嘿,如果蘋果說得那麼正確... ...

另外我也必須設置:animation.fillMode = kCAFillModeBackwards 否則模型層將使圓達到他們的最後動畫之前的狀態,並且只有稍後動畫纔會觸發。

編輯

好了,所以我發現,使用多層爲我的動畫是緩慢的(我用多層每個視圖,所以一個圓圈,我想有生命的和我有「時間約六。那8次是非常慢的(6x8 = 48動畫)。只需要更快的動畫就可以在一個圖層中動畫。

0

我的解決辦法是在Objective-C,你需要採取這種爲迅速。試一試: -
創建一個NSOperationQueue並將動畫調用添加到此隊列中。設置setMaxConcurrentOperationCount = 1以確保順序執行。

NSOperationQueue *animQueue = [[NSOperationQueue alloc] init]; 
[animQueue setMaxConcurrentOperationCount:1]; 

現在從功能actionForKey而不是調用動畫功能,將它添加到我沒有測試此操作隊列

NSBlockOperation *animOperation = [[NSBlockOperation alloc] init]; 
__weak NSBlockOperation *weakOp = animOperation; 
[weakOp addExecutionBlock:^{ 
    [self makeAnimationForKey:event)] 
}]; 
[animQueue addOperation:animOperation]; 

,但我認爲它應該工作。

+0

您知道我是否可以將此OperationQueue從UIView傳遞到CAShapeLayers而沒有任何問題? – Orion 2015-03-13 11:45:34

+0

不能肯定地說,但從我的理解來看,通過將方法添加到'OperationQueue'中,我們實際上並沒有將任何對象傳遞給其他接收器,我們只是序列化執行。所以無論你想使用'UIView'還是'CAShapeLayer';應該沒有什麼區別。試試吧,不要花太多時間。 – Gandalf 2015-03-13 12:04:32

+0

將隊列傳遞給添加圖層的UIView中的CAShapeLayers時,它似乎不起作用。 – Orion 2015-03-13 12:42:28

0

試試這個..

UIview.animateWithDuration(2.0, delay 2.0, options: .CurveEaseIn, animations: { 

//enter item to be animated here 

}, completion: nil) 
+0

我一直在研究這個和我在這裏給出的答案:http://stackoverflow.com/questions/28784108/animating-strokeend-in-a-cashapelayer-using-an-animation-block-in-swift/29099102#29099102顯示您不能使用此方法爲endAngle屬性設置動畫。 – Orion 2015-03-17 14:03:15