提交 0471b5a9 编写于 作者: lizhongyi_'s avatar lizhongyi_

iOS 平台 uni-animation-view 组件 lottie-ios 用源码引入参与混编,避免在M系列mac上编译报错

上级 96b57601
<?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", "deploymentTarget": "13.0",
"validArchitectures": [ "validArchitectures": [
"arm64", "arm64",
"x86_64" "x86_64"
......
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
</view> </view>
</template> </template>
<script lang="uts"> <script lang="uts">
import { // import {
LottieAnimationView, // LottieAnimationView,
LottieAnimation, // LottieAnimation,
LottieLoopMode // LottieLoopMode
} from 'Lottie' // } from 'Lottie'
import { import {
URL, URL,
Bundle Bundle
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
} from "DCloudUTSFoundation" } from "DCloudUTSFoundation"
//原生提供以下属性或方法的实现 //原生提供以下属性或方法的实现
export default { export default {
/** /**
* 组件名称,也就是开发者使用的标签 * 组件名称,也就是开发者使用的标签
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
this.playAnimation() this.playAnimation()
} }
}, },
immediate: false //创建时是否通过此方法更新属性,默认值为false immediate: false //创建时是否通过此方法更新属性,默认值为false
}, },
"loop": { "loop": {
handler(newValue: boolean, oldValue: boolean) { handler(newValue: boolean, oldValue: boolean) {
...@@ -94,7 +94,7 @@ ...@@ -94,7 +94,7 @@
this.$el.loopMode = LottieLoopMode.playOnce this.$el.loopMode = LottieLoopMode.playOnce
} }
}, },
immediate: false //创建时是否通过此方法更新属性,默认值为false immediate: false //创建时是否通过此方法更新属性,默认值为false
}, },
"autoplay": { "autoplay": {
handler(newValue: boolean, oldValue: boolean) { handler(newValue: boolean, oldValue: boolean) {
...@@ -102,7 +102,7 @@ ...@@ -102,7 +102,7 @@
this.playAnimation() this.playAnimation()
} }
}, },
immediate: false //创建时是否通过此方法更新属性,默认值为false immediate: false //创建时是否通过此方法更新属性,默认值为false
}, },
"action": { "action": {
handler(newValue: string, oldValue: string) { handler(newValue: string, oldValue: string) {
...@@ -125,21 +125,21 @@ ...@@ -125,21 +125,21 @@
// 非法入参,不管 // 非法入参,不管
} }
}, },
immediate: false //创建时是否通过此方法更新属性,默认值为false immediate: false //创建时是否通过此方法更新属性,默认值为false
}, },
"hidden": { "hidden": {
handler(newValue: boolean, oldValue: boolean) { handler(newValue: boolean, oldValue: boolean) {
this.$el.isHidden = this.hidden this.$el.isHidden = this.hidden
}, },
immediate: false //创建时是否通过此方法更新属性,默认值为false immediate: false //创建时是否通过此方法更新属性,默认值为false
}, },
}, },
expose: ['setRepeatMode'], expose: ['setRepeatMode'],
methods: { methods: {
// 需要对外暴露的方法 // 需要对外暴露的方法
// 设置 RepeatMode // 设置 RepeatMode
setRepeatMode(repeatMode: string) { setRepeatMode(repeatMode: string) {
if (repeatMode == "RESTART") { if (repeatMode == "RESTART") {
if (this.loop) { if (this.loop) {
...@@ -156,7 +156,7 @@ ...@@ -156,7 +156,7 @@
} }
}, },
// 不对外暴露的方法 // 不对外暴露的方法
// 播放动画 // 播放动画
playAnimation() { playAnimation() {
// 构建动画资源 url // 构建动画资源 url
var animationUrl: URL | null var animationUrl: URL | null
...@@ -188,20 +188,20 @@ ...@@ -188,20 +188,20 @@
} }
} }
}, },
created() { //创建组件,替换created created() { //创建组件,替换created
}, },
NVBeforeLoad() { //组件将要创建,对应前端beforeMount NVBeforeLoad() { //组件将要创建,对应前端beforeMount
//可选实现,这里可以提前做一些操作 //可选实现,这里可以提前做一些操作
}, },
NVLoad(): LottieAnimationView { //创建原生View,必须定义返回值类型(Android需要明确知道View类型,需特殊校验) NVLoad(): LottieAnimationView { //创建原生View,必须定义返回值类型(Android需要明确知道View类型,需特殊校验)
// 初始化 Lottie$el // 初始化 Lottie$el
const animationView = new LottieAnimationView() const animationView = new LottieAnimationView()
// 默认只播放一次动画 // 默认只播放一次动画
animationView.loopMode = LottieLoopMode.playOnce animationView.loopMode = LottieLoopMode.playOnce
return animationView return animationView
}, },
NVLoaded() { //原生View已创建 NVLoaded() { //原生View已创建
/// 更新 props 中定义的属性值 /// 更新 props 中定义的属性值
...@@ -216,18 +216,18 @@ ...@@ -216,18 +216,18 @@
} }
}, },
NVLayouted() { //原生View布局完成 NVLayouted() { //原生View布局完成
//可选实现,这里可以做布局后续操作 //可选实现,这里可以做布局后续操作
}, },
NVBeforeUnload() { //原生View将释放 NVBeforeUnload() { //原生View将释放
//可选实现,这里可以做释放View之前的操作 //可选实现,这里可以做释放View之前的操作
}, },
NVUnloaded() { //原生View已释放 NVUnloaded() { //原生View已释放
//可选实现,这里可以做释放View之后的操作 //可选实现,这里可以做释放View之后的操作
}, },
unmounted() { //组件销毁 unmounted() { //组件销毁
//可选实现 //可选实现
} }
} }
</script> </script>
<?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.
/// The CALayer type responsible for only rendering the `transform` of a `LayerModel`
final class TransformLayer: BaseCompositionLayer {
/// `TransformLayer`s don't render any visible content,
/// they just `transform` their sublayers
override var renderLayerContents: Bool { false }
}
// Created by eric_horacek on 12/15/20.
// Copyright © 2020 Airbnb Inc. All rights reserved.
/// An Epoxy model with an associated context type that's passed into callback closures.
protocol CallbackContextEpoxyModeled: EpoxyModeled {
/// A context type that's passed into callback closures.
associatedtype CallbackContext
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册