Menggunakan UIViewPropertyAnimator untuk Membuat Animasi Kustom

Membuat animasi itu luar biasa. Mereka adalah bagian penting dari Pedoman Antarmuka Manusia iOS . Animasi membantu menarik perhatian pengguna pada hal-hal penting atau hanya membuat aplikasi kurang membosankan.

Ada beberapa cara untuk menerapkan animasi di iOS. Mungkin cara yang paling populer adalah dengan menggunakan UIView.animate (withDuration: animations :) . Anda dapat menghidupkan lapisan gambar menggunakan CABasicAnimation . Selain itu, UIKit memungkinkan Anda untuk mengonfigurasi animasi khusus untuk menampilkan pengontrol menggunakan UIViewControllerTransitioningDelegate .

Pada artikel ini saya ingin membahas cara lain yang menarik untuk menghidupkan tampilan - UIViewPropertyAnimator . Kelas ini menyediakan lebih banyak fungsi manajemen daripada pendahulunya, UIView.animat e. Gunakan untuk membuat animasi sementara, interaktif, dan terganggu. Selain itu, dimungkinkan untuk dengan cepat mengubah animator.

Memperkenalkan UIViewPropertyAnimator


UIViewPropertyAnimator diperkenalkan di iOS 10 . Ini memungkinkan Anda untuk membuat animasi dengan cara berorientasi objek. Mari kita lihat contoh animasi yang dibuat menggunakan UIViewPropertyAnimator .

gambar

Ini adalah bagaimana ketika menggunakan UIView.

UIView.animate(withDuration: 0.3) { view.frame = view.frame.offsetBy(dx: 100, dy: 0) } 

Dan inilah cara melakukannya dengan UIViewPropertyAnimator :

 let animator = UIViewPropertyAnimator(duration:0.3, curve: .linear) { view.frame = view.frame.offsetBy(dx:100, dy:0) } animator.startAnimation() 

Jika Anda perlu memeriksa animasi, cukup buat Playground dan jalankan kode seperti yang ditunjukkan di bawah ini. Kedua fragmen kode akan menghasilkan hasil yang sama.

gambar

Anda mungkin berpikir bahwa dalam contoh ini tidak ada banyak perbedaan. Jadi, apa gunanya menambahkan cara baru untuk membuat animasi? UIViewPropertyAnimator menjadi lebih berguna ketika Anda perlu membuat animasi interaktif.

Animasi interaktif dan terputus


Apakah Anda ingat gerakan klasik "Finger Swipe to Unlock Device"? Atau gerakan "Gerakkan jari Anda di layar dari bawah ke atas" untuk membuka Pusat Kontrol? Ini adalah contoh bagus dari animasi interaktif. Anda dapat mulai memindahkan gambar dengan jari Anda, lalu lepaskan dan gambar akan kembali ke posisi semula. Selain itu, Anda dapat menangkap gambar selama animasi dan terus memindahkannya dengan jari Anda.

Animasi UIView tidak menyediakan cara mudah untuk mengontrol persentase penyelesaian animasi. Anda tidak dapat menjeda animasi di tengah-tengah loop dan melanjutkan eksekusi setelah terputus.

Dalam hal ini, kita akan berbicara tentang UIViewPropertyAnimator . Selanjutnya, kita akan melihat bagaimana Anda dapat dengan mudah membuat animasi yang sepenuhnya interaktif, terputus, dan membalikkan animasi dalam beberapa langkah.

Persiapan proyek peluncuran


Pertama, Anda perlu mengunduh proyek pemula . Setelah Anda membuka arsip, Anda akan menemukan aplikasi CityGuide yang membantu pengguna merencanakan liburan mereka. Pengguna dapat menggulir daftar kota, dan kemudian membuka deskripsi rinci dengan informasi rinci tentang kota yang ia sukai.

