1
1
mirror of https://github.com/exyte/Macaw.git synced 2024-10-10 19:37:32 +03:00

Animation play/stop

This commit is contained in:
Victor Sukochev 2016-04-06 17:18:30 +06:00
parent 9dc0761d1d
commit 3d0317e9ee
6 changed files with 209 additions and 122 deletions

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10116" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="8Ku-fM-19f">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10116" systemVersion="15E65" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="8Ku-fM-19f">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
@ -77,7 +77,27 @@
<view key="view" contentMode="scaleToFill" id="y1S-j5-FMS" customClass="ThirdPageCustomView" customModule="Example" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="myZ-CN-l6C">
<rect key="frame" x="276" y="544" width="48" height="22"/>
<constraints>
<constraint firstAttribute="width" constant="48" id="Jge-rD-lfB"/>
<constraint firstAttribute="height" constant="22" id="LHt-QV-KDv"/>
</constraints>
<state key="normal" title="Play"/>
<connections>
<action selector="onPlayerBtnClicked" destination="y1S-j5-FMS" eventType="touchUpInside" id="69e-Z7-WVS"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="myZ-CN-l6C" firstAttribute="centerX" secondItem="y1S-j5-FMS" secondAttribute="centerX" id="iiN-tB-5cf"/>
<constraint firstItem="1ea-S4-NTm" firstAttribute="top" secondItem="myZ-CN-l6C" secondAttribute="bottom" constant="34" id="sUz-cy-GZo"/>
</constraints>
<connections>
<outlet property="playerBtn" destination="myZ-CN-l6C" id="mBO-we-5tC"/>
</connections>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="asF-Da-v23" userLabel="First Responder" sceneMemberID="firstResponder"/>

View File

