接著上一個主題的內容 Lineage M 卡包動畫 – Frame animation 這次要加入抽卡的元素。
當我們打開卡包的時候,會有多張卡片在畫面上輪轉,最後慢慢停到一張卡片。
Slot Machine – 角子機動畫
除了前一個主題做的卡包動畫外,這次新增的部分有:
- 打開卡包時,閃爍一下。
- 多張卡片開始向做快速移動
- 最後慢慢停到一張卡片上
- 再次閃爍一下
- 顯示卡片背景氣息動畫(圖片在快速移動的時候不顯示)
CardModel
卡片根據稀有度會有不一樣的背景色和氣息動畫。
1 2 3 4 5 6 7 8 9 10 11 |
enum CardLevel { case gray case green case blue case red } struct CardModel { var level:CardLevel var image:UIImage } |
通過 CardModel & CardLevel 建立一批不同稀有度的卡片。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var cards = [CardModel]() for i in 1...36 { var cardLevel:CardLevel = .gray switch i % 4 { case 0: cardLevel = .red case 1: cardLevel = .blue case 2: cardLevel = .green case 3: cardLevel = .gray default: break; } let card = CardModel(level: cardLevel, image: UIImage(named: "monster-\(i)")!) cards.append(card) } |
SlotView
繼續沿用上一個主題的卡包動畫,但原本打開卡片就看到卡片內容的部分拿掉了,而是變成打開後先輪播所有卡片,最後停留在最後一張卡片上。
建立輪播圖
無視性能的最簡單實現方法,建立一個 monsterScrollView 根據卡片數量來設定 width 然後將所有的卡片都放上去。
根據 CardModel 提供的 level 來設定不一樣的背景圖,結合卡片圖放在 monsterScrollView 上面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
fileprivate func addMonsterView() { monsterScrollViewMask = UIView() monsterScrollViewMask!.frame = frame monsterScrollViewMask?.clipsToBounds = true addSubview(monsterScrollViewMask!) let monsterScrollViewFrame = CGRect(x: 0, y: 0, width: frame.width * CGFloat(cards.count), height: frame.height) monsterScrollView = UIView(frame: monsterScrollViewFrame) monsterScrollViewMask?.addSubview(monsterScrollView!) for i in 0..<cards.count { let monsterFrame = CGRect(x: CGFloat(i) * frame.width, y: 0, width: frame.width, height: frame.height) let monsterLevelView = UIImageView(frame: monsterFrame) switch cards[i].level { case .gray: monsterLevelView.image = UIImage(named:"img-card-bg-gray") case .green: monsterLevelView.image = UIImage(named:"img-card-bg-green") case .blue: monsterLevelView.image = UIImage(named:"img-card-bg-blue") case .red: monsterLevelView.image = UIImage(named:"img-card-bg-red") } monsterScrollView?.addSubview(monsterLevelView) let monsterImageView = UIImageView(frame: monsterFrame) monsterImageView.image = cards[i].image monsterScrollView!.contentMode = .scaleAspectFit monsterScrollView!.addSubview(monsterImageView) } } |
輪播圖動畫
將很長的 monsterScrollView 直接 addSubView 在 SlotView 上,當打開卡包的時候將整個 monsterScrollView 向左邊移動。
這時候看起來會下面的效果,但其實我們希望卡片只會顯示在白色的框內,這方面可以通過 mask 或者 clicpsToBounds 來實現。
需要注意的是,因爲我們所有的 View 都是放在 SlotView 上的,
如果單純直接對 SlotView 設定 clickpsToBounds = true 卡片外的效果就會被切掉,比如卡包背後超出卡片的氣息動畫。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
fileprivate func startScrollingSlot() { if monsterScrollView == nil { return } if isScrolling { return } else { isScrolling = true } shineOnce() // show border when scrolling layer.borderColor = UIColor.white.cgColor layer.borderWidth = 1 // reset position let originalFrame = monsterScrollView!.frame monsterScrollView!.frame = CGRect(x: 0, y: 0, width: originalFrame.width, height: originalFrame.height) let newFrame = CGRect(x: -self.frame.width * CGFloat(cards.count - 1), y: 0, width: self.frame.width, height: self.frame.height) UIView.animate(withDuration: 3, delay: 0, options: .curveEaseOut, animations: { self.monsterScrollView!.frame = newFrame }, completion: { finished in self.isScrolling = false self.shineOnce() self.showCardAura() // hide border when scroll ended self.layer.borderWidth = 0.0 }) } |
閃現動畫
打開卡包的一瞬間以及卡片移動停止的時候,畫面都會閃現一下,這裡一樣是通過一張光亮的圖片,通過控制 Alpha 來實現。
1 2 3 4 5 6 7 8 9 10 11 12 |
fileprivate func addShineView() { let shineMask = UIView(frame: frame) shineMask.clipsToBounds = true addSubview(shineMask) shineView = UIImageView(frame: CGRect(x: 0, y: 0, width: frame.width * 1.3, height: frame.height * 1.3)) shineView?.center = center shineView!.image = UIImage(named:"img-card-shime")! shineView?.contentMode = .scaleAspectFill shineView!.alpha = 0.0 shineMask.addSubview(shineView!) } |
筆記
優化
這次因為只有一個長條圖在畫面上移動,所以即使是在最近效能不好的模擬器上移動都不會有卡頓的感覺,但實際上 SlotMachine 常常是要同時有多個輪播圖的效果,那麼上面的例子就不適合了。
更好的作法應該是類似 UITableView/UICollectionView 的方式,留個 preload 空間,剩下的圖都不留在畫面上,比如這次的例子,畫面上其實留5張圖片就夠用了。
Reference
- 在 Github 上有本文的 Source Code