Pertimbangkan kode sumber proyek sebelum kita mulai membuat animasi yang indah. Inilah yang dapat Anda temukan di sebuah proyek dengan membukanya di Xcode :

  1. ViewController.swift : Pengontrol aplikasi utama dengan UICollectionView yang menampilkan berbagai objek Kota .
  2. CityCollectionViewCell.swift: Cell untuk menampilkan City . Bahkan, dalam artikel ini sebagian besar perubahan akan berlaku untuk kelas ini. Anda mungkin memperhatikan bahwa descriptionLabel dan closeButton sudah didefinisikan di kelas. Namun, setelah memulai aplikasi, objek-objek ini akan disembunyikan. Jangan khawatir, mereka akan terlihat sedikit kemudian. Kelas ini juga memiliki properti collectionView dan index . Nanti mereka akan digunakan untuk animasi.
  3. CityCollectionViewFlowLayout.swift: Kelas ini bertanggung jawab untuk pengguliran horizontal. Kami belum akan mengubahnya.
  4. City.swift : Model aplikasi utama memiliki metode yang digunakan di ViewController.
  5. Main.storyboard: Di sana Anda dapat menemukan antarmuka pengguna untuk ViewController dan CityCollectionViewCell .

Mari kita coba membangun dan menjalankan aplikasi sampel. Sebagai hasil dari ini, kami memperoleh yang berikut ini.

cityguideapp-iphone8

Implementasi penyebaran dan runtuhnya animasi


Setelah memulai aplikasi, daftar kota ditampilkan. Tetapi pengguna tidak dapat berinteraksi dengan objek dalam bentuk sel. Sekarang Anda perlu menampilkan informasi untuk setiap kota ketika pengguna mengklik salah satu sel. Lihatlah versi final aplikasi. Inilah yang sebenarnya perlu dikembangkan:

gambar

Animasi terlihat bagus, bukan? Tapi tidak ada yang istimewa di sini, itu hanya logika dasar dari UIViewPropertyAnimator . Mari kita lihat bagaimana menerapkan jenis animasi ini. Buat metode collectionView (_: didSelectItemAt) , tambahkan fragmen kode berikut ke akhir file ViewController :

 func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { let selectedCell = collectionView.cellForItem(at: indexPath)! as! CityCollectionViewCell selectedCell.toggle() } 

Sekarang kita perlu mengimplementasikan metode toggle . Mari kita beralih ke CityCollectionViewCell.swift dan mengimplementasikan metode ini.

Pertama, tambahkan enumerasi Negara ke bagian atas file, tepat sebelum deklarasi kelas CityCollectionViewCell . Daftar ini memungkinkan Anda untuk melacak keadaan sel:

 private enum State { case expanded case collapsed var change: State { switch self { case .expanded: return .collapsed case .collapsed: return .expanded } } } 

Tambahkan beberapa properti untuk mengontrol animasi di kelas CityCollectionViewCell :

 private var initialFrame: CGRect? private var state: State = .collapsed private lazy var animator: UIViewPropertyAnimator = { return UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut) }() 

Variabel initialFrame digunakan untuk menyimpan bingkai sel sampai animasi berjalan . negara digunakan untuk melacak jika sel diperluas atau runtuh. Dan variabel animator digunakan untuk mengontrol animasi.

Sekarang tambahkan metode sakelar dan panggil dari metode tutup , misalnya:

 @IBAction func close(_ sender: Any) { toggle() } func toggle() { switch state { case .expanded: collapse() case .collapsed: expand() } } 

Lalu kami menambahkan dua metode lagi: expand () dan collapse () . Kami akan melanjutkan implementasi mereka. Pertama, kita mulai dengan metode expansiond () :

 private func expand() { guard let collectionView = self.collectionView, let index = self.index else { return } animator.addAnimations { self.initialFrame = self.frame self.descriptionLabel.alpha = 1 self.closeButton.alpha = 1 self.layer.cornerRadius = 0 self.frame = CGRect(x: collectionView.contentOffset.x, y:0, width: collectionView.frame.width, height: collectionView.frame.height) if let leftCell = collectionView.cellForItem(at: IndexPath(row: index - 1, section: 0)) { leftCell.center.x -= 50 } if let rightCell = collectionView.cellForItem(at: IndexPath(row: index + 1, section: 0)) { rightCell.center.x += 50 } self.layoutIfNeeded() } animator.addCompletion { position in switch position { case .end: self.state = self.state.change collectionView.isScrollEnabled = false collectionView.allowsSelection = false default: () } } animator.startAnimation() } 

