feat: 卡片背景绘制

This commit is contained in:
Junhui Chen 2025-09-11 13:37:00 +08:00
parent 2487d7ebf7
commit 562c7aab88
2 changed files with 158 additions and 46 deletions

View File

@ -20,52 +20,6 @@ struct CustomLightSequenceAnimation: View {
self.imageSize = squareSize / 3 // 1/3 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 { private var centerPosition: CGPoint {
CGPoint(x: screenWidth / 2, y: squareSize * 0.325) 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 { struct CustomLightSequenceAnimation_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
CustomLightSequenceAnimation() CustomLightSequenceAnimation()
} }
} }
//
struct CardBlindBackground_Previews: PreviewProvider {
static var previews: some View {
CardBlindBackground()
.frame(width: 400, height: 600)
}
}

View 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~10.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 -> P3Lf~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)
}
}