本文的技术实现来自SwingAnimation和DynamicWidget,这里只是简单讲解和应用(主要是DynamicWidget)。
📢📢📢 使用clockHandRotationEffect这个API的小伙伴们要注意啦,在最新版的 Xcode 26.1.1 中该API会失效!也就是小组件无法动起来了!🤯 不过好在旧版Xcode(例如26.0.1)运行的项目还是能使用的。私有API还是有风险啊,只好等专业人士去适配了😵。
Demo地址:OneDay
Github上有位大佬已经分享了一个可以让小组件执行动画的Modifier --- SwingAnimation,该Modifier应该是使用了苹果的私有api --- clockHandRotationEffect,这个估计是实现系统时钟小组件的动画组件。
我一直都想在小组件上播放gif,曾想过clockHandRotationEffect是不是也能对Z轴进行动画,通过不断变换zIndex实现gif的播放,但是clockHandRotationEffect貌似只能实现旋转的动画,底层代码也没公开,这就整不会了,只能暂且作罢。
直到最近发现有另一位大佬使用clockHandRotationEffect实现了gif的播放:DynamicWidget
首先感谢两位大佬的分享,看了下DynamicWidget的代码,作者使用了一种十分巧妙的方式实现了多张图片叠加并轮播展示,从而实现了gif效果。
实现
核心代码如下:
ZStack {
ForEach(1...gifImages.count, id: \.self) { index in
Image(uiImage: gifImages[index-1])
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: width, height: height)
.mask(
ArcView(arcStartAngle: angle * Double(index - 1),
arcEndAngle: angle * Double(index),
arcRadius: arcRadius)
.stroke(style: .init(lineWidth: arcWidth, lineCap: .square, lineJoin: .miter))
.frame(width: width, height: height)
.clockHandRotationEffect(period: .custom(duration))
.offset(y: arcRadius) // ⚠️ 需要先进行旋转,再设置offset
)
}
}
.frame(width: width, height: height)
- 其中
ArcView是一个Shape,绘制的是一段圆弧,用来作为Image的mask图层(跟UIKit的mask一样,限制展示的区域)。
直接说原理吧,作者是先对gif进行解码,对每一帧图片都加了一层mask图层,然后对全部mask图层执行旋转动画,以控制当前帧要展示的图片。
用文字表达有点难,简单起见,选取3张图片,将其缩小再缩小,并垂直铺开,这样就一目了然:
- PS:这里为了演示,圆弧没有作为
Image的mask,而是添加在其上面并进行了染色处理。
Image能展示的区域就是圆弧跟图片重合的区域,因此ArcView边宽要设置成Image的最大边,确保圆弧经过Image时能覆盖整个图片。
-
每一个
Image的ArcView占360° / 图片总数的弧度,确保全部叠加起来能刚好围成一个圆。 -
最后把全部
Image叠加起来,并对全部ArcView执行旋转动画,就能做到「每一段圆弧滑过自己的Image」后能立马接上「下一个Image的圆弧」,实现无缝衔接。
- 图片位置不变,图片的展示取决于圆弧的位置,我们只能看到圆弧跟图片重合的区域。
从Z轴上看如同无间断切换图片,实现gif播放😄。
注意
- 当gif的图片数超过360张,超出部分可能就会丢失了(跟开头图片的弧度重合导致被覆盖),因此要注意好这个图片数。
- 另外图片数过少(圆弧过长)、时间较长(圆弧转动变慢)的gif,可能会看到图片“撕裂”的现象,这是因为在两个相连的mask圆弧交接处,此时是同时展示了两张图片:
- 不过一般很少有这种gif吧,这也跟苹果的帧率有关,放心的是作者把该圆弧设置得很大很大,基本很难会看到这种现象。
分析就这么多了,本人技术有限无法再深入探讨🙃。
最后
我把gif效果也收录到我的OneDay中,方便自己选择相册的gif作为小组件,需要的话可以去试一下:
- 桌面小组件
- 充电小组件
最后再次感谢两位大佬的分享!SwingAnimation 和 DynamicWidget。