Berapa kodenya. Biarkan saya menjelaskan apa yang terjadi langkah demi langkah:

  1. Pertama, periksa apakah collectionView dan index tidak sama dengan nol. Jika tidak, kami tidak akan dapat memulai animasi.
  2. Selanjutnya, mulai membuat animasi dengan memanggil animator.addAnimations .
  3. Selanjutnya, simpan frame saat ini, yang digunakan untuk mengembalikannya dalam animasi konvolusi.
  4. Kemudian kita mengatur nilai alpha untuk descriptionLabel dan closeButton untuk membuatnya terlihat.
  5. Selanjutnya, hapus sudut bulat dan atur bingkai baru untuk sel. Sel akan ditampilkan di layar penuh.
  6. Selanjutnya kita pindahkan sel tetangga.
  7. Sekarang panggil metode animator.addComplete () untuk menonaktifkan interaksi gambar koleksi. Ini mencegah pengguna untuk menggulirnya selama ekspansi sel. Juga ubah status sel saat ini. Penting untuk mengubah keadaan sel, dan hanya setelah itu animasi berakhir.

Sekarang tambahkan animasi konvolusi. Singkatnya, hanya saja kita mengembalikan sel ke keadaan sebelumnya:

 private func collapse() { guard let collectionView = self.collectionView, let index = self.index else { return } animator.addAnimations { self.descriptionLabel.alpha = 0 self.closeButton.alpha = 0 self.layer.cornerRadius = self.cornerRadius self.frame = self.initialFrame! if let leftCell = collectionView.cellForItem(at: IndexPath(row: index - 1, section: 0)) { leftCell.center.x += 50 } if let rightCell = collectionView.cellForItem(at: IndexPath(row: index + 1, section: 0)) { rightCell.center.x -= 50 } self.layoutIfNeeded() } animator.addCompletion { position in switch position { case .end: self.state = self.state.change collectionView.isScrollEnabled = true collectionView.allowsSelection = true default: () } } animator.startAnimation() } 

Sekarang saatnya mengkompilasi dan menjalankan aplikasi. Coba klik pada sel dan Anda akan melihat animasinya. Untuk menutup gambar, klik ikon silang di sudut kanan atas.

Menambahkan pemrosesan gerakan


Anda dapat mengklaim mencapai hasil yang sama menggunakan UIView.animate . Apa gunanya menggunakan UIViewPropertyAnimator ?

