เจาะลึกการออกแบบการเคลื่อนไหว: การเปลี่ยน iOS ขั้นสูง
เผยแพร่แล้ว: 2020-11-09ความคิดสร้างสรรค์นำมาซึ่งความปรารถนาอย่างต่อเนื่องที่จะสร้างความประทับใจให้ผู้ใช้ เป็นเวลาหลายศตวรรษมาแล้วที่มนุษย์พยายามใช้วิธีการที่มีอยู่เพื่อสร้างปฏิสัมพันธ์ที่เป็นธรรมชาติที่สุด โดยถือว่าธรรมชาติเป็นตัวอย่างพื้นฐาน
เมื่อค้นพบโลก คนๆ หนึ่งจะมีความรู้สึกอ่อนไหวต่อรายละเอียดเล็กๆ น้อยๆ ของโลกรอบตัวมากขึ้นเรื่อยๆ ซึ่งทำให้พวกเขาสามารถแยกแยะการประดิษฐ์จากสิ่งมีชีวิตตามสัญชาตญาณได้ บรรทัดนี้ไม่ชัดเจนด้วยการพัฒนาเทคโนโลยี ซึ่งซอฟต์แวร์มีเป้าหมายเพื่อสร้างสภาพแวดล้อมที่ผู้ใช้อธิบายประสบการณ์ของเขาในโลกที่สร้างขึ้นโดยธรรมชาติ

สะท้อนธรรมชาติในการออกแบบแอพ
บทความนี้จะแนะนำขั้นตอนการเบลอเส้นขอบโดยใช้ตัวอย่างการเปลี่ยนรูปร่างในแอนิเมชั่นเชิงโต้ตอบขององค์ประกอบในชีวิตประจำวันในแอปพลิเคชัน iOS ส่วนใหญ่ วิธีหนึ่งในการเลียนแบบธรรมชาติคือการเปลี่ยนแปลงตำแหน่งของวัตถุในเวลาต่างๆ ฟังก์ชันที่เป็นแบบอย่างของเวลาแอนิเมชันแสดงไว้ด้านล่าง

เมื่อรวมกับการใช้เวลาอย่างเหมาะสม โดยการเพิ่มการแปลงทางเรขาคณิต เราจะได้รับเอฟเฟกต์จำนวนอนันต์ จากการสาธิตความเป็นไปได้ของการออกแบบและเทคโนโลยีในปัจจุบัน แอปพลิเคชัน Motion Patterns ได้ถูกสร้างขึ้น ซึ่งรวมถึงโซลูชันยอดนิยม ซึ่งพัฒนาโดยบริษัทพัฒนาซอฟต์แวร์ของเรา เนื่องจากฉันไม่ใช่นักเขียน แต่เป็นโปรแกรมเมอร์ และไม่มีอะไรจะพูดได้ดีไปกว่าตัวอย่างสด ฉันไม่มีทางเลือกอื่นนอกจากต้องเชิญคุณเข้าสู่โลกมหัศจรรย์นี้!
ตัวอย่างที่จะค้นพบ

