提交 13f9dcb1 编写于 作者: lizhongyi_'s avatar lizhongyi_

animation -view 组件iOS平台 lottie-ios 库改为源码形式引入参与混编

上级 e282cc6f
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>
{
"deploymentTarget": "11.0",
"deploymentTarget": "13.0",
"validArchitectures": [
"arm64",
"x86_64"
......
......@@ -4,11 +4,11 @@
</view>
</template>
<script lang="uts">
import {
LottieAnimationView,
LottieAnimation,
LottieLoopMode
} from 'Lottie'
// import {
// LottieAnimationView,
// LottieAnimation,
// LottieLoopMode
// } from 'Lottie'
import {
URL
} from 'Foundation'
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>
// Created by Cal Stephens on 1/6/22.
// Copyright © 2022 Airbnb Inc. All rights reserved.
import QuartzCore
extension CAAnimation {
/// Creates a `CAAnimation` that wraps this animation,
/// applying timing-related configuration from the given `LayerAnimationContext`.
/// - This animation should start at the beginning of the animation and
/// last the entire duration of the animation. It will be trimmed and retimed
/// to match the current playback state / looping configuration of the animation view.
@nonobjc
func timed(with context: LayerAnimationContext, for layer: CALayer) -> CAAnimation {
// The base animation always has the duration of the full animation,
// since that's the time space where keyframing and interpolating happens.
// So we start with a simple animation timeline from 0% to 100%:
//
// ┌──────────────────────────────────┐
// │ baseAnimation │
// └──────────────────────────────────┘
// 0% 100%
//
let baseAnimation = self
baseAnimation.duration = context.animationDuration
baseAnimation.speed = (context.endFrame < context.startFrame) ? -1 : 1
// To select the subrange of the `baseAnimation` that should be played,
// we create a parent animation with the duration of that subrange
// to clip the `baseAnimation`. This parent animation can then loop
// and/or autoreverse over the clipped subrange.
//
// ┌────────────────────┬───────►
// │ clippingParent │ ...
// └────────────────────┴───────►
// 25% 75%
// ┌──────────────────────────────────┐
// │ baseAnimation │
// └──────────────────────────────────┘
// 0% 100%
//
let clippingParent = CAAnimationGroup()
clippingParent.animations = [baseAnimation]
clippingParent.duration = Double(abs(context.endFrame - context.startFrame)) / context.animation.framerate
baseAnimation.timeOffset = context.animation.time(forFrame: context.startFrame)
clippingParent.autoreverses = context.timingConfiguration.autoreverses
clippingParent.repeatCount = context.timingConfiguration.repeatCount
clippingParent.timeOffset = context.timingConfiguration.timeOffset
// Once the animation ends, it should pause on the final frame
clippingParent.fillMode = .both
clippingParent.isRemovedOnCompletion = false
// We can pause the animation on a specific frame by setting the root layer's
// `speed` to 0, and then setting the `timeOffset` for the given frame.
// - For that setup to work properly, we have to set the `beginTime`
// of this animation to a time slightly before the current time.
// - It's not really clear why this is necessary, but `timeOffset`
// is not applied correctly without this configuration.
// - We can't do this when playing the animation in real time,
// because it can cause keyframe timings to be incorrect.
if context.timingConfiguration.speed == 0 {
let currentTime = layer.convertTime(CACurrentMediaTime(), from: nil)
clippingParent.beginTime = currentTime - .leastNonzeroMagnitude
}
return clippingParent
}
}
extension CALayer {
/// Adds the given animation to this layer, timed with the given timing configuration
/// - The given animation should start at the beginning of the animation and
/// last the entire duration of the animation. It will be trimmed and retimed
/// to match the current playback state / looping configuration of the animation view.
@nonobjc
func add(_ animation: CAPropertyAnimation, timedWith context: LayerAnimationContext) {
add(animation.timed(with: context, for: self), forKey: animation.keyPath)
}
}
// Created by Cal Stephens on 1/28/22.
// Copyright © 2022 Airbnb Inc. All rights reserved.
import QuartzCore
extension CAShapeLayer {
/// Adds animations for the given `CombinedShapeItem` to this `CALayer`
@nonobjc
func addAnimations(
for combinedShapes: CombinedShapeItem,
context: LayerAnimationContext,
pathMultiplier: PathMultiplier)
throws
{
try addAnimation(
for: .path,
keyframes: combinedShapes.shapes,
value: { paths in
let combinedPath = CGMutablePath()
for path in paths {
combinedPath.addPath(path.cgPath().duplicated(times: pathMultiplier))
}
return combinedPath
},
context: context)
}
}
// MARK: - CombinedShapeItem
/// A custom `ShapeItem` subclass that combines multiple `Shape`s into a single `KeyframeGroup`
final class CombinedShapeItem: ShapeItem {
// MARK: Lifecycle
init(shapes: KeyframeGroup<[BezierPath]>, name: String) {
self.shapes = shapes
super.init(name: name, type: .shape, hidden: false)
}
required init(from _: Decoder) throws {
fatalError("init(from:) has not been implemented")
}
required init(dictionary _: [String: Any]) throws {
fatalError("init(dictionary:) has not been implemented")
}
// MARK: Internal
let shapes: KeyframeGroup<[BezierPath]>
}
extension CombinedShapeItem {
/// Manually combines the given shape keyframes by manually interpolating at each frame
static func manuallyInterpolating(
shapes: [KeyframeGroup<BezierPath>],
name: String)
-> CombinedShapeItem
{
let interpolators = shapes.map { shape in
KeyframeInterpolator(keyframes: shape.keyframes)
}
let times = shapes.flatMap { $0.keyframes.map { $0.time } }
let minimumTime = times.min() ?? 0
let maximumTime = times.max() ?? 0
let animationLocalTimeRange = Int(minimumTime)...Int(maximumTime)
let interpolatedKeyframes = animationLocalTimeRange.map { localTime in
Keyframe(
value: interpolators.compactMap { interpolator in
interpolator.value(frame: AnimationFrameTime(localTime)) as? BezierPath
},
time: AnimationFrameTime(localTime))
}
return CombinedShapeItem(
shapes: KeyframeGroup(keyframes: ContiguousArray(interpolatedKeyframes)),
name: name)
}
}
// Created by Cal Stephens on 12/21/21.
// Copyright © 2021 Airbnb Inc. All rights reserved.
import QuartzCore
extension CAShapeLayer {
/// Adds animations for the given `BezierPath` keyframes to this `CALayer`
@nonobjc
func addAnimations(
for customPath: KeyframeGroup<BezierPath>,
context: LayerAnimationContext,
pathMultiplier: PathMultiplier = 1,
transformPath: (CGPath) -> CGPath = { $0 },
roundedCorners: RoundedCorners? = nil)
throws
{
let combinedKeyframes = try BezierPathKeyframe.combining(
path: customPath,
cornerRadius: roundedCorners?.radius)
try addAnimation(
for: .path,
keyframes: combinedKeyframes,
value: { pathKeyframe in
var path = pathKeyframe.path
if let cornerRadius = pathKeyframe.cornerRadius {
path = path.roundCorners(radius: cornerRadius.cgFloatValue)
}
return transformPath(path.cgPath().duplicated(times: pathMultiplier))
},
context: context)
}
}
extension CGPath {
/// Duplicates this `CGPath` so that it is repeated the given number of times
func duplicated(times: Int) -> CGPath {
if times <= 1 {
return self
}
let cgPath = CGMutablePath()
for _ in 0..<times {
cgPath.addPath(self)
}
return cgPath
}
}
// MARK: - BezierPathKeyframe
/// Data that represents how to render a bezier path at a specific point in time
struct BezierPathKeyframe: Interpolatable {
let path: BezierPath
let cornerRadius: LottieVector1D?
/// Creates a single array of animatable keyframes from the given sets of keyframes
/// that can have different counts / timing parameters
static func combining(
path: KeyframeGroup<BezierPath>,
cornerRadius: KeyframeGroup<LottieVector1D>?) throws
-> KeyframeGroup<BezierPathKeyframe>
{
guard
let cornerRadius,
cornerRadius.keyframes.contains(where: { $0.value.cgFloatValue > 0 })
else {
return path.map { path in
BezierPathKeyframe(path: path, cornerRadius: nil)
}
}
return Keyframes.combined(
path, cornerRadius,
makeCombinedResult: BezierPathKeyframe.init)
}
func interpolate(to: BezierPathKeyframe, amount: CGFloat) -> BezierPathKeyframe {
BezierPathKeyframe(
path: path.interpolate(to: to.path, amount: amount),
cornerRadius: cornerRadius.interpolate(to: to.cornerRadius, amount: amount))
}
}
// Created by Cal Stephens on 12/21/21.
// Copyright © 2021 Airbnb Inc. All rights reserved.
import QuartzCore
extension CAShapeLayer {
/// Adds animations for the given `Ellipse` to this `CALayer`
@nonobjc
func addAnimations(
for ellipse: Ellipse,
context: LayerAnimationContext,
pathMultiplier: PathMultiplier)
throws
{
try addAnimation(
for: .path,
keyframes: ellipse.combinedKeyframes(),
value: { keyframe in
BezierPath.ellipse(
size: keyframe.size.sizeValue,
center: keyframe.position.pointValue,
direction: ellipse.direction)
.cgPath()
.duplicated(times: pathMultiplier)
},
context: context)
}
}
extension Ellipse {
/// Data that represents how to render an ellipse at a specific point in time
struct Keyframe: Interpolatable {
let size: LottieVector3D
let position: LottieVector3D
func interpolate(to: Ellipse.Keyframe, amount: CGFloat) -> Ellipse.Keyframe {
Keyframe(
size: size.interpolate(to: to.size, amount: amount),
position: position.interpolate(to: to.position, amount: amount))
}
}
/// Creates a single array of animatable keyframes from the separate arrays of keyframes in this Ellipse
func combinedKeyframes() throws -> KeyframeGroup<Ellipse.Keyframe> {
Keyframes.combined(
size, position,
makeCombinedResult: Ellipse.Keyframe.init)
}
}
// Created by Cal Stephens on 5/17/22.
// Copyright © 2022 Airbnb Inc. All rights reserved.
import QuartzCore
// MARK: - OpacityAnimationModel
protocol OpacityAnimationModel {
/// The opacity animation to apply to a `CALayer`
var opacity: KeyframeGroup<LottieVector1D> { get }
}
// MARK: - Transform + OpacityAnimationModel
extension Transform: OpacityAnimationModel { }
// MARK: - ShapeTransform + OpacityAnimationModel
extension ShapeTransform: OpacityAnimationModel { }
// MARK: - Fill + OpacityAnimationModel
extension Fill: OpacityAnimationModel { }
// MARK: - GradientFill + OpacityAnimationModel
extension GradientFill: OpacityAnimationModel { }
// MARK: - Stroke + OpacityAnimationModel
extension Stroke: OpacityAnimationModel { }
// MARK: - GradientStroke + OpacityAnimationModel
extension GradientStroke: OpacityAnimationModel { }
extension CALayer {
/// Adds the opacity animation from the given `OpacityAnimationModel` to this layer
@nonobjc
func addOpacityAnimation(for opacity: OpacityAnimationModel, context: LayerAnimationContext) throws {
try addAnimation(
for: .opacity,
keyframes: opacity.opacity,
value: {
// Lottie animation files express opacity as a numerical percentage value
// (e.g. 0%, 50%, 100%) so we divide by 100 to get the decimal values
// expected by Core Animation (e.g. 0.0, 0.5, 1.0).
$0.cgFloatValue / 100
},
context: context)
}
}
// Created by Cal Stephens on 1/11/22.
// Copyright © 2022 Airbnb Inc. All rights reserved.
// MARK: - KeyframeGroup + exactlyOneKeyframe
extension KeyframeGroup {
/// Retrieves the first `Keyframe` from this group,
/// and asserts that there are not any extra keyframes that would be ignored
/// - This should only be used in cases where it's fundamentally not possible to
/// support animating a given property (e.g. if Core Animation itself doesn't
/// support the property).
func exactlyOneKeyframe(
context: CompatibilityTrackerProviding,
description: String,
fileID _: StaticString = #fileID,
line _: UInt = #line)
throws
-> T
{
try context.compatibilityAssert(
keyframes.count == 1,
"""
The Core Animation rendering engine does not support animating multiple keyframes
for \(description) values, due to limitations of Core Animation.
""")
return keyframes[0].value
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册