Nah, ini saatnya membuat animasi menjadi interaktif. Tambahkan UIPanGestureRecognizer dan properti baru bernama popupOffset untuk melacak seberapa banyak sel dapat dipindahkan. Mari kita deklarasikan variabel-variabel ini di kelas CityCollectionViewCell :

 private let popupOffset: CGFloat = (UIScreen.main.bounds.height - cellSize.height)/2.0 private lazy var panRecognizer: UIPanGestureRecognizer = { let recognizer = UIPanGestureRecognizer() recognizer.addTarget(self, action: #selector(popupViewPanned(recognizer:))) return recognizer }() 

Kemudian tambahkan metode berikut untuk mendaftarkan definisi gesek:

 override func awakeFromNib() { self.addGestureRecognizer(panRecognizer) } 

Sekarang Anda perlu menambahkan metode popupViewPanned untuk melacak gerakan gesek. Rekatkan kode berikut ke CityCollectionViewCell :

 @objc func popupViewPanned(recognizer: UIPanGestureRecognizer) { switch recognizer.state { case .began: toggle() animator.pauseAnimation() case .changed: let translation = recognizer.translation(in: collectionView) var fraction = -translation.y / popupOffset if state == .expanded { fraction *= -1 } animator.fractionComplete = fraction case .ended: animator.continueAnimation(withTimingParameters: nil, durationFactor: 0) default: () } } 

Ada tiga negara. Di awal gerakan, kami menginisialisasi animator menggunakan metode sakelar dan segera menghentikannya. Sementara pengguna menyeret sel, kami memperbarui animasi dengan mengatur properti pengali fractionComplete . Ini adalah keajaiban utama animator yang memungkinkan mereka untuk mengontrol. Akhirnya, ketika pengguna melepaskan jarinya, metode animator continueAnimation dipanggil untuk melanjutkan animasi. Kemudian sel akan bergerak ke posisi target.

Setelah memulai aplikasi, Anda dapat menarik sel ke atas untuk mengembangkannya. Lalu seret sel yang diperluas ke bawah untuk menutupnya.

Animasi sekarang terlihat cukup bagus, tetapi mengganggu animasi di tengah tidak mungkin. Karena itu, untuk membuat animasi sepenuhnya interaktif, Anda perlu menambahkan fungsi lain - interupsi. Pengguna dapat memulai memperluas / runtuh animasi seperti biasa, tetapi animasi harus dihentikan segera setelah pengguna mengklik sel selama siklus animasi.

Untuk melakukan ini, simpan kemajuan animasi dan kemudian ambil nilai ini untuk menghitung persentase penyelesaian animasi.

Pertama, nyatakan properti baru di CityCollectionViewCell :

 private var animationProgress: CGFloat = 0 

Kemudian perbarui blok .began metode popupViewPanned dengan baris kode berikut untuk mengingat progresnya:

 animationProgress = animator.fractionComplete 

Di blok .changed , Anda perlu memperbarui baris kode berikut untuk menghitung persentase penyelesaian dengan benar:

 animator.fractionComplete = fraction + animationProgress 

Sekarang aplikasi siap untuk diuji. Jalankan proyek dan lihat apa yang terjadi. Jika semua tindakan dilakukan dengan benar mengikuti instruksi saya, animasi akan terlihat seperti ini:

gambar

Animasi terbalik


Anda dapat menemukan cacat untuk implementasi saat ini. Jika Anda menarik sel sedikit dan mengembalikannya ke posisi semula, sel akan terus membesar saat Anda melepaskan jari Anda. Mari kita selesaikan masalah ini untuk membuat animasi interaktif lebih baik.
Mari perbarui blok .end dari metode popupViewPanned , seperti yang dijelaskan di bawah ini:

 let velocity = recognizer.velocity(in: self) let shouldComplete = velocity.y > 0 if velocity.y == 0 { animator.continueAnimation(withTimingParameters: nil, durationFactor: 0) break } switch state { case .expanded: if !shouldComplete && !animator.isReversed { animator.isReversed = !animator.isReversed } if shouldComplete && animator.isReversed { animator.isReversed = !animator.isReversed } case .collapsed: if shouldComplete && !animator.isReversed { animator.isReversed = !animator.isReversed } if !shouldComplete && animator.isReversed { animator.isReversed = !animator.isReversed } } animator.continueAnimation(withTimingParameters: nil, durationFactor: 0) 

Sekarang kita memperhitungkan kecepatan gerakan untuk menentukan apakah animasi harus dibalik.

Dan akhirnya, masukkan baris kode lain ke dalam blok yang diubah . Letakkan kode ini di sebelah kanan perhitungan animator.fractionComplete .

 if animator.isReversed { fraction *= -1 } 

Ayo jalankan aplikasi lagi. Sekarang semuanya harus bekerja tanpa gagal.

gambar

Perbaiki gerakan pan


Jadi, kami telah menyelesaikan implementasi animasi menggunakan UIViewPropertyAnimator . Namun, ada satu kesalahan yang tidak menyenangkan. Anda mungkin telah bertemu dengannya saat menguji aplikasi. Masalahnya adalah bahwa menggulir sel secara horizontal tidak mungkin. Mari kita coba geser ke kiri / kanan melalui sel, dan kita dihadapkan dengan masalah.

Alasan utama terkait dengan UIPanGestureRecognizer yang kami buat . Itu juga menangkap gerakan swipe, dan bertentangan dengan UICollectionViewView gesture built-in.

Meskipun pengguna masih dapat menggulir melalui bagian atas / bawah sel atau ruang antar sel untuk menggulir kota, saya masih tidak menyukai antarmuka pengguna yang buruk. Mari kita perbaiki.

Untuk menyelesaikan konflik, kita perlu mengimplementasikan metode delegasi yang disebut gestRecognizerShouldBegin (_ :) . Metode ini mengontrol apakah pengenal isyarat harus terus menginterpretasikan sentuhan. Jika Anda mengembalikan false dalam metode ini, pengenal isyarat akan mengabaikan sentuhan. Jadi yang akan kita lakukan adalah memberikan alat pengenalan panorama kita sendiri kemampuan untuk mengabaikan gerakan horisontal.

Untuk melakukan ini, mari atur delegasi pengenal pan kami. Masukkan baris kode berikut ke inisialisasi panRecognizer (Anda dapat meletakkan kode tepat sebelum pengenal balik :

 recognizer.delegate = self 

Kemudian, kami menerapkan metode gestRecognizerShouldBegin (_ :) sebagai berikut:

 override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { return abs((panRecognizer.velocity(in: panRecognizer.view)).y) > abs((panRecognizer.velocity(in: panRecognizer.view)).x) } 

Kami akan membuka / menutup jika kecepatan vertikal lebih besar dari kecepatan horizontal.

Wow! Mari kita coba aplikasi ini lagi. Sekarang Anda dapat bergerak di sekitar daftar kota dengan menggesekkan kiri / kanan melintasi sel.

gambar

Bonus: Fitur Sinkronisasi Kustom


Sebelum kita menyelesaikan tutorial ini, mari kita bicara tentang fungsi pengaturan waktu. Apakah Anda masih ingat kasus ketika pengembang meminta Anda untuk menerapkan fungsi sinkronisasi khusus untuk animasi yang Anda buat?

Biasanya, Anda harus mengubah UIView.animation menjadi CABasicAnimation atau membungkusnya dengan CATransaction . Dengan UIViewPropertyAnimator, Anda dapat dengan mudah mengimplementasikan fungsi pengaturan waktu kustom.

Fungsi pengaturan waktu (atau fungsi pelonggaran) dipahami sebagai fungsi kecepatan animasi yang memengaruhi laju perubahan satu atau properti animasi lainnya. Empat jenis saat ini didukung: easyInOut, easyIn, easyOut, linear.

Ganti inisialisasi animator dengan fungsi pengaturan waktu ini (coba gambar kurva kubik Bezier Anda sendiri) sebagai berikut:

 private lazy var animator: UIViewPropertyAnimator = { let cubicTiming = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.17, y: 0.67), controlPoint2: CGPoint(x: 0.76, y: 1.0)) return UIViewPropertyAnimator(duration: 0.3, timingParameters: cubicTiming) }() 

Atau, alih-alih menggunakan parameter sinkronisasi kubik, Anda juga dapat menggunakan sinkronisasi pegas, misalnya:

  let springTiming = UISpringTimingParameters(mass: 1.0, stiffness: 2.0, damping: 0.2, initialVelocity: .zero) 

Cobalah untuk memulai proyek lagi dan lihat apa yang terjadi.

Kesimpulan


Melalui UIViewPropertyAnimator, Anda dapat meningkatkan layar statis dan interaksi pengguna melalui animasi interaktif.

Saya tahu bahwa Anda tidak sabar untuk menyadari apa yang telah Anda pelajari dalam proyek Anda sendiri. Jika Anda menerapkan pendekatan ini dalam proyek Anda, itu akan sangat keren, beri tahu saya tentang ini dengan meninggalkan komentar di bawah.

Sebagai referensi, di sini Anda dapat mengunduh draft terakhir .

Tautan selanjutnya


Animasi profesional menggunakan UIKit - https://developer.apple.com/videos/play/wwdc2017/230/

Dokumentasi UIViewPropertyAnimator untuk Pengembang Apple - https://developer.apple.com/documentation/uikit/uiviewpropertyanimator

Source: https://habr.com/ru/post/id458970/


All Articles