mirror of
https://github.com/exyte/Macaw.git
synced 2024-10-11 03:49:05 +03:00
Animation play/stop
This commit is contained in:
parent
9dc0761d1d
commit
3d0317e9ee
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?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>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
@ -77,7 +77,27 @@
|
|||||||
<view key="view" contentMode="scaleToFill" id="y1S-j5-FMS" customClass="ThirdPageCustomView" customModule="Example" customModuleProvider="target">
|
<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"/>
|
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<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"/>
|
<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>
|
</view>
|
||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="asF-Da-v23" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="asF-Da-v23" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
@ -5,6 +5,23 @@ import Macaw
|
|||||||
|
|
||||||
class ThirdPageCustomView: MacawView {
|
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) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
|
||||||
func cloudExample() -> Group {
|
func cloudExample() -> Group {
|
||||||
@ -131,8 +148,6 @@ class ThirdPageCustomView: MacawView {
|
|||||||
pos: Transform().move(-80, my: -100)
|
pos: Transform().move(-80, my: -100)
|
||||||
)
|
)
|
||||||
|
|
||||||
super.init(node: group, coder: aDecoder)
|
|
||||||
|
|
||||||
// Animation 1
|
// Animation 1
|
||||||
// let cloud1ShapeAnimation = Animation(observableValue: cloud1.posProperty,
|
// let cloud1ShapeAnimation = Animation(observableValue: cloud1.posProperty,
|
||||||
// finalValue: Transform.move(120, my: 320).scale(0.3, sy: 0.3),
|
// 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,
|
let cloud3ShapeAnimation = PathAnimation(observableValue: cloud3.posProperty,
|
||||||
path: path,
|
path: path,
|
||||||
animationDuration: 3.0)
|
animationDuration: 3.0)
|
||||||
let animation = [
|
let animation1 = [
|
||||||
// cloud1ShapeAnimation,
|
// cloud1ShapeAnimation,
|
||||||
cloud2ShapeAnimation.easeIn(),
|
cloud2ShapeAnimation.easeIn(),
|
||||||
cloud3ShapeAnimation.easeOut()
|
cloud3ShapeAnimation.easeOut()
|
||||||
].animationSequence().looped().infiniteLoop()
|
].animationSequence().looped().infiniteLoop()
|
||||||
super.addAnimation(animation)
|
|
||||||
|
|
||||||
// Animation 2
|
// Animation 2
|
||||||
|
|
||||||
let cloud4ShapeAnimation = PathAnimation(observableValue: cloud4.posProperty,
|
let animation2 = PathAnimation(observableValue: cloud4.posProperty,
|
||||||
function: { t in
|
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)
|
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()
|
}, 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) {
|
required init?(node: Node, coder aDecoder: NSCoder) {
|
||||||
|
animations = []
|
||||||
super.init(node: node, coder: aDecoder)
|
super.init(node: node, coder: aDecoder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,37 +1,64 @@
|
|||||||
import Foundation
|
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 {
|
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 {
|
public class Animation<T: Interpolable>: Animatable {
|
||||||
|
|
||||||
let value: ObservableValue<T>
|
let value: ObservableValue<T>
|
||||||
|
|
||||||
let start: T
|
let start: T
|
||||||
let final: T
|
let final: T
|
||||||
let duration: Double
|
let duration: Double
|
||||||
|
|
||||||
public required init(observableValue: ObservableValue<T>, startValue: T, finalValue: T, animationDuration: Double) {
|
public required init(observableValue: ObservableValue<T>, startValue: T, finalValue: T, animationDuration: Double) {
|
||||||
value = observableValue
|
value = observableValue
|
||||||
start = startValue
|
start = startValue
|
||||||
final = finalValue
|
final = finalValue
|
||||||
duration = animationDuration
|
duration = animationDuration
|
||||||
|
}
|
||||||
}
|
|
||||||
|
public convenience init(observableValue: ObservableValue<T>, finalValue: T, animationDuration: Double) {
|
||||||
public convenience init(observableValue: ObservableValue<T>, finalValue: T, animationDuration: Double) {
|
self.init(observableValue: observableValue, startValue: observableValue.get(), finalValue: finalValue, animationDuration: animationDuration)
|
||||||
self.init(observableValue: observableValue, startValue: observableValue.get(), finalValue: finalValue, animationDuration: animationDuration )
|
}
|
||||||
}
|
|
||||||
|
public override func animate(progress: Double) {
|
||||||
public override func animate(progress: Double) {
|
|
||||||
|
value.set(start.interpolate(final, progress: progress))
|
||||||
value.set(start.interpolate(final, progress: progress))
|
}
|
||||||
}
|
|
||||||
|
public override func getDuration() -> Double {
|
||||||
public override func getDuration() -> Double {
|
return duration
|
||||||
return duration
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,49 +2,69 @@ import Foundation
|
|||||||
import QuartzCore
|
import QuartzCore
|
||||||
|
|
||||||
class AnimationLoop {
|
class AnimationLoop {
|
||||||
|
|
||||||
var displayLink: CADisplayLink?
|
var displayLink: CADisplayLink?
|
||||||
|
|
||||||
var animationSubscriptions: [AnimationSubscription] = []
|
var animationSubscriptions: [AnimationSubscription] = []
|
||||||
var rendererCall: (()->())?
|
var rendererCall: (() -> ())?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
displayLink = CADisplayLink(target: self, selector: #selector(onFrameUpdate(_:)))
|
displayLink = CADisplayLink(target: self, selector: #selector(onFrameUpdate(_:)))
|
||||||
displayLink?.paused = false
|
displayLink?.paused = false
|
||||||
displayLink?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)
|
displayLink?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
dynamic private func onFrameUpdate(displayLink: CADisplayLink) {
|
dynamic private func onFrameUpdate(displayLink: CADisplayLink) {
|
||||||
|
|
||||||
var toRemove = [AnimationSubscription]()
|
var toRemove = [AnimationSubscription]()
|
||||||
|
|
||||||
animationSubscriptions.forEach { subscription in
|
animationSubscriptions.forEach { subscription in
|
||||||
|
|
||||||
if subscription.startTime == .None {
|
// Handle manually set position
|
||||||
subscription.startTime = displayLink.timestamp
|
if !subscription.anim.paused {
|
||||||
}
|
if let pausedPosition = subscription.anim.currentProgress {
|
||||||
|
subscription.anim.currentProgress = .None
|
||||||
guard let startTime = subscription.startTime else {
|
subscription.startTime = displayLink.timestamp - subscription.anim.getDuration() * pausedPosition
|
||||||
return
|
subscription.moveToTimeFrame(pausedPosition)
|
||||||
}
|
return
|
||||||
|
}
|
||||||
let timePosition = displayLink.timestamp - startTime
|
}
|
||||||
let position = timePosition / subscription.anim.getDuration()
|
|
||||||
|
// Calculating current position
|
||||||
if position > 1.0 {
|
if subscription.startTime == .None {
|
||||||
toRemove.append(subscription)
|
subscription.startTime = displayLink.timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
subscription.moveToTimeFrame(position, advance: 0)
|
guard let startTime = subscription.startTime else {
|
||||||
}
|
return
|
||||||
|
}
|
||||||
rendererCall?()
|
|
||||||
|
let timePosition = displayLink.timestamp - startTime
|
||||||
// Removing
|
let position = timePosition / subscription.anim.getDuration()
|
||||||
toRemove.forEach { subsription in
|
|
||||||
if let index = animationSubscriptions.indexOf ({ $0 === subsription }) {
|
if position > 1.0 || subscription.anim.shouldBeRemoved {
|
||||||
animationSubscriptions.removeAtIndex(index)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class AnimationSubscription {
|
class AnimationSubscription {
|
||||||
|
|
||||||
let anim: Animatable
|
let anim: Animatable
|
||||||
|
|
||||||
var startTime: CFTimeInterval?
|
var startTime: CFTimeInterval?
|
||||||
|
|
||||||
init( animation: Animatable ) {
|
init(animation: Animatable, paused: Bool = false) {
|
||||||
anim = animation
|
anim = animation
|
||||||
}
|
anim.paused = paused
|
||||||
|
}
|
||||||
func moveToTimeFrame(position: Double, advance: Double) {
|
|
||||||
anim.animate(position)
|
func moveToTimeFrame(position: Double) {
|
||||||
}
|
anim.animate(position)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,35 +3,34 @@ import UIKit
|
|||||||
|
|
||||||
public class MacawView: UIView {
|
public class MacawView: UIView {
|
||||||
|
|
||||||
var node: Node!
|
var node: Node!
|
||||||
var context: RenderContext!
|
var context: RenderContext!
|
||||||
var renderer: NodeRenderer!
|
var renderer: NodeRenderer!
|
||||||
var loop: AnimationLoop?
|
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
public required init?(node: Node, coder aDecoder: NSCoder) {
|
||||||
|
super.init(coder: aDecoder)
|
||||||
public convenience required init?(coder aDecoder: NSCoder) {
|
|
||||||
self.init(node: Group(pos: Transform()), coder: aDecoder)
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func drawRect(rect: CGRect) {
|
self.node = node
|
||||||
self.context.cgContext = UIGraphicsGetCurrentContext()
|
self.context = RenderContext(view: self)
|
||||||
renderer.render()
|
self.renderer = RenderUtils.createNodeRenderer(node, context: context)!
|
||||||
}
|
self.loop = AnimationLoop()
|
||||||
|
self.loop?.rendererCall = {
|
||||||
public func addAnimation(animation: Animatable) {
|
self.setNeedsDisplay()
|
||||||
let subscription = AnimationSubscription(animation: animation)
|
}
|
||||||
self.loop?.animationSubscriptions.append(subscription)
|
}
|
||||||
}
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user