มาดูกันว่าการออกแบบการเคลื่อนไหวสามารถเปลี่ยนการออกแบบที่ล้ำสมัยให้เป็นสิ่งที่พิเศษได้อย่างไร! ในตัวอย่างด้านล่าง ทางด้านซ้ายมีแอปพลิเคชันที่ใช้เฉพาะแอนิเมชั่น iOS พื้นฐาน ในขณะที่ทางด้านขวามีแอปพลิเคชันเวอร์ชันเดียวกันที่มีการปรับปรุงบางอย่าง
เอฟเฟกต์ “ดำน้ำลึก”
นี่คือการ เปลี่ยนแปลงโดยใช้การเปลี่ยนแปลงระหว่างสองสถานะของมุมมอง สร้างขึ้นบนพื้นฐานของคอลเล็กชัน หลังจากเลือกเซลล์เฉพาะแล้ว การเปลี่ยนไปยังรายละเอียดขององค์ประกอบจะเกิดขึ้นโดยการเปลี่ยนองค์ประกอบแต่ละองค์ประกอบ * โซลูชันเพิ่มเติมคือการใช้ทรานซิชันเชิงโต้ตอบ ซึ่งอำนวยความสะดวกในการใช้งานแอปพลิเคชัน
*จริง ๆ แล้วการคัดลอก / แมปองค์ประกอบข้อมูลในมุมมองชั่วคราวที่มีส่วนร่วมในการเปลี่ยนแปลง ระหว่างจุดเริ่มต้นและจุดสิ้นสุด … แต่ฉันจะอธิบายสิ่งนี้ในบทความนี้ในภายหลัง…
เอฟเฟกต์ “มองทะลุขอบ”
การใช้แอนิเมชั่นมุมมองเลื่อนในการกระทำจะเปลี่ยนรูปภาพใน รูปแบบ 3 มิติสำหรับเอฟเฟกต์ลูกบาศก์ ปัจจัยหลักที่ทำให้เกิดเอฟเฟกต์คือออฟเซ็ตของมุมมองการเลื่อน
เอฟเฟกต์ “เชื่อมต่อจุด”
นี่คือการเปลี่ยนแปลงระหว่างฉากต่างๆ ที่ เปลี่ยนวัตถุขนาดเล็กเป็นทั้งหน้าจอ คอลเลกชันที่ใช้สำหรับวัตถุประสงค์นี้ทำงานพร้อมกัน การเปลี่ยนแปลงบนหน้าจอหนึ่งสอดคล้องกับการเปลี่ยนแปลงในอีกหน้าจอหนึ่ง นอกจากนี้ เมื่อคุณป้อนภาพย่อส่วนในพื้นหลัง เอฟเฟกต์พารัลแลกซ์จะปรากฏขึ้นเมื่อคุณปัดไปมาระหว่างฉากต่างๆ
“เปลี่ยนรูปร่าง” เอฟเฟกต์
แอนิเมชั่นประเภทสุดท้ายเป็นแบบง่ายๆ โดยใช้ไลบรารี Lottie เป็นการใช้งานทั่วไปสำหรับไอคอนแอนิเมชั่น ในกรณีนี้ นี่คือไอคอนบนแถบแท็บ นอกจากนี้ ด้วยการเปลี่ยนแท็บที่เหมาะสม แอนิเมชั่นของการเปลี่ยนแปลงในทิศทางเฉพาะถูกใช้เพื่อเพิ่มเอฟเฟกต์การโต้ตอบให้เข้มข้นขึ้น
เจาะลึก: รูปแบบการออกแบบการเคลื่อนไหวครั้งแรกของเรา
ตอนนี้ได้เวลาเข้าประเด็นแล้ว… เราจำเป็นต้องเจาะลึกลงไปในโครงสร้างของกลไกที่ควบคุมตัวอย่างเหล่านี้
ในบทความนี้ ผมจะแนะนำให้คุณรู้จักกับ รูปแบบการออกแบบการเคลื่อนไหวรูปแบบ แรก ซึ่งเราตั้งชื่อว่า 'Dive Deeper' พร้อมคำอธิบายที่เป็นนามธรรมในการใช้งาน โดยไม่ต้องลงรายละเอียดเฉพาะเจาะจง เราวางแผนที่จะสร้างรหัสที่แน่นอนและพื้นที่เก็บข้อมูลทั้งหมดให้กับทุกคนในอนาคตโดยไม่มีข้อจำกัดใดๆ
สถาปัตยกรรมของโปรเจ็กต์และรูปแบบการออกแบบการเขียนโปรแกรมที่เข้มงวดนั้นไม่ใช่สิ่งสำคัญในตอนนี้—เราเน้นที่แอนิเมชันและทรานซิชัน
ในบทความนี้ เราจะใช้คุณลักษณะสองชุดที่มีให้เพื่อจัดการมุมมองระหว่างการเปลี่ยนฉาก ดังนั้น ฉันอยากจะชี้ให้เห็นว่าบทความนี้มีไว้สำหรับผู้ที่ค่อนข้างคุ้นเคยกับ UIKit และไวยากรณ์ของ Swift
https://developer.apple.com/documentation/uikit/uiviewcontrolleranimatedtransitioning
https://developer.apple.com/documentation/uikit/uipercentdriveninteractivetransition
https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/CustomizingtheTransitionAnimations.html
ครั้งแรก: โครงสร้าง
สำหรับเวอร์ชันพื้นฐานที่ใช้โซลูชันที่กำหนด จะต้องใช้คลาสตัวช่วยหลายคลาส รับผิดชอบในการให้ข้อมูลที่จำเป็นเกี่ยวกับมุมมองที่เกี่ยวข้องกับการเปลี่ยนแปลง และควบคุมการเปลี่ยนแปลงเองและการโต้ตอบ