@ -5,6 +5,23 @@ import Macaw
class ThirdPageCustomView: MacawView {
@IBOutlet var playerBtn: UIButton?
let animations: [Animatable]
var paused = true
@IBAction func onPlayerBtnClicked() {
paused = !paused
if paused {
playerBtn?.setTitle("Play", forState: .Normal)
animations.pause()
} else {
playerBtn?.setTitle("Pause", forState: .Normal)
animations.play()
}
}
required init?(coder aDecoder: NSCoder) {
func cloudExample() -> Group {
@ -131,8 +148,6 @@ class ThirdPageCustomView: MacawView {
pos: Transform().move(-80, my: -100)
)
super.init(node: group, coder: aDecoder)
// Animation 1
// let cloud1ShapeAnimation = Animation(observableValue: cloud1.posProperty,
// finalValue: Transform.move(120, my: 320).scale(0.3, sy: 0.3),
@ -152,24 +167,29 @@ class ThirdPageCustomView: MacawView {
let cloud3ShapeAnimation = PathAnimation(observableValue: cloud3.posProperty,
path: path,
animationDuration: 3.0)
let animation = [
let animation1 = [
// cloud1ShapeAnimation,
cloud2ShapeAnimation.easeIn(),
cloud3ShapeAnimation.easeOut()
].animationSequence().looped().infiniteLoop()
super.addAnimation(animation)
// Animation 2
let cloud4ShapeAnimation = PathAnimation(observableValue: cloud4.posProperty,
let animation2 = PathAnimation(observableValue: cloud4.posProperty,
function: { t in
return Transform.move(50.0 + 300 * t, my: 100 + 50 * sin(M_PI * t * 6.0)).scale(0.15, sy: 0.15)
}, animationDuration: 3.0).looped().infiniteLoop()
super.addAnimation(cloud4ShapeAnimation)
animations = [animation1, animation2]
super.init(node: group, coder: aDecoder)
super.addAnimation(animation1, autoPlay: false)
super.addAnimation(animation2, autoPlay: false)
}
required init?(node: Node, coder aDecoder: NSCoder) {
animations = []
super.init(node: node, coder: aDecoder)
}
}

View File

@ -1,37 +1,64 @@
import Foundation
public extension SequenceType where Generator.Element: Animatable {
func play() {
self.forEach { animation in
animation.play()
}
}
func pause() {
self.forEach { animation in
animation.pause()
}
}
func remove() {
self.forEach { animation in
animation.remove()
}
}
}
public class Animatable {
func animate(progress: Double) {}
func getDuration() -> Double{ return 0}
var shouldBeRemoved = false
var paused = false
public var currentProgress: Double?
func animate(progress: Double) { }
func getDuration() -> Double { return 0 }
func play() { paused = false }
func pause() { paused = true }
func remove() { shouldBeRemoved = true }
}
public class Animation<T: Interpolable>: Animatable {
let value: ObservableValue<T>
let start: T
let final: T
let duration: Double
public required init(observableValue: ObservableValue<T>, startValue: T, finalValue: T, animationDuration: Double) {
value = observableValue
start = startValue
final = finalValue
duration = animationDuration
}
public convenience init(observableValue: ObservableValue<T>, finalValue: T, animationDuration: Double) {
self.init(observableValue: observableValue, startValue: observableValue.get(), finalValue: finalValue, animationDuration: animationDuration )
}
public override func animate(progress: Double) {
value.set(start.interpolate(final, progress: progress))
}
public override func getDuration() -> Double {
return duration
}
let value: ObservableValue<T>
let start: T
let final: T
let duration: Double
public required init(observableValue: ObservableValue<T>, startValue: T, finalValue: T, animationDuration: Double) {
value = observableValue
start = startValue
final = finalValue
duration = animationDuration
}
public convenience init(observableValue: ObservableValue<T>, finalValue: T, animationDuration: Double) {
self.init(observableValue: observableValue, startValue: observableValue.get(), finalValue: finalValue, animationDuration: animationDuration)
}
public override func animate(progress: Double) {
value.set(start.interpolate(final, progress: progress))
}
public override func getDuration() -> Double {
return duration
}
}

View File

@ -2,49 +2,69 @@ import Foundation
import QuartzCore
class AnimationLoop {
var displayLink: CADisplayLink?
var animationSubscriptions: [AnimationSubscription] = []
var rendererCall: (()->())?
init() {
displayLink = CADisplayLink(target: self, selector: #selector(onFrameUpdate(_:)))
displayLink?.paused = false
displayLink?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)
}
dynamic private func onFrameUpdate(displayLink: CADisplayLink) {
var toRemove = [AnimationSubscription]()
animationSubscriptions.forEach { subscription in
if subscription.startTime == .None {
subscription.startTime = displayLink.timestamp
}
guard let startTime = subscription.startTime else {
return
}
let timePosition = displayLink.timestamp - startTime
let position = timePosition / subscription.anim.getDuration()
if position > 1.0 {
toRemove.append(subscription)
}
subscription.moveToTimeFrame(position, advance: 0)
}
rendererCall?()
// Removing
toRemove.forEach { subsription in
if let index = animationSubscriptions.indexOf ({ $0 === subsription }) {
animationSubscriptions.removeAtIndex(index)
}
}
}
var displayLink: CADisplayLink?
var animationSubscriptions: [AnimationSubscription] = []
var rendererCall: (() -> ())?
init() {
displayLink = CADisplayLink(target: self, selector: #selector(onFrameUpdate(_:)))
displayLink?.paused = false
displayLink?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)
}
dynamic private func onFrameUpdate(displayLink: CADisplayLink) {
var toRemove = [AnimationSubscription]()
animationSubscriptions.forEach { subscription in
// Handle manually set position
if !subscription.anim.paused {
if let pausedPosition = subscription.anim.currentProgress {
subscription.anim.currentProgress = .None
subscription.startTime = displayLink.timestamp - subscription.anim.getDuration() * pausedPosition
subscription.moveToTimeFrame(pausedPosition)
return
}
}
// Calculating current position
if subscription.startTime == .None {
subscription.startTime = displayLink.timestamp
}
guard let startTime = subscription.startTime else {
return
}
let timePosition = displayLink.timestamp - startTime
let position = timePosition / subscription.anim.getDuration()
if position > 1.0 || subscription.anim.shouldBeRemoved {
toRemove.append(subscription)
}
// Saving paused position
if subscription.anim.paused {
if subscription.anim.currentProgress == .None {
subscription.anim.currentProgress = position
}
return
}
subscription.moveToTimeFrame(position)
}
rendererCall?()
// Removing
toRemove.forEach { subsription in
if let index = animationSubscriptions.indexOf({ $0 === subsription }) {
animationSubscriptions.removeAtIndex(index)
}
}
}
}

View File

@ -1,16 +1,17 @@
import Foundation
class AnimationSubscription {
let anim: Animatable
var startTime: CFTimeInterval?
init( animation: Animatable ) {
anim = animation
}
func moveToTimeFrame(position: Double, advance: Double) {
anim.animate(position)
}
let anim: Animatable
var startTime: CFTimeInterval?
init(animation: Animatable, paused: Bool = false) {
anim = animation
anim.paused = paused
}
func moveToTimeFrame(position: Double) {
anim.animate(position)
}
}

View File

@ -3,35 +3,34 @@ import UIKit
public class MacawView: UIView {
var node: Node!
var context: RenderContext!
var renderer: NodeRenderer!
var loop: AnimationLoop?
public required init?(node: Node, coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.node = node
self.context = RenderContext(view: self)
self.renderer = RenderUtils.createNodeRenderer(node, context: context)!
self.loop = AnimationLoop()
self.loop?.rendererCall = {
self.setNeedsDisplay()
}
var node: Node!
var context: RenderContext!
var renderer: NodeRenderer!
var loop: AnimationLoop?
}
public convenience required init?(coder aDecoder: NSCoder) {
self.init(node: Group(pos: Transform()), coder: aDecoder)
}
public required init?(node: Node, coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
override public func drawRect(rect: CGRect) {
self.context.cgContext = UIGraphicsGetCurrentContext()
renderer.render()
}
public func addAnimation(animation: Animatable) {
let subscription = AnimationSubscription(animation: animation)
self.loop?.animationSubscriptions.append(subscription)
}
self.node = node
self.context = RenderContext(view: self)
self.renderer = RenderUtils.createNodeRenderer(node, context: context)!
self.loop = AnimationLoop()
self.loop?.rendererCall = {
self.setNeedsDisplay()
}
}
public convenience required init?(coder aDecoder: NSCoder) {
self.init(node: Group(pos: Transform()), coder: aDecoder)
}
override public func drawRect(rect: CGRect) {
self.context.cgContext = UIGraphicsGetCurrentContext()
renderer.render()
}
public func addAnimation(animation: Animatable, autoPlay: Bool = true) {
let subscription = AnimationSubscription(animation: animation, paused: !autoPlay)
self.loop?.animationSubscriptions.append(subscription)
}
}