CAShapeLayer เป็นคลาสย่อยของ CALayer ที่ออกแบบมาสำหรับการเรนเดอร์เส้นทางเวกเตอร์แบบเร่งด้วยฮาร์ดแวร์บน GPU แตกต่างจาก CALayer พื้นฐานที่ทำงานกับภาพแรสเตอร์ผ่าน contents โดย CAShapeLayer จะเรนเดอร์รูปทรงตามอำเภอใจที่กำหนดผ่าน CGPath โดยไม่ต้องสร้างบัฟเฟอร์นอกหน้าจอแยกต่างหาก ตาม Apple QuartzCore Documentation, 2025 CAShapeLayer ใช้การเร่งด้วยฮาร์ดแวร์สำหรับการปรับขอบให้เรียบและรองรับแอนิเมชัน path, strokeStart และ strokeEnd เป็นเครื่องมือมาตรฐานสำหรับสร้างไอคอน ตัวบ่งชี้ความคืบหน้า และมาสก์ใน iOS
ประเด็นสำคัญ
CAShapeLayer เป็นเลเยอร์เฉพาะทางสำหรับเรนเดอร์กราฟิกเวกเตอร์ เปิดตัวใน iOS 3.0 แตกต่างจาก CALayer ที่เก็บภาพแรสเตอร์ CAShapeLayer เก็บคำอธิบายทางคณิตศาสตร์ของเส้นทาง — CGPath — และเรนเดอร์มันโดยตรงบน GPU ซึ่งช่วยให้ปรับขนาดรูปทรงได้โดยไม่สูญเสียคุณภาพและทำให้คุณสมบัติของมันเคลื่อนไหวได้โดยไม่ต้องวาดเนื้อหาใหม่
ในเชิงสถาปัตยกรรม CAShapeLayer สืบทอดความสามารถทั้งหมดของ CALayer: เงา, เส้นขอบ, การแปลง, มาสก์ แต่เพิ่มคุณสมบัติเฉพาะสำหรับการทำงานกับเส้นทาง: path, fillColor, strokeColor, lineWidth, lineCap, lineJoin, miterLimit, strokeStart, strokeEnd และ fillRule แต่ละคุณสมบัติเหล่านี้สามารถทำให้เคลื่อนไหวผ่าน CABasicAnimation
ตาม Apple Documentation CAShapeLayer ใช้การเร่งด้วยฮาร์ดแวร์สำหรับการเรนเดอร์ แตกต่างจากการวาดผ่าน Core Graphics ใน drawRect: ที่ CPU เรนเดอร์เส้นทางลงในบริบทแรสเตอร์ CAShapeLayer ส่งคำสั่งโดยตรงไปยัง GPU ผ่าน Metal ผลลัพธ์คือประสิทธิภาพที่สูงขึ้น โดยเฉพาะระหว่างแอนิเมชัน
CAShapeLayer มีประโยชน์อย่างยิ่งสำหรับองค์ประกอบอินเทอร์เฟซที่เปลี่ยนรูปร่างเพื่อตอบสนองต่อการกระทำของผู้ใช้: ตัวบ่งชี้ความคืบหน้า, แผนภูมิ, ไอคอนแถบแท็บ, มาสก์สำหรับแอนิเมชันการเปิดเผยเนื้อหา
กระบวนการเรนเดอร์ของ CAShapeLayer แตกต่างจาก CALayer พื้นฐาน แทนที่จะเก็บบัฟเฟอร์แรสเตอร์ที่มีพิกเซล CAShapeLayer ส่งคำอธิบายจุดยอดของเส้นทางไปยัง GPU — ชุดของจุด, เส้นโค้งเบซิเยร์ และส่วนของเส้นตรง GPU จะแรสเตอร์เส้นทางนี้ในรอบเดียว โดยใช้สีเติมและสีเส้นขอบในขั้นตอน fragment shader
วิธีการนี้มีข้อดีสองประการ: การปรับขนาดโดยไม่สูญเสียคุณภาพ (กราฟิกเวกเตอร์ยังคงคมชัดที่ความละเอียดใดๆ) และแอนิเมชันที่มีประสิทธิภาพ — เมื่อเส้นทางเปลี่ยนไป GPU จะคำนวณเฉพาะพิกเซลที่เปลี่ยนแปลงเท่านั้น ไม่ใช่ทั้งหน้าจอ
path — คุณสมบัติหลักของ CAShapeLayer ที่รับ CGPath เส้นทางสามารถประกอบด้วยเส้นตรง, ส่วนโค้ง, เส้นโค้งเบซิเยร์ และสี่เหลี่ยมผืนผ้า สร้างผ่าน UIBezierPath ใน Swift หรือ CGPath ใน Core Graphics ตัวอย่าง: UIBezierPath(roundedRect: rect, cornerRadius: 16).cgPath สร้างสี่เหลี่ยมผืนผ้ามุมมน
คุณสมบัติ lineDashPattern ช่วยให้สร้างเส้นประได้ รับอาร์เรย์ของตัวเลข: [6, 3] หมายถึง 6 จุดของขีดและ 3 จุดของช่องว่าง lineDashPhase กำหนดออฟเซ็ตรูปแบบและสามารถทำให้เคลื่อนไหวเพื่อเอฟเฟกต์ขีดเคลื่อนที่
การเลือกระหว่าง CAShapeLayer และ CALayer พื้นฐานขึ้นอยู่กับประเภทของเนื้อหา ถ้าองค์ประกอบเป็นพื้นที่สี่เหลี่ยมที่มีพื้นหลัง เส้นขอบ และเงา — CALayer ก็เพียงพอ ถ้าองค์ประกอบมีรูปร่างตามอำเภอใจ ต้องการแอนิเมชันเส้นทางหรือการปรับขนาดโดยไม่สูญเสียคุณภาพ — CAShapeLayer เหมาะกว่า
ประสิทธิภาพก็แตกต่างกันด้วย CALayer ที่มี cornerRadius และ masksToBounds จะสร้างบัฟเฟอร์นอกหน้าจอสำหรับการตัดเนื้อหา เพิ่ม 2–5 มิลลิวินาทีต่อเฟรม CAShapeLayer ไม่ต้องการบัฟเฟอร์นอกหน้าจอ — การตัดทำที่ระดับ vertex shader สำหรับองค์ประกอบที่มีมาสก์ซับซ้อน CAShapeLayer อาจเร็วกว่า 2–3 เท่า
การรวม CAShapeLayer กับเลเยอร์อื่นเป็นรูปแบบที่พบบ่อย ตัวอย่างเช่น CAGradientLayer ใช้ CAShapeLayer เป็นมาสก์เพื่อสร้างรูปทรงที่มีการเติมสีไล่ระดับ วิธีการนี้ให้ทั้งคุณภาพเวกเตอร์และการเติมสีไล่ระดับโดยไม่สูญเสียประสิทธิภาพ
แอนิเมชันเส้นทาง เป็นหนึ่งในความสามารถที่ทรงพลังที่สุดของ CAShapeLayer ถ้าทั้งสองเส้นทาง (เริ่มต้นและสิ้นสุด) มีจำนวนจุดควบคุมและส่วนเท่ากัน Core Animation จะทำการประมาณค่าระหว่างกลางอย่างราบรื่น ผลลัพธ์คือเอฟเฟกต์การเปลี่ยนรูปทรงหนึ่งไปสู่อีกรูปทรงหนึ่ง
เงื่อนไขความเข้ากันได้ของเส้นทาง: UIBezierPath เริ่มต้นและสิ้นสุดต้องมีโครงสร้างเดียวกัน — จำนวนการเรียก moveTo, addLine, addCurve และ addQuadCurve ต้องตรงกัน ถ้าโครงสร้างต่างกัน แอนิเมชันจะเกิดขึ้นทันทีโดยไม่มีการประมาณค่าระหว่างกลาง
ตาม Apple WWDC 2024 การรวมกันของ strokeStart และ strokeEnd เป็นรูปแบบแอนิเมชัน CAShapeLayer ที่ใช้บ่อยที่สุด การทำให้ strokeEnd เคลื่อนไหวจาก 0.0 ถึง 1.0 สร้างเอฟเฟกต์การวาดเส้นทางจากต้นจนจบ ใช้ในตัวบ่งชี้การโหลด, แอนิเมชันเปิดเผยแผนภูมิ และการเปลี่ยนหน้าจอแบบกำหนดเอง
เพื่อสร้างเอฟเฟกต์เต้นเป็นจังหวะ ให้ทำให้ strokeEnd และ opacity เคลื่อนไหวพร้อมกันด้วยฟังก์ชันเวลาที่ต่างกัน สำหรับเอฟเฟกต์มดเดิน ให้ทำให้ lineDashPhase เคลื่อนไหวด้วย lineDashPattern คงที่ ทั้งสองเทคนิค implement ผ่าน CABasicAnimation โดยไม่ต้องใช้ CPU
CAShapeLayer มักใช้เป็น มาสก์ สำหรับเลเยอร์อื่น ตัวอย่างเช่น สำหรับแอนิเมชันเปิดเผยเนื้อหา: ในระยะเริ่มต้น mask.path เป็นสี่เหลี่ยมผืนผ้าขนาดศูนย์ และสุดท้ายเป็นขนาดเต็ม Core Animation จะประมาณค่าเส้นทางและเนื้อหาจะเปิดเผยอย่างราบรื่น
รูปแบบนี้ใช้ใน iOS สำหรับแอนิเมชันขยายการ์ด, การแสดงหน้าต่างโมดอล และการเปลี่ยนระหว่าง view controllers แตกต่างจากแอนิเมชัน alpha แอนิเมชันมาสก์ CAShapeLayer ให้เอฟเฟกต์การเปิดเผยที่เป็นธรรมชาติมากขึ้นด้วยรูปร่างตามอำเภอใจ
ตัวอย่างแรก — การสร้างตัวบ่งชี้ความคืบหน้าแบบวงกลมด้วยแอนิเมชัน strokeEnd นี่คือองค์ประกอบ UI มาตรฐานที่แสดงความสามารถหลักของ CAShapeLayer:
func createProgressIndicator(in view: UIView) -> CAShapeLayer {
let circlePath = UIBezierPath(
arcCenter: CGPoint(x: 50, y: 50),
radius: 40,
startAngle: -.pi / 2,
endAngle: 3 * .pi / 2,
clockwise: true
)
let trackLayer = CAShapeLayer()
trackLayer.path = circlePath.cgPath
trackLayer.strokeColor = UIColor.systemGray5.cgColor
trackLayer.fillColor = nil
trackLayer.lineWidth = 8
view.layer.addSublayer(trackLayer)
let progressLayer = CAShapeLayer()
progressLayer.path = circlePath.cgPath
progressLayer.strokeColor = UIColor.systemBlue.cgColor
progressLayer.fillColor = nil
progressLayer.lineWidth = 8
progressLayer.lineCap = .round
progressLayer.strokeEnd = 0
view.layer.addSublayer(progressLayer)
return progressLayer
}
// เคลื่อนไหวเป็น 75%
progressLayer.animateStrokeEnd(to: 0.75, duration: 1.5)
ตัวอย่างที่สอง — แอนิเมชันเปลี่ยนรูปร่างระหว่างวงกลมและสี่เหลี่ยมจัตุรัส UIBezierPath ทั้งสองมีโครงสร้างที่เข้ากันได้:
let morphLayer = CAShapeLayer()
morphLayer.fillColor = UIColor.systemPurple.cgColor
morphLayer.frame = rect
let circlePath = UIBezierPath(roundedRect: rect, cornerRadius: rect.width / 2)
let squarePath = UIBezierPath(roundedRect: rect, cornerRadius: 0)
morphLayer.path = circlePath.cgPath
let morphAnimation = CABasicAnimation(keyPath: "path")
morphAnimation.toValue = squarePath.cgPath
morphAnimation.duration = 0.6
morphAnimation.autoreverses = true
morphAnimation.repeatCount = .infinity
morphLayer.add(morphAnimation, forKey: "morph")
ตัวอย่างที่สาม — มาสก์ CAShapeLayer สำหรับแอนิเมชันเปิดเผยเนื้อหา มาสก์ขยายจากศูนย์กลางไปยังขอบ:
func revealContent(_ view: UIView) {
let maskLayer = CAShapeLayer()
let size = view.bounds.size.width * 1.5
let center = CGPoint(x: view.bounds.midX, y: view.bounds.midY)
let initialPath = UIBezierPath(arcCenter: center, radius: 0, startAngle: 0, endAngle: .pi * 2, clockwise: true)
let finalPath = UIBezierPath(arcCenter: center, radius: size, startAngle: 0, endAngle: .pi * 2, clockwise: true)
maskLayer.path = initialPath.cgPath
view.layer.mask = maskLayer
let animation = CABasicAnimation(keyPath: "path")
animation.toValue = finalPath.cgPath
animation.duration = 0.4
animation.timingFunction = CAMediaTimingFunction(name: .easeOut)
maskLayer.add(animation, forKey: "reveal")
maskLayer.path = finalPath.cgPath
}
คำถามที่พบบ่อย
drawRect ใช้ CPU สำหรับเรนเดอร์ลงในบริบทแรสเตอร์ — ซึ่งช้าสำหรับแอนิเมชัน CAShapeLayer เรนเดอร์เส้นทางบน GPU ให้ 60 FPS ระหว่างแอนิเมชัน path, strokeEnd และการแปลง นอกจากนี้ CAShapeLayer ไม่ต้องการการวาดใหม่เมื่อเปลี่ยนขนาด — รูปทรงปรับขนาดแบบเวกเตอร์
แอนิเมชันเส้นทางที่ราบรื่นต้องการ โครงสร้างที่เหมือนกัน ของ UIBezierPath เริ่มต้นและสิ้นสุด จำนวนการเรียก moveTo, addLine, addCurve ต้องตรงกัน ใช้ UIBezierPath ที่มีจำนวนส่วนเท่ากันสำหรับรูปทรงเริ่มต้นและสิ้นสุด
ตั้งค่า lineDashPattern — อาร์เรย์ของตัวเลขที่สลับความยาวขีดและช่องว่าง ตัวอย่างเช่น [8, 4] สร้างขีด 8pt และช่องว่าง 4pt สำหรับเอฟเฟกต์มดเดิน ให้ทำให้ lineDashPhase เคลื่อนไหวผ่าน CABasicAnimation ด้วยฟังก์ชันเวลาเชิงเส้น
ได้ นี่เป็นหนึ่งในกรณีการใช้งานหลัก กำหนด CAShapeLayer ให้กับคุณสมบัติ mask ของ CALayer ใดๆ รวมถึง CAGradientLayer และคลาสย่อยอื่นๆ การทำให้เส้นทางมาสก์เคลื่อนไหวสร้างเอฟเฟกต์การเปิดเผยเนื้อหาอย่างราบรื่นผ่านรูปทรงตามอำเภอใจ
สร้าง CAGradientLayer และตั้งค่า mask = CAShapeLayer ที่มีเส้นทางที่ต้องการ สีไล่ระดับจะมองเห็นได้เฉพาะภายในเส้นทางมาสก์ รูปแบบนี้รวมข้อดีของรูปทรงเวกเตอร์ของ CAShapeLayer และการเติมสีไล่ระดับของ CAGradientLayer
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