どうも、久しぶりです。
無事ハイラル王国を救えました。ミファーがあざとかったと思います。
それはさておき、まえまえからやってみたかったやつがついに動いたのでみてください。
VPRubberMenuって?
Makingってアプリご存知ですか?
これです
NIKEの出してるオサレなアプリです。いろいろと面白い動きをしているので非常に触っていて楽しいです。
え?何をするアプリかって?わかんないです。
NIKEが培ってきた素材についての情報共有とかなんとか・・・
まぁそれは置いといてアプリを起動するとCollectionViewでカテゴリを選ぶのですがそのCollectionViewの動きが面白いです。なんかゴムみたいにポヨンポヨン弾みます。
意味もなくスクロールしてしまいます。
そうすると次に考えるのが**どうやって作ってるのか?ですね。
調べてたところ見つけたのがVPRubberMenu**です。
この人もMakingをみて触発されて作ったらしい・・・(すげぇ
動かしてみよう!
やったぜ、これ動かしてみよう!と思いforkするじゃないですか、動かないです
そもそもエラーでビルドできないです。
中身がobjective-cで書かれていてもうobjective-cの記憶が薄れ始めてる僕にとってはロジック理解しつつobjective-cを治すのがダルンダルンだったのでSwiftで書き直しながらやってること理解するようにしました。
Swift化した
で、できたのが**これ**です
こんな感じに動きます
コードをみながらの解説
このアニメーションを作り出すに関係する要素は3つです。
- UICollectionViewFlowLayout
- UIDynamicAnimator
- UIBezierPath
です。
ここまで言っといてですがそれぞれと今回真面目に向かい合ったのでまだ仲良くなりきれてないので途中説明がふわってなりますのでそこはダメやなぁこいつと思って指摘なり調べたりしてください。(汗)
UICollectionViewFlowLayoutとUIDynamicAnimator
この二つは一緒になってゴムみたいな動作をCollectionViewに与えます。
主な仕事はprepareメソッドで行われます。
レイアウトの更新時に毎回呼ばれるやつですね
ここで可視領域計算してその中に新しく入って来たCellにBehaviour貼り付けて逆に領域から出ていったCellからBehaviourを剥がします。
可視領域を使ってその中のCellのIndexを取るのはこの部分ですね
// 描画範囲の決定 collectionViewのサイズにinsetはx軸とy軸でそれぞれ-100(画面外にでてる状態)にする
let visibleRect = CGRect(origin: collectionView?.bounds.origin ?? .zero , size: collectionView?.bounds.size ?? .zero).insetBy(dx: -100, dy: -100)
// 可視領域に入っているcellのIndexを取得
let itemsInVisibleRectArray: [UICollectionViewLayoutAttributes] = super.layoutAttributesForElements(in: visibleRect) ?? []
可視領域ですが計算では画面外に少し遊びを作ってます。
多分ですけど画面外出た瞬間もしくは入って来た瞬間にアニメーションをつけたりけしたりした場合中途半端な慣性がついたアニメーションになるからかなぁと思います
※X軸の-100は僕が元のcellの大きさと変えちゃったからもしかしたらいらないかも、元のコードでもあんまりいる気がしていない
BehaviourですがUIAttachmentBehaviorを利用します。
これを使うとバネのような動きをViewに与えることができます。
こんな感じで簡単に作れます
let springBehaviour = UIAttachmentBehavior(item: item, attachedToAnchor: center)
springBehaviour.length = 0.0 // 2点間の距離指定
springBehaviour.damping = 0.5 // 減衰率、0に近づくほど振動が止まらなくなる
springBehaviour.frequency = 0.8 //振動数、これが大きいとよく跳ねる
springBehaviour.action = { // アニメーションが行われるたびに呼ばれる。ここではセルの高さが変わる
if let cell = self.collectionView?.cellForItem(at: item.indexPath) as? RubberCell {
cell.setNewHeight(self.latestDelta)
}
FlowLayoutだけで跳ねるアニメーションはできます。
ただ、これだけだとそれっぽくないと思います。引っ張られて動いてるように見せるためにCellの形を変えるとそれっぽくなります
UIBezierPath
これはCellで使われていました。こんな感じです。
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 0.0, y: 35.0)) // 起点
bezierPath.addCurve(to: CGPoint(x: frame.size.width,y: 35.0), // 終着点
controlPoint1: CGPoint(x: frame.size.width/2, y: 35+height),
controlPoint2: CGPoint(x: frame.size.width, y: 35))
bezierPath.addLine(to: CGPoint(x: frame.size.width, y: frame.size.height))
bezierPath.addLine(to: CGPoint(x: 0.0, y: frame.size.height))
bezierPath.addLine(to: CGPoint(x: 0.0, y: 35.0))
丸く線を書くのはこちらの記事を読むと理解が進むのかなぁ〜〜?と思います。
正直帰着点は僕もよくわかってないです。はい
先ほどBehaviourでsetされたactionでアニメーションが実行されるたびに上記のbezierPathをつかいCellのlayer.maskを更新することでCellの境界を丸く歪ませて引っ張られるように見せることができるってことですねぇ
あとがき
おもしろいUIと思って作ってみましたがいろいろ勉強になる事ばっかでした。
CollectionViewFlowLayoutとBehaviourはもうちょっと深ぼって調べようかと思いました。