小组件实现动画的若干种办法:
1.getTimeline:刷新不及时、且每天有固定的刷新次数
2.特殊字体:利用Text定时器属性及图片制作成的特殊字体
3.利用私有方法:clockHandRotationEffect
一. TimeLine
网上有很多资料,省略
二. 特殊字体
项目地址:https://github.com/liudhzhyym/WidgetAnimationSample?tab=readme-ov-file
三. clockHandRotationEffect
这个可以制作很多动画:平移、旋转、缩放等
具体效果:滚动相册、摇摇乐、时钟、风扇等
今天我们来细说如何展示Gif或者视频
gif解析出每一张图片、视频解析出妹一帧
核心代码如下:
import SwiftUI
import ClockHandRotationKit
@available(iOS 14.0, *)
struct ArcView: Shape {
var arcStartAngle: Double
var arcEndAngle: Double
var arcRadius: Double
func path(in rect: CGRect) -> Path {
var path = Path()
path.addArc(center: CGPoint(x: rect.midX, y: rect.midY),
radius: arcRadius,
startAngle: .degrees(arcStartAngle),
endAngle: .degrees(arcEndAngle),
clockwise: false)
return path
}
}
@available(iOS 14.0, *)
//原理其实就是有N帧就放N张图片在原地不动,然后借助mask这个方法放一个遮罩.每个遮罩对应圆的一块角度区域.然后都旋转起来后,就会出现类似电影一样一帧一帧出现的视频效果.
struct LocalGifView: View {
// var entry: DynamicWidgetProvider.Entry
var name: String
var body: some View {
if let gifPath = Bundle.main.path(forResource: name, ofType: "gif"),
let gifData = NSData(contentsOfFile: gifPath),
let gifImage = UIImage.sd_image(withGIFData: gifData as Data),
let gifImages = gifImage.images, gifImages.count > 0 {
GeometryReader { proxy in
let width = proxy.size.width
let height = proxy.size.height
let duration = gifImage.duration
//https://max2d.com/archives/897 计算原理. 如果要方便,那就是lineWidth设置为imageWH.然后arcRadius设置为一个非常非常大的数即可不用做以下运算. 下面的只是为了说清楚算法
//多少度
let angle = 360.0 / Double(gifImages.count)
//正方形宽高
let imageWH = max(width, height)
//一半宽高
let halfWH = (imageWH / 2)
//一半弧度
let halfRadian = (angle / 2)/180.0 * M_PI
let tanHalf = tan(halfRadian)
//半径
let radiu = sqrt((5 * halfWH * halfWH) + (halfWH * halfWH) / (tanHalf * tanHalf) + (4 * halfWH * halfWH / tanHalf))
let b = halfWH / tanHalf
let t = radiu - imageWH - b
let lineWidth = radiu - b;
let arcRadius = (radiu - (lineWidth / 2)) * 300//这里其实不乘以300才是完完全全正确的结果,每个大小都是刚刚好的.但是这样转起来之后重叠部分会留阴影, 所以加以放大后.半径变大了,但是重叠部分还是那么大.相应的周长绝对速度就变大了. 这样阴影就会快速略过,肉眼便无法感知了.越放大效果越好.应该是这个理
let offsetY = arcRadius - t / 2
ZStack {
ForEach(1...gifImages.count, id: \.self) { index in
Image(uiImage: gifImages[gifImages.count-index])
.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: lineWidth, lineCap: .square, lineJoin: .miter))
.frame(width: width, height: height)
.clockHandRotationEffect(period: .custom(duration))
.offset(y: offsetY)
)
}
}
.frame(width: width, height: height)
}
}
}
}
第二种写法:
import SwiftUI
import ClockHandRotationKit
import SDWebImage
struct ArcView: Shape {
var arcStartAngle: Double
var arcEndAngle: Double
var arcRadius: Double
func path(in rect: CGRect) -> Path {
var path = Path()
path.addArc(center: CGPoint(x: rect.midX, y: rect.midY),
radius: arcRadius,
startAngle: .degrees(arcStartAngle),
endAngle: .degrees(arcEndAngle),
clockwise: false)
return path
}
}
struct DynamicGifView: View {
var entry: DynamicWidgetProvider.Entry
var name: String
var body: some View {
if let gifPath = Bundle.main.path(forResource: name, ofType: "gif"),
let gifData = NSData(contentsOfFile: gifPath),
let gifImage = UIImage.sd_image(withGIFData: gifData as Data),
let gifImages = gifImage.images, gifImages.count > 0 {
GeometryReader { proxy in
let width = proxy.size.width
let height = proxy.size.height
let arcWidth = max(width, height)
let arcRadius = arcWidth * arcWidth
let angle = 360.0 / Double(gifImages.count)
let duration = gifImage.duration
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 / 2))
.offset(y: arcRadius) // ⚠️ 需要先进行旋转,再设置offset
)
}
}
.frame(width: width, height: height)
}
}
}
}
ClockHandRotationKit 用到了私有方法已经被苹果封了,需要制作XCFrameWork引入到工程
import SwiftUI
import WidgetKit
@available(iOS 14.0, *)
public enum ClockHandRotationPeriod {
case custom(TimeInterval)
case secondHand, hourHand, minuteHand
}
@available(iOS 14.0, *)
public struct ClockHandRotationModifier : ViewModifier {
let clockPeriod: WidgetKit._ClockHandRotationEffect.Period
let clockTimezone: TimeZone
let clockAnchor: UnitPoint
public init(period: ClockHandRotationPeriod, timezone: TimeZone = .current, anchor: UnitPoint = .center) {
var clockPeriod: WidgetKit._ClockHandRotationEffect.Period = .secondHand
switch period {
case .custom(let timeInterval):
clockPeriod = .custom(timeInterval)
case .secondHand:
clockPeriod = .secondHand
case .hourHand:
clockPeriod = .hourHand
case .minuteHand:
clockPeriod = .minuteHand
}
self.clockPeriod = clockPeriod
self.clockTimezone = timezone
self.clockAnchor = anchor
}
public func body(content: Content) -> some View {
content
._clockHandRotationEffect(self.clockPeriod, in: self.clockTimezone, anchor: self.clockAnchor)
}
}
@available(iOS 14.0, *)
extension View {
public func clockHandRotationEffect(period : ClockHandRotationPeriod, in timeZone: TimeZone = .current, anchor: UnitPoint = .center) -> some View {
return modifier(ClockHandRotationModifier(period: period, timezone: timeZone, anchor: anchor))
}
}
参考资料:
特殊GIF字体
TopWidget开源代码
Gif动画实现
灵动组件
网友评论