feat: 卡片背景绘制
This commit is contained in:
parent
2487d7ebf7
commit
562c7aab88
@ -20,52 +20,6 @@ struct CustomLightSequenceAnimation: View {
|
||||
self.imageSize = squareSize / 3 // 光束卡片尺寸(1/3背景大小)
|
||||
}
|
||||
|
||||
// MARK: - SwiftUI 背景重绘(方形版本)
|
||||
private struct CardBlindBackground: View {
|
||||
var body: some View {
|
||||
GeometryReader { geo in
|
||||
let w = geo.size.width
|
||||
let h = geo.size.height
|
||||
ZStack {
|
||||
// 主背景卡片(方形)
|
||||
RoundedRectangle(cornerRadius: 28)
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [Color.white, Color.white.opacity(0.96)],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
)
|
||||
.shadow(color: Color.black.opacity(0.06), radius: 16, x: 0, y: 8)
|
||||
.frame(width: w * 0.88, height: h * 0.88)
|
||||
.position(x: w / 2, y: h / 2)
|
||||
|
||||
// 左上光斑
|
||||
Circle()
|
||||
.fill(Color.themePrimary.opacity(0.18))
|
||||
.blur(radius: 40)
|
||||
.frame(width: min(w, h) * 0.35, height: min(w, h) * 0.35)
|
||||
.position(x: w * 0.25, y: h * 0.25)
|
||||
|
||||
// 右下光斑
|
||||
Circle()
|
||||
.fill(Color.orange.opacity(0.14))
|
||||
.blur(radius: 50)
|
||||
.frame(width: min(w, h) * 0.40, height: min(w, h) * 0.40)
|
||||
.position(x: w * 0.75, y: h * 0.75)
|
||||
|
||||
// 中央高光描边
|
||||
RoundedRectangle(cornerRadius: 28)
|
||||
.stroke(Color.white.opacity(0.35), lineWidth: 1)
|
||||
.frame(width: w * 0.88, height: h * 0.88)
|
||||
.position(x: w / 2, y: h / 2)
|
||||
.blendMode(.overlay)
|
||||
.opacity(0.7)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 卡片中心位置(固定,确保摆正居中)
|
||||
private var centerPosition: CGPoint {
|
||||
CGPoint(x: screenWidth / 2, y: squareSize * 0.325)
|
||||
@ -82,9 +36,57 @@ private struct CardBlindBackground: View {
|
||||
|
||||
}
|
||||
|
||||
|
||||
// MARK: - SwiftUI 背景重绘(方形版本)
|
||||
private struct CardBlindBackground: View {
|
||||
var body: some View {
|
||||
GeometryReader { geo in
|
||||
let w = geo.size.width
|
||||
let h = geo.size.height
|
||||
ZStack {
|
||||
// 主背景卡片(方形)
|
||||
ScoopRoundedRect(cornerRadius: 20, scoopDepth: 20, scoopHalfWidth: 90, scoopCenterX: 0.5, convexDown: true, flatHalfWidth: 60)
|
||||
.fill(Theme.Colors.primary)
|
||||
.shadow(color: .black.opacity(0.08), radius: 12, y: 6)
|
||||
.padding()
|
||||
|
||||
// 左上光斑
|
||||
// Circle()
|
||||
// .fill(Color.themePrimary.opacity(0.18))
|
||||
// .blur(radius: 40)
|
||||
// .frame(width: min(w, h) * 0.35, height: min(w, h) * 0.35)
|
||||
// .position(x: w * 0.25, y: h * 0.25)
|
||||
|
||||
// 右下光斑
|
||||
// Circle()
|
||||
// .fill(Color.orange.opacity(0.14))
|
||||
// .blur(radius: 50)
|
||||
// .frame(width: min(w, h) * 0.40, height: min(w, h) * 0.40)
|
||||
// .position(x: w * 0.75, y: h * 0.75)
|
||||
|
||||
// 中央高光描边
|
||||
// RoundedRectangle(cornerRadius: 28)
|
||||
// .stroke(Color.white.opacity(0.35), lineWidth: 1)
|
||||
// .frame(width: w * 0.88, height: h * 0.88)
|
||||
// .position(x: w / 2, y: h / 2)
|
||||
// .blendMode(.overlay)
|
||||
// .opacity(0.7)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 预览
|
||||
struct CustomLightSequenceAnimation_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CustomLightSequenceAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
// 预览
|
||||
struct CardBlindBackground_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
CardBlindBackground()
|
||||
.frame(width: 400, height: 600)
|
||||
}
|
||||
}
|
||||
110
wake/SharedUI/Graphics/ScoopRoundedRect.swift
Normal file
110
wake/SharedUI/Graphics/ScoopRoundedRect.swift
Normal file
@ -0,0 +1,110 @@
|
||||
import SwiftUI
|
||||
|
||||
/// 顶部带“内凹”弧形的连续圆角矩形
|
||||
struct ScoopRoundedRect: Shape {
|
||||
var cornerRadius: CGFloat = 20
|
||||
/// 凹凸的“深度”:>0 向下凸(更接近你截图的效果),<0 为向上凹
|
||||
var scoopDepth: CGFloat = 10
|
||||
/// 半宽:控制水平占据范围(越大越“平缓”)
|
||||
var scoopHalfWidth: CGFloat = 18
|
||||
/// 相对位置(0~1,0.5 正中)
|
||||
var scoopCenterX: CGFloat = 0.33
|
||||
/// 是否向下凸;为 false 时表现为向上“凹”(与 notch 类似)
|
||||
var convexDown: Bool = true
|
||||
/// 凹陷/鼓包底部的“平底”半宽(0 为无平底)
|
||||
var flatHalfWidth: CGFloat = 8
|
||||
|
||||
func path(in rect: CGRect) -> Path {
|
||||
let r = min(cornerRadius, min(rect.width, rect.height) * 0.5)
|
||||
let topY = rect.minY
|
||||
|
||||
// 约束中心与范围,避免穿出圆角
|
||||
let minX = rect.minX + r
|
||||
let maxX = rect.maxX - r
|
||||
let centerX = rect.minX + rect.width * scoopCenterX
|
||||
let hw = min(scoopHalfWidth, (maxX - minX) * 0.45)
|
||||
let flatHW = max(0, min(flatHalfWidth, hw * 0.8))
|
||||
let shoulder = max(1, hw - flatHW) // 两侧曲线的水平长度
|
||||
let startX = max(minX, centerX - (flatHW + shoulder))
|
||||
let endX = min(maxX, centerX + (flatHW + shoulder))
|
||||
let leftFlatX = max(minX, centerX - flatHW)
|
||||
let rightFlatX = min(maxX, centerX + flatHW)
|
||||
let depth = (convexDown ? 1 : -1) * scoopDepth
|
||||
|
||||
// 左曲线:P0 -> Lf(水平);右曲线:Rf -> P3(水平);Lf~Rf 之间是一段水平直线
|
||||
let P0 = CGPoint(x: startX, y: topY)
|
||||
let Lf = CGPoint(x: leftFlatX, y: topY + depth)
|
||||
let Rf = CGPoint(x: rightFlatX, y: topY + depth)
|
||||
let P3 = CGPoint(x: endX, y: topY)
|
||||
|
||||
// 使用圆角近似系数控制手柄长度(基于 shoulder)
|
||||
let k = shoulder * 0.5522847498
|
||||
let C1 = CGPoint(x: P0.x + k, y: P0.y) // P0 水平切线
|
||||
let C2 = CGPoint(x: Lf.x - k, y: Lf.y) // Lf 水平切线
|
||||
let C3 = CGPoint(x: Rf.x + k, y: Rf.y) // Rf 水平切线
|
||||
let C4 = CGPoint(x: P3.x - k, y: P3.y) // P3 水平切线
|
||||
|
||||
var p = Path()
|
||||
// 顶部从左上圆角起
|
||||
p.move(to: CGPoint(x: rect.minX + r, y: topY))
|
||||
// 左侧直线到凹/凸开始
|
||||
p.addLine(to: P0)
|
||||
// 左侧进入曲线
|
||||
p.addCurve(to: Lf, control1: C1, control2: C2)
|
||||
// 平底水平直线
|
||||
p.addLine(to: Rf)
|
||||
// 右侧离开曲线
|
||||
p.addCurve(to: P3, control1: C3, control2: C4)
|
||||
// 顶部到右上圆角
|
||||
p.addLine(to: CGPoint(x: rect.maxX - r, y: topY))
|
||||
// 右上圆角
|
||||
p.addQuadCurve(to: CGPoint(x: rect.maxX, y: rect.minY + r),
|
||||
control: CGPoint(x: rect.maxX, y: rect.minY))
|
||||
// 右侧直线到右下圆角
|
||||
p.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - r))
|
||||
// 右下圆角
|
||||
p.addQuadCurve(to: CGPoint(x: rect.maxX - r, y: rect.maxY),
|
||||
control: CGPoint(x: rect.maxX, y: rect.maxY))
|
||||
// 底边到左下圆角
|
||||
p.addLine(to: CGPoint(x: rect.minX + r, y: rect.maxY))
|
||||
// 左下圆角
|
||||
p.addQuadCurve(to: CGPoint(x: rect.minX, y: rect.maxY - r),
|
||||
control: CGPoint(x: rect.minX, y: rect.maxY))
|
||||
// 左侧到左上圆角
|
||||
p.addLine(to: CGPoint(x: rect.minX, y: rect.minY + r))
|
||||
// 左上圆角
|
||||
p.addQuadCurve(to: CGPoint(x: rect.minX + r, y: rect.minY),
|
||||
control: CGPoint(x: rect.minX, y: rect.minY))
|
||||
p.closeSubpath()
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
struct ScoopRoundedRect_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack(spacing: 30) {
|
||||
// 向下“鼓包”且底部有平直段
|
||||
ScoopRoundedRect(cornerRadius: 24, scoopDepth: 8, scoopHalfWidth: 26, scoopCenterX: 0.25, convexDown: true, flatHalfWidth: 12)
|
||||
.fill(Color.orange)
|
||||
.frame(height: 140)
|
||||
.shadow(color: .black.opacity(0.08), radius: 12, y: 6)
|
||||
.padding()
|
||||
|
||||
// 中央更深、更宽,平底更宽
|
||||
ScoopRoundedRect(cornerRadius: 28, scoopDepth: 12, scoopHalfWidth: 36, scoopCenterX: 0.5, convexDown: true, flatHalfWidth: 18)
|
||||
.fill(Color.orange)
|
||||
.frame(height: 140)
|
||||
.shadow(color: .black.opacity(0.08), radius: 12, y: 6)
|
||||
.padding()
|
||||
|
||||
// 作为对比:向上“凹陷”的 notch,带平底
|
||||
ScoopRoundedRect(cornerRadius: 24, scoopDepth: 10, scoopHalfWidth: 22, scoopCenterX: 0.6, convexDown: false, flatHalfWidth: 10)
|
||||
.fill(Color.orange)
|
||||
.frame(height: 140)
|
||||
.shadow(color: .black.opacity(0.08), radius: 12, y: 6)
|
||||
.padding()
|
||||
}
|
||||
.background(Color(white: 0.96))
|
||||
.previewLayout(.sizeThatFits)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user