คลาสพื้นฐานที่รับผิดชอบในการจัดการการเปลี่ยนแปลง พร็อกซี จะเป็น TransitionAnimation โดยจะตัดสินว่าการเปลี่ยนแปลงจะเกิดขึ้นด้วยวิธีใดและครอบคลุมฟังก์ชันมาตรฐานที่จำเป็นสำหรับการดำเนินการที่ทีม Apple ให้ไว้
/// นี่คือคลาสพื้นฐานสำหรับการเปลี่ยนซึ่งมีพฤติกรรมที่แตกต่างกันในการนำเสนอและการยกเลิกในช่วงเวลาที่กำหนด
TransitionAnimator คลาสเปิด: NSObject, UIViewControllerAnimatedTransitioning {
/// ระบุว่าเป็นการนำเสนอหรือยกเลิกการเปลี่ยนแปลง
นำเสนอ: Bool = true
/// ช่วงเวลาที่มีการเปลี่ยนแปลงทั้งหมดเกิดขึ้น
ระยะเวลาปล่อยส่วนตัว: TimeInterval
/// ตัวเริ่มต้นเริ่มต้นของแอนิเมเตอร์การเปลี่ยนแปลงพร้อมค่าเริ่มต้น
/// - ระยะเวลาพารามิเตอร์: ช่วงเวลาที่เกิดการเปลี่ยนแปลงทั้งหมด
/// - การนำเสนอพารามิเตอร์: ตัวบ่งชี้ว่ากำลังนำเสนอหรือยกเลิกการเปลี่ยนแปลง
การเริ่มต้นสาธารณะ (duration: TimeInterval = 0.5 นำเสนอ: Bool = true) {
self.duration = ระยะเวลา
self.presenting = นำเสนอ
super.init()
}
/// กำหนดระยะเวลาของการเปลี่ยนแปลง
/// - บริบทการเปลี่ยนผ่านพารามิเตอร์: บริบทของการเปลี่ยนแปลงปัจจุบัน
/// - ส่งกลับ: ระยะเวลาที่ระบุเมื่อเริ่มต้นแอนิเมเตอร์
การเปลี่ยนแปลงของ func สาธารณะ Duration (โดยใช้ TransitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
คืนตัวเอง.duration
}
/// หัวใจของแอนิเมชั่นการเปลี่ยนแปลง ในการเปลี่ยนฟังก์ชันนี้เกิดขึ้น
/// - สำคัญ: การแทนที่ฟังก์ชันนี้ในประเภททรานสิชันที่เป็นรูปธรรมมากขึ้นเป็นสิ่งสำคัญในการแสดงแอนิเมชั่น
/// - บริบทการเปลี่ยนผ่านพารามิเตอร์: บริบทของการเปลี่ยนแปลง
animateTransition func สาธารณะ (โดยใช้ TransitionContext: UIViewControllerContextTransitioning) { }
}ตาม TransitionAnimator เราสร้างไฟล์ TransformTransition ซึ่งงานที่จะทำการเปลี่ยนแปลงเฉพาะ การเปลี่ยนแปลงด้วยการแปลง (การเลี้ยงดู)
/// การดำเนินการเปลี่ยนการแปลง
TransformTransition คลาสเปิด: TransitionAnimator {
/// ดูโมเดลซึ่งมีข้อกำหนดทั้งหมดของการเปลี่ยนแปลงที่จำเป็นทั้งหมด
var viewModel ส่วนตัว: TransformViewModel
/// ตัวเริ่มต้นเริ่มต้นของการเปลี่ยนการแปลง
/// - พารามิเตอร์ viewModel: ดูแบบจำลองของการเปลี่ยนแปลงการแปลง
/// - ระยะเวลาพารามิเตอร์: ระยะเวลาของการเปลี่ยนแปลง
init (viewModel: TransformViewModel ระยะเวลา: TimeInterval) {
self.viewModel = ดูโมเดล
super.init(duration: duration, presenting: viewModel.presenting)
}องค์ประกอบของคลาส TransformTransition รวมถึง TransformViewModel ซึ่งตามชื่อแนะนำ แจ้งกลไกของโมเดลมุมมองที่การเปลี่ยนแปลงนี้จะนำไปใช้กับ
/// ดูแบบจำลองการเปลี่ยนรูปซึ่งมีข้อมูลพื้นฐานเกี่ยวกับมัน
คลาสสุดท้าย TransformViewModel {
/// ระบุว่าการเปลี่ยนการแปลงแสดงหรือปิดมุมมอง
ให้นำเสนอ: Bool
/// อาร์เรย์ของโมเดลที่มีข้อกำหนดเกี่ยวกับการแปลงสำหรับแต่ละมุมมอง
ให้โมเดล: [TransformModel]
/// ตัวเริ่มต้นเริ่มต้นของโมเดลมุมมองการแปลง
/// - การนำเสนอพารามิเตอร์: ระบุว่ากำลังนำเสนอหรือปิดการเปลี่ยนการแปลง
/// - โมเดลพารามิเตอร์: อาร์เรย์ของโมเดลที่มีข้อกำหนดเกี่ยวกับการแปลงสำหรับแต่ละมุมมอง
init (นำเสนอ: Bool รุ่น: [TransformModel]) {
self.presenting = นำเสนอ
self.models = โมเดล
}
}โมเดลการแปลงเป็นคลาสเสริมที่อธิบายองค์ประกอบเฉพาะของมุมมองที่เกี่ยวข้องกับการเปลี่ยนแปลงที่อยู่ในพาเรนต์ ซึ่งมักจะเป็นมุมมองของคอนโทรลเลอร์ที่สามารถแปลงได้
ในกรณีของการเปลี่ยนแปลง ถือเป็นขั้นตอนที่จำเป็นเนื่องจากการเปลี่ยนแปลงนี้ประกอบด้วยการดำเนินการตามความคิดเห็นเฉพาะระหว่างรัฐที่กำหนด
ประการที่สอง: การนำไปใช้
เราขยายโมเดลการดูที่เราเริ่มการเปลี่ยนแปลงด้วย Transformable ซึ่งบังคับให้เราใช้ฟังก์ชันที่จะเตรียมองค์ประกอบที่จำเป็นทั้งหมด ขนาดของฟังก์ชันนี้สามารถขยายได้เร็วมาก ดังนั้น เราขอแนะนำให้คุณแยกเป็นส่วนย่อยๆ เช่น ต่อองค์ประกอบ
/// โปรโตคอลสำหรับคลาสที่ต้องการเปลี่ยนการแปลง
โปรโตคอลที่แปลงได้: ViewModel {
/// จัดทำแบบจำลองความคิดเห็นที่เกี่ยวข้องกับการเปลี่ยนแปลง
/// - พารามิเตอร์ fromView: มุมมองจากการเปลี่ยนภาพเริ่มต้น
/// - พารามิเตอร์เพื่อดู: มุมมองที่จะเปลี่ยน
/// - การนำเสนอพารามิเตอร์: ระบุว่ากำลังนำเสนอหรือยกเลิก
/// - ส่งกลับ: อาร์เรย์ของโครงสร้างที่เก็บข้อมูลที่จำเป็นทั้งหมดพร้อมที่จะแปลงการเปลี่ยนแปลงสำหรับแต่ละมุมมอง
func prepareTransitionModels (จากมุมมอง: UIView, toView: UIView, กำลังนำเสนอ: Bool) -> [TransformModel]
}สมมติฐานไม่ได้บอกว่าจะค้นหาข้อมูลของความคิดเห็นที่มีส่วนร่วมในการเปลี่ยนแปลงได้อย่างไร ในตัวอย่างของฉัน ฉันใช้แท็กที่แสดงมุมมองที่กำหนด คุณมีอิสระในการใช้งานส่วนนี้

โมเดลของการแปลงมุมมองเฉพาะ (TransformModel) เป็นโมเดลที่เล็กที่สุดในรายการทั้งหมด ประกอบด้วยข้อมูลการแปลงคีย์ เช่น มุมมองเริ่มต้น มุมมองการเปลี่ยน เฟรมเริ่มต้น เฟรมสิ้นสุด ศูนย์เริ่มต้น ศูนย์สิ้นสุด ภาพเคลื่อนไหวพร้อมกัน และการดำเนินการสิ้นสุด พารามิเตอร์ส่วนใหญ่ไม่จำเป็นต้องใช้ระหว่างการแปลง ดังนั้นจึงมีค่าเริ่มต้นของตัวเอง เพื่อผลลัพธ์ที่น้อยที่สุดก็เพียงพอแล้วที่จะใช้เฉพาะสิ่งที่จำเป็นเท่านั้น
/// ตัวเริ่มต้นเริ่มต้นของโมเดลการแปลงที่มีค่าเริ่มต้น
/// - พารามิเตอร์ initialView: มุมมองจากจุดเริ่มต้นของการเปลี่ยน
/// - พารามิเตอร์ phantomView: มุมมองที่แสดงระหว่างการเปลี่ยนการแปลง
/// - พารามิเตอร์ initialFrame: เฟรมของมุมมองที่เริ่มการแปลงการเปลี่ยนแปลง
/// - พารามิเตอร์ finalFrame: เฟรมของมุมมองซึ่งจะแสดงเมื่อสิ้นสุดการเปลี่ยนการแปลง
/// - พารามิเตอร์ initialCenter: จำเป็นเมื่อจุดศูนย์กลางของมุมมองเริ่มต้นแตกต่างจากจุดศูนย์กลางของมุมมองเริ่มต้น
/// - Parameter finalCenter: จำเป็นเมื่อจุดศูนย์กลางของมุมมองสุดท้ายแตกต่างจากจุดศูนย์กลางของมุมมองสุดท้าย
/// - Parameter ParallelAnimation: ภาพเคลื่อนไหวเพิ่มเติมของมุมมองที่ดำเนินการระหว่างการเปลี่ยนรูปแบบ
/// - พารามิเตอร์เสร็จสมบูรณ์: บล็อกของโค้ดที่ทริกเกอร์หลังจากการเปลี่ยนการแปลง
/// - หมายเหตุ: ต้องใช้เฉพาะมุมมองเริ่มต้นเพื่อดำเนินการเปลี่ยนรูปแบบที่เรียบง่ายที่สุด
init (initialView: UIView,
phantomView: UIView = UIView (),
initialFrame: CGRect = CGRect (),
เฟรมสุดท้าย: CGRect = CGRect (),
initialCenter: CGPoint? = ไม่มี
finalCenter: CGPoint? = ไม่มี
ParallelAnimation: (() -> โมฆะ)? = ไม่มี
เสร็จสิ้น: (() -> โมฆะ)? = ไม่มี) {
self.initialView = initialView
self.phantomView = phantomView
self.initialFrame = initialFrame
self.finalFrame = เฟรมสุดท้าย
self.parallelAnimation = แอนิเมชั่นคู่ขนาน
self.completion = เสร็จสิ้น
self.initialCenter = initialCenter ?? CGPoint(x: initialFrame.midX, y: initialFrame.midY)
self.finalCenter = finalCenter ?? CGPoint(x: finalFrame.midX, y: finalFrame.midY)
}ความสนใจของคุณอาจถูกจับโดย Phantom View นี่คือช่วงเวลาที่ฉันจะอธิบายเวิร์กโฟลว์สำหรับการเปลี่ยน iOS ในรูปแบบที่สั้นที่สุด…

เมื่อผู้ใช้ต้องการย้ายไปยังฉากถัดไป iOS จะเตรียมตัวควบคุมเฉพาะโดยการคัดลอกตัวควบคุมเริ่มต้น (สีน้ำเงิน) และเป้าหมาย (สีเขียว) ไปยังหน่วยความจำ ถัดไป บริบทการเปลี่ยนแปลงจะถูกสร้างขึ้นผ่านผู้ประสานงานการเปลี่ยนแปลงซึ่งมีคอนเทนเนอร์ มุมมอง 'โง่' ที่ไม่มีฟังก์ชันพิเศษใดๆ นอกเหนือจากการจำลองมุมมองการเปลี่ยนแปลงระหว่างสองฉาก
หลักการสำคัญของการทำงานกับการเปลี่ยนคือการไม่เพิ่มมุมมองจริงใดๆ ให้กับบริบทการเปลี่ยนแปลง เนื่องจากเมื่อสิ้นสุดการเปลี่ยนแปลง บริบททั้งหมดจะได้รับการจัดสรรคืนพร้อมกับมุมมองที่เพิ่มลงในคอนเทนเนอร์ นี่คือมุมมองที่มีอยู่เฉพาะระหว่างการเปลี่ยนแปลงและจะถูกลบออก
ดังนั้น การใช้มุมมองแฝงที่จำลองมุมมองจริงจึงเป็นวิธีแก้ปัญหาที่สำคัญสำหรับการเปลี่ยนแปลงนี้

ในกรณีนี้ เรามีการเปลี่ยนภาพที่เปลี่ยนมุมมองหนึ่งเป็นอีกมุมมองหนึ่งโดยการเปลี่ยนรูปร่างและขนาด ในการทำเช่นนี้ เมื่อเริ่มต้นการเปลี่ยนแปลง ฉันสร้าง PhantomView ขององค์ประกอบที่กำหนดและเพิ่มลงในคอนเทนเนอร์ FadeView เป็นมุมมองเสริมเพื่อเพิ่มความนุ่มนวลให้กับการเปลี่ยนแปลงโดยรวม
/// หัวใจของทรานส์ฟอร์เมชั่น ที่ซึ่งทรานสิชั่นดำเนินการ แทนที่ `TransitionAnimator.animateTransition(...)`
/// - บริบทการเปลี่ยนผ่านพารามิเตอร์: บริบทของการเปลี่ยนการแปลงปัจจุบัน
แทนที่ open func animateTransition (โดยใช้ TransitionContext: UIViewControllerContextTransitioning) {
guard ให้ toViewController = TransitContext.view(forKey: .to)
ให้ fromViewController = TransitContext.view (forKey: .from) อื่น {
ส่งคืน Log.unexpectedState()
}
ให้ containerView = TransitContext.containerView
ให้ระยะเวลา = เปลี่ยนระยะเวลา (โดยใช้: เปลี่ยนบริบท)
ให้ fadeView = toViewController.makeFadeView(ความทึบ: (!นำเสนอ).cgFloatValue)
ให้รุ่น = viewModel.models
ให้ presentView = นำเสนอ ? toViewController : fromViewController
model.forEach { $0.initialView.isHidden = true }
PresentView.isHidden = true
containerView.addSubview (toViewController)
ถ้านำเสนอ {
containerView.insertSubview (fadeView, ด้านล่าง Subview: toViewController)
} อื่น {
containerView.addSubview (fadeView)
}
containerView.addSubviews (viewModel.models.map { $0.phantomView })ในขั้นตอนต่อไป ฉันแปลงเป็นรูปร่างเป้าหมายผ่านการแปลง และขึ้นอยู่กับว่าเป็นการนำเสนอหรือการเรียกคืน มันดำเนินการเพิ่มเติมเพื่อล้างมุมมองเฉพาะ - นี่คือสูตรทั้งหมดสำหรับการเปลี่ยนแปลงนี้
ให้ภาพเคลื่อนไหว: () -> โมฆะ = { [ตัวเองอ่อนแอ] ใน
ยามปล่อยให้ตัวเอง = ตัวเองอื่น { กลับ Log.unexpectedState () }
fadeView.alpha = self.presenting.cgFloatValue
model.forEach {
ให้ center = self.presenting ? $0.finalCenter : $0.initialCenter
ให้ transform = self.presenting ? $0.presentTransform : $0.dismissTransform
$0.phantomView.setTransformAndCenter(แปลง, ศูนย์กลาง)
}
model.compactMap { $0.parallelAnimation }.forEach { $0() }
}
ปล่อยให้เสร็จสิ้น: (บูล) -> โมฆะ = { _ in
TransitionContext.completeTransition(!transitionContext.transitionWasCancelled)
PresentView.isHidden = false
models.compactMap { $0 เสร็จสมบูรณ์ }.forEach { $0() }
model.forEach { $0.initialView.isHidden = false }
ถ้า !self.presenting && transitionContext.transitionWasCancelled {
toViewController.removeFromSuperview()
fadeView.removeFromSuperview()
}
}
UIView.animate(ด้วยDuration: Duration,
ล่าช้า: 0
ใช้SpringWithDamping: 1,
เริ่มต้นสปริงความเร็ว: 0.5,
ตัวเลือก: .curveEaseOut,
แอนิเมชั่น: แอนิเมชั่น,
เสร็จสิ้น: เสร็จสิ้น)ที่สาม: ส่วนผสมพิเศษ
หลังจากรวบรวมฟังก์ชัน คลาส และโปรโตคอลทั้งหมดแล้ว ผลลัพธ์ควรมีลักษณะดังนี้:
องค์ประกอบสุดท้ายของการเปลี่ยนแปลงของเราคือการโต้ตอบอย่างเต็มที่ เพื่อจุดประสงค์นี้ เราจะใช้ Pan Gesture ที่เพิ่มในมุมมองคอนโทรลเลอร์ TransitionInteractor...
/// ผู้ไกล่เกลี่ยในการจัดการการเปลี่ยนแปลงแบบโต้ตอบ
TransitionInteractor คลาสสุดท้าย: UIPercentDrivenInteractiveTransition {
/// ระบุว่าการเปลี่ยนแปลงได้เริ่มต้นขึ้นแล้ว
var hasStarted = false
/// ระบุว่าการเปลี่ยนแปลงควรเสร็จสิ้นหรือไม่
var shouldFinish = false
}… ซึ่งเราเริ่มต้นในตัวควบคุมด้วย
/// จัดการท่าทางการเลื่อนบนรายการมุมมองคอลเลกชัน และจัดการการเปลี่ยนแปลง
@objc func handlePanGesture (_ ตัวรับรู้ท่าทางสัมผัส: UIPanGestureRecognizer) {
ให้เปอร์เซ็นต์เกณฑ์: CGFloat = 0.1
ให้การแปล = GestureRecognizer.translation (ใน: มุมมอง)
ให้ verticalMovement = translation.y / view.bounds.height
ให้ upwardMovement = fminf(Float(verticalMovement), 0.0)
ให้ upwardMovementPercent = fminf(abs(upwardMovement), 0.9)
ให้คืบหน้า = CGFloat(upwardMovementPercent)
guard ให้ผู้โต้ตอบ = การโต้ตอบตัวควบคุมอื่น { กลับ }
สลับ GestureRecognizer.state {
กรณี .begin:
โต้ตอบ.hasStarted = true
ให้ tapPosition = GestureRecognizer.location (ใน: collectionView)
showDetailViewControllerFrom(ตำแหน่ง: tapPosition)
กรณี .change:
โต้ตอบ.shouldFinish = ความคืบหน้า > เปอร์เซ็นต์เกณฑ์
reactor.update (ความคืบหน้า)
กรณี .cancelled:
ตัวโต้ตอบ.hasStarted = false
ตัวโต้ตอบ. ยกเลิก ()
กรณี .ended:
ตัวโต้ตอบ.hasStarted = false
โต้ตอบ.ควรเสร็จสิ้น
? ตัวโต้ตอบเสร็จสิ้น ()
: ตัวโต้ตอบ. ยกเลิก ()
ค่าเริ่มต้น:
หยุดพัก
}
}การโต้ตอบที่พร้อมควรเป็นดังนี้:
หากทุกอย่างเป็นไปตามแผน แอปพลิเคชันของเราจะได้รับประโยชน์มากขึ้นในสายตาของผู้ใช้

ค้นพบเพียงยอดภูเขาน้ำแข็ง
คราวหน้า ผมจะอธิบายการใช้งานประเด็นที่เกี่ยวข้องกับการออกแบบการเคลื่อนไหวในฉบับต่อไปนี้
แอปพลิเคชัน การออกแบบ และซอร์สโค้ดเป็นทรัพย์สินของ Miquido และสร้างขึ้นด้วยความหลงใหลโดยนักออกแบบและโปรแกรมเมอร์ที่มีพรสวรรค์ในการใช้งาน ซึ่งเราไม่รับผิดชอบในการใช้งานของเรา ซอร์สโค้ดโดยละเอียดจะพร้อมใช้งานในอนาคตผ่านบัญชี github ของเรา เราขอเชิญคุณติดตามเรา!
ขอบคุณสำหรับความสนใจของคุณ แล้วพบกันใหม่!
