mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-06 17:00:13 +00:00
Lottie improvements
This commit is contained in:
parent
73d4647439
commit
309356b8c6
@ -35,7 +35,7 @@ public final class AnimationNode : ASDisplayNode {
|
|||||||
|
|
||||||
self.setViewBlock({
|
self.setViewBlock({
|
||||||
if let animationName = animationName, let url = getAppBundle().url(forResource: animationName, withExtension: "json"), let animation = Animation.filepath(url.path) {
|
if let animationName = animationName, let url = getAppBundle().url(forResource: animationName, withExtension: "json"), let animation = Animation.filepath(url.path) {
|
||||||
let view = AnimationView(animation: animation)
|
let view = AnimationView(animation: animation, configuration: LottieConfiguration(renderingEngine: .mainThread, decodingStrategy: .codable))
|
||||||
view.animationSpeed = self.speed
|
view.animationSpeed = self.speed
|
||||||
view.backgroundColor = .clear
|
view.backgroundColor = .clear
|
||||||
view.isOpaque = false
|
view.isOpaque = false
|
||||||
@ -63,7 +63,7 @@ public final class AnimationNode : ASDisplayNode {
|
|||||||
|
|
||||||
self.setViewBlock({
|
self.setViewBlock({
|
||||||
if let json = try? JSONSerialization.jsonObject(with: animationData, options: []) as? [String: Any], let animation = try? Animation(dictionary: json) {
|
if let json = try? JSONSerialization.jsonObject(with: animationData, options: []) as? [String: Any], let animation = try? Animation(dictionary: json) {
|
||||||
let view = AnimationView(animation: animation)
|
let view = AnimationView(animation: animation, configuration: LottieConfiguration(renderingEngine: .mainThread, decodingStrategy: .codable))
|
||||||
view.animationSpeed = self.speed
|
view.animationSpeed = self.speed
|
||||||
view.backgroundColor = .clear
|
view.backgroundColor = .clear
|
||||||
view.isOpaque = false
|
view.isOpaque = false
|
||||||
|
|||||||
@ -138,7 +138,7 @@ public final class LottieAnimationComponent: Component {
|
|||||||
self.didPlayToCompletion = false
|
self.didPlayToCompletion = false
|
||||||
|
|
||||||
if let url = getAppBundle().url(forResource: component.animation.name, withExtension: "json"), let animation = Animation.filepath(url.path) {
|
if let url = getAppBundle().url(forResource: component.animation.name, withExtension: "json"), let animation = Animation.filepath(url.path) {
|
||||||
let view = AnimationView(animation: animation)
|
let view = AnimationView(animation: animation, configuration: LottieConfiguration(renderingEngine: .mainThread, decodingStrategy: .codable))
|
||||||
switch component.animation.mode {
|
switch component.animation.mode {
|
||||||
case .still, .animateTransitionFromPrevious:
|
case .still, .animateTransitionFromPrevious:
|
||||||
view.loopMode = .playOnce
|
view.loopMode = .playOnce
|
||||||
|
|||||||
@ -112,7 +112,7 @@ private class StickerNode: ASDisplayNode {
|
|||||||
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 400.0, height: 400.0))
|
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 400.0, height: 400.0))
|
||||||
|
|
||||||
let pathPrefix = context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
|
let pathPrefix = context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
|
||||||
animationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: file.resource, isVideo: file.isVideoSticker), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix))
|
animationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: file.resource, isVideo: file.isVideoSticker), width: Int(fittedDimensions.width * 1.6), height: Int(fittedDimensions.height * 1.6), playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix))
|
||||||
|
|
||||||
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: context.account.postbox, file: file, small: false, size: fittedDimensions))
|
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: context.account.postbox, file: file, small: false, size: fittedDimensions))
|
||||||
|
|
||||||
@ -125,7 +125,8 @@ private class StickerNode: ASDisplayNode {
|
|||||||
let additionalAnimationNode = AnimatedStickerNode()
|
let additionalAnimationNode = AnimatedStickerNode()
|
||||||
|
|
||||||
let pathPrefix = context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(effect.resource.id)
|
let pathPrefix = context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(effect.resource.id)
|
||||||
additionalAnimationNode.setup(source: source, width: Int(fittedDimensions.width * 2.0), height: Int(fittedDimensions.height * 2.0), playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix))
|
let _ = pathPrefix
|
||||||
|
additionalAnimationNode.setup(source: source, width: Int(fittedDimensions.width * 1.4), height: Int(fittedDimensions.height * 1.4), playbackMode: .loop, mode: .direct(cachePathPrefix: nil))
|
||||||
self.additionalAnimationNode = additionalAnimationNode
|
self.additionalAnimationNode = additionalAnimationNode
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1082,7 +1082,7 @@ public final class StandaloneReactionAnimation: ASDisplayNode {
|
|||||||
self.addSubnode(additionalAnimationNode)
|
self.addSubnode(additionalAnimationNode)
|
||||||
|
|
||||||
if !isLarge, !avatarPeers.isEmpty, let url = getAppBundle().url(forResource: "effectavatar", withExtension: "json"), let composition = Animation.filepath(url.path) {
|
if !isLarge, !avatarPeers.isEmpty, let url = getAppBundle().url(forResource: "effectavatar", withExtension: "json"), let composition = Animation.filepath(url.path) {
|
||||||
let view = AnimationView(animation: composition)
|
let view = AnimationView(animation: composition, configuration: LottieConfiguration(renderingEngine: .mainThread, decodingStrategy: .codable))
|
||||||
view.animationSpeed = 1.0
|
view.animationSpeed = 1.0
|
||||||
view.backgroundColor = nil
|
view.backgroundColor = nil
|
||||||
view.isOpaque = false
|
view.isOpaque = false
|
||||||
|
|||||||
@ -11,7 +11,7 @@ final class LockView: UIButton, TGModernConversationInputMicButtonLock {
|
|||||||
guard let url = getAppBundle().url(forResource: "LockWait", withExtension: "json"), let animation = Animation.filepath(url.path)
|
guard let url = getAppBundle().url(forResource: "LockWait", withExtension: "json"), let animation = Animation.filepath(url.path)
|
||||||
else { return AnimationView() }
|
else { return AnimationView() }
|
||||||
|
|
||||||
let view = AnimationView(animation: animation)
|
let view = AnimationView(animation: animation, configuration: LottieConfiguration(renderingEngine: .mainThread, decodingStrategy: .codable))
|
||||||
view.loopMode = .autoReverse
|
view.loopMode = .autoReverse
|
||||||
view.backgroundColor = .clear
|
view.backgroundColor = .clear
|
||||||
view.isOpaque = false
|
view.isOpaque = false
|
||||||
@ -22,7 +22,7 @@ final class LockView: UIButton, TGModernConversationInputMicButtonLock {
|
|||||||
guard let url = getAppBundle().url(forResource: "Lock", withExtension: "json"), let animation = Animation.filepath(url.path)
|
guard let url = getAppBundle().url(forResource: "Lock", withExtension: "json"), let animation = Animation.filepath(url.path)
|
||||||
else { return AnimationView() }
|
else { return AnimationView() }
|
||||||
|
|
||||||
let view = AnimationView(animation: animation)
|
let view = AnimationView(animation: animation, configuration: LottieConfiguration(renderingEngine: .mainThread, decodingStrategy: .codable))
|
||||||
view.backgroundColor = .clear
|
view.backgroundColor = .clear
|
||||||
view.isOpaque = false
|
view.isOpaque = false
|
||||||
return view
|
return view
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
// Copyright © 2021 Airbnb Inc. All rights reserved.
|
// Copyright © 2021 Airbnb Inc. All rights reserved.
|
||||||
|
|
||||||
import QuartzCore
|
import QuartzCore
|
||||||
|
import UIKit
|
||||||
|
|
||||||
extension CALayer {
|
extension CALayer {
|
||||||
|
|
||||||
@ -77,6 +78,12 @@ extension CALayer {
|
|||||||
let animation = CABasicAnimation(keyPath: property.caLayerKeypath)
|
let animation = CABasicAnimation(keyPath: property.caLayerKeypath)
|
||||||
animation.fromValue = keyframeValue
|
animation.fromValue = keyframeValue
|
||||||
animation.toValue = keyframeValue
|
animation.toValue = keyframeValue
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
let maxFps = Float(UIScreen.main.maximumFramesPerSecond)
|
||||||
|
if maxFps > 61.0 {
|
||||||
|
animation.preferredFrameRateRange = CAFrameRateRange(minimum: maxFps, maximum: maxFps, preferred: maxFps)
|
||||||
|
}
|
||||||
|
}
|
||||||
return animation
|
return animation
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,6 +141,12 @@ extension CALayer {
|
|||||||
let calculationMode = try self.calculationMode(for: keyframes, context: context)
|
let calculationMode = try self.calculationMode(for: keyframes, context: context)
|
||||||
|
|
||||||
let animation = CAKeyframeAnimation(keyPath: property.caLayerKeypath)
|
let animation = CAKeyframeAnimation(keyPath: property.caLayerKeypath)
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
let maxFps = Float(UIScreen.main.maximumFramesPerSecond)
|
||||||
|
if maxFps > 61.0 {
|
||||||
|
animation.preferredFrameRateRange = CAFrameRateRange(minimum: maxFps, maximum: maxFps, preferred: maxFps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Position animations define a `CGPath` curve that should be followed,
|
// Position animations define a `CGPath` curve that should be followed,
|
||||||
// instead of animating directly between keyframe point values.
|
// instead of animating directly between keyframe point values.
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import QuartzCore
|
import QuartzCore
|
||||||
|
import UIKit
|
||||||
|
|
||||||
// MARK: - MainThreadAnimationLayer
|
// MARK: - MainThreadAnimationLayer
|
||||||
|
|
||||||
@ -112,6 +113,12 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer {
|
|||||||
let animation = CABasicAnimation(keyPath: event)
|
let animation = CABasicAnimation(keyPath: event)
|
||||||
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
||||||
animation.fromValue = presentation()?.currentFrame
|
animation.fromValue = presentation()?.currentFrame
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
let maxFps = Float(UIScreen.main.maximumFramesPerSecond)
|
||||||
|
if maxFps > 61.0 {
|
||||||
|
animation.preferredFrameRateRange = CAFrameRateRange(minimum: maxFps, maximum: maxFps, preferred: maxFps)
|
||||||
|
}
|
||||||
|
}
|
||||||
return animation
|
return animation
|
||||||
}
|
}
|
||||||
return super.action(forKey: event)
|
return super.action(forKey: event)
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import QuartzCore
|
import QuartzCore
|
||||||
|
import UIKit
|
||||||
|
|
||||||
// MARK: - LottieBackgroundBehavior
|
// MARK: - LottieBackgroundBehavior
|
||||||
|
|
||||||
@ -393,6 +394,42 @@ final public class AnimationView: AnimationViewBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var workaroundDisplayLink: CADisplayLink?
|
||||||
|
private var needsWorkaroundDisplayLink: Bool = false {
|
||||||
|
didSet {
|
||||||
|
if self.needsWorkaroundDisplayLink != oldValue {
|
||||||
|
if self.needsWorkaroundDisplayLink {
|
||||||
|
if workaroundDisplayLink == nil {
|
||||||
|
class WorkaroundDisplayLinkTarget {
|
||||||
|
private let f: () -> Void
|
||||||
|
|
||||||
|
init(_ f: @escaping () -> Void) {
|
||||||
|
self.f = f
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func update() {
|
||||||
|
self.f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.workaroundDisplayLink = CADisplayLink(target: WorkaroundDisplayLinkTarget { [weak self] in
|
||||||
|
let _ = self?.realtimeAnimationProgress
|
||||||
|
}, selector: #selector(WorkaroundDisplayLinkTarget.update))
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
let maxFps = Float(UIScreen.main.maximumFramesPerSecond)
|
||||||
|
self.workaroundDisplayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: maxFps, maximum: maxFps, preferred: maxFps)
|
||||||
|
}
|
||||||
|
self.workaroundDisplayLink?.add(to: .main, forMode: .common)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let workaroundDisplayLink = self.workaroundDisplayLink {
|
||||||
|
self.workaroundDisplayLink = nil
|
||||||
|
workaroundDisplayLink.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Plays the animation from its current state to the end.
|
/// Plays the animation from its current state to the end.
|
||||||
///
|
///
|
||||||
@ -1136,6 +1173,8 @@ final public class AnimationView: AnimationViewBase {
|
|||||||
animationLayer?.removeAnimation(forKey: activeAnimationName)
|
animationLayer?.removeAnimation(forKey: activeAnimationName)
|
||||||
updateAnimationFrame(pauseFrame)
|
updateAnimationFrame(pauseFrame)
|
||||||
animationContext = nil
|
animationContext = nil
|
||||||
|
|
||||||
|
self.needsWorkaroundDisplayLink = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates an in flight animation.
|
/// Updates an in flight animation.
|
||||||
@ -1258,6 +1297,12 @@ final public class AnimationView: AnimationViewBase {
|
|||||||
layerAnimation.fillMode = CAMediaTimingFillMode.both
|
layerAnimation.fillMode = CAMediaTimingFillMode.both
|
||||||
layerAnimation.repeatCount = loopMode.caAnimationConfiguration.repeatCount
|
layerAnimation.repeatCount = loopMode.caAnimationConfiguration.repeatCount
|
||||||
layerAnimation.autoreverses = loopMode.caAnimationConfiguration.autoreverses
|
layerAnimation.autoreverses = loopMode.caAnimationConfiguration.autoreverses
|
||||||
|
if #available(iOS 15.0, *) {
|
||||||
|
let maxFps = Float(UIScreen.main.maximumFramesPerSecond)
|
||||||
|
if maxFps > 61.0 {
|
||||||
|
layerAnimation.preferredFrameRateRange = CAFrameRateRange(minimum: maxFps, maximum: maxFps, preferred: maxFps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
layerAnimation.isRemovedOnCompletion = false
|
layerAnimation.isRemovedOnCompletion = false
|
||||||
if timeOffset != 0 {
|
if timeOffset != 0 {
|
||||||
@ -1269,6 +1314,7 @@ final public class AnimationView: AnimationViewBase {
|
|||||||
animationContext.closure.animationKey = activeAnimationName
|
animationContext.closure.animationKey = activeAnimationName
|
||||||
|
|
||||||
animationlayer.add(layerAnimation, forKey: activeAnimationName)
|
animationlayer.add(layerAnimation, forKey: activeAnimationName)
|
||||||
|
self.needsWorkaroundDisplayLink = true
|
||||||
updateRasterizationState()
|
updateRasterizationState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -48,7 +48,7 @@ public final class CompatibleAnimationView: UIView {
|
|||||||
|
|
||||||
@objc
|
@objc
|
||||||
public init(compatibleAnimation: CompatibleAnimation) {
|
public init(compatibleAnimation: CompatibleAnimation) {
|
||||||
animationView = AnimationView(animation: compatibleAnimation.animation)
|
animationView = AnimationView(animation: compatibleAnimation.animation, configuration: LottieConfiguration(renderingEngine: .mainThread, decodingStrategy: .codable))
|
||||||
self.compatibleAnimation = compatibleAnimation
|
self.compatibleAnimation = compatibleAnimation
|
||||||
super.init(frame: .zero)
|
super.init(frame: .zero)
|
||||||
commonInit()
|
commonInit()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user