小組件實(shí)現(xiàn)動(dòng)畫的若干種辦法:
1.getTimeline:刷新不及時(shí)姨拥、且每天有固定的刷新次數(shù)
2.特殊字體:利用Text定時(shí)器屬性及圖片制作成的特殊字體
3.利用私有方法:clockHandRotationEffect
一. TimeLine
網(wǎng)上有很多資料绅喉,省略
二. 特殊字體
項(xiàng)目地址:https://github.com/liudhzhyym/WidgetAnimationSample?tab=readme-ov-file
三. clockHandRotationEffect
這個(gè)可以制作很多動(dòng)畫:平移渠鸽、旋轉(zhuǎn)叫乌、縮放等
具體效果:滾動(dòng)相冊(cè)柴罐、搖搖樂、時(shí)鐘憨奸、風(fēng)扇等
今天我們來細(xì)說如何展示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, *)
//原理其實(shí)就是有N幀就放N張圖片在原地不動(dòng),然后借助mask這個(gè)方法放一個(gè)遮罩.每個(gè)遮罩對(duì)應(yīng)圓的一塊角度區(qū)域.然后都旋轉(zhuǎn)起來后,就會(huì)出現(xiàn)類似電影一樣一幀一幀出現(xiàn)的視頻效果.
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 計(jì)算原理. 如果要方便,那就是lineWidth設(shè)置為imageWH.然后arcRadius設(shè)置為一個(gè)非常非常大的數(shù)即可不用做以下運(yùn)算. 下面的只是為了說清楚算法
//多少度
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//這里其實(shí)不乘以300才是完完全全正確的結(jié)果,每個(gè)大小都是剛剛好的.但是這樣轉(zhuǎn)起來之后重疊部分會(huì)留陰影, 所以加以放大后.半徑變大了,但是重疊部分還是那么大.相應(yīng)的周長絕對(duì)速度就變大了. 這樣陰影就會(huì)快速略過,肉眼便無法感知了.越放大效果越好.應(yīng)該是這個(gè)理
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) // ?? 需要先進(jìn)行旋轉(zhuǎn),再設(shè)置offset
)
}
}
.frame(width: width, height: height)
}
}
}
}
ClockHandRotationKit 用到了私有方法已經(jīng)被蘋果封了排宰,需要制作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動(dòng)畫實(shí)現(xiàn)
靈動(dòng)組件