iOS - 禮物連擊動畫

簡單結構分析:

在做禮物點擊的時候,我們先把需要顯示在屏幕上的禮物放入到一個容器containerView中如下圖的紅色框,將一個禮物當做成一個通道channelView,當屏幕或者容器顯示不了這么多通道的時候,我們將還未執行的通道禮物3放入一個緩存數組中等待執行

Snip20170811_3.png

01.gif

一 :DigitLabel基本效果

1.數字的描邊效果: 重寫override func drawText(in rect: CGRect)方法,先畫出一條橙色的外邊然后再畫一條白色的 這樣顯示的效果就很描邊一樣。
class HJGiftDigitLabel: UILabel {

    override func drawText(in rect: CGRect) {
        
        // 1.獲取當前上下文
        let content = UIGraphicsGetCurrentContext()
        
        content?.setLineJoin(.round)
        content?.setLineWidth(5.0)
        content?.setTextDrawingMode(.stroke)
        textColor = UIColor.orange
        
        super.drawText(in: rect)
        
        content?.setTextDrawingMode(.fill)
        textColor = UIColor.white
        
        super.drawText(in: rect)
        
        
    }

2.數字的動畫效果:首先使得數字動畫放大多倍,然后再縮小,再回到原來的大小。
func ShowDigitAnimation(_ complection: @escaping () -> ()) {
        
        UIView.animateKeyframes(withDuration: 0.25, delay: 0, options: [], animations: {
            
            UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5, animations: {
                
                self.transform = CGAffineTransform(scaleX: 3.0, y: 3.0)
                
            })
            
            UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.5, animations: {
                
                self.transform = CGAffineTransform(scaleX: 0.7, y: 0.7)
                
            })
            
        }) { (isFinished) in
            
            UIView.animate(withDuration: 0.25, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 10, options: [], animations: {
                
                self.transform = CGAffineTransform.identity
                
            }, completion: { (isFinifshed) in
                
                complection()
                
            })
            
            
        }

二 :禮物通道ChannelView的設計與實現

1.通過xib描述好View,并使用代碼完成基本的UI設置。
// MARK:- 設置UI界面
extension HJGiftChannelView {
    override func layoutSubviews() {
        super.layoutSubviews()
        
        bgView.layer.cornerRadius = frame.height * 0.5
        iconImageView.layer.cornerRadius = frame.height * 0.5
        bgView.layer.masksToBounds = true
        iconImageView.layer.masksToBounds = true
        iconImageView.layer.borderWidth = 1
        iconImageView.layer.borderColor = UIColor.green.cgColor
    }
}

2.我們的用戶名稱(userName) 用戶頭像(userIcon) 禮物名稱(GiftName) 禮物圖片(GiftURL)基本都是由外部的參數傳入進來,所以我們定義一個GiftChannelModel
class HJGiftChannelModel: NSObject {

    var senderName : String = ""
    var senderUrl : String = ""
    var GiftName : String = ""
    var GiftUrl : String = ""
    
    init(_ senderName : String,_ senderUrl : String,_ GiftName : String,_ GiftUrl : String) {
        
        self.senderName = senderName
        self.senderUrl = senderUrl
        self.GiftName = GiftName
        self.GiftUrl = GiftUrl
        
    }
    
    // 重寫isEqual方法
    override func isEqual(_ object: Any?) -> Bool {
        
        guard let object = object as? HJGiftChannelModel  else {
            return false
        }
        
        guard object.senderName == senderName && object.GiftName == GiftName else {
            
            return false
        }
        
        return true
    }
    
}

  1. 通過定義好的GiftChannelModel給我們的GiftChannelView的UI屬性設置好基本的信息
 var giftModel : HJGiftChannelModel? {
    
        didSet{
        
            // 1. 模型校驗
            guard let giftModel = giftModel else {
                return
            }
            // 2. 設置基本信息
            iconImageView.image = UIImage(named: giftModel.senderUrl)
            senderLabel.text = giftModel.senderName
            giftDescLabel.text = "送出禮物:【\(giftModel.GiftName)】"
            giftImageView.image = UIImage(named: giftModel.GiftUrl)
4. 執行動畫,我們的動畫效果是直接從屏幕的直接進入到屏幕上 并且在屏幕上顯示三秒之后才開始消失動畫,所以在我們需要知道禮物通道(ChannelView)的狀態是在animating //正在執行動畫 idle //閑置的 willEnd //將要結束動畫 endAnimating //已經結束動畫所以定義一個枚舉 enum HJGiftChannelState
enum HJGiftChannelState {

    case idle  //閑置的
    case animating //正在執行動畫
    case willEnd  //將要結束動畫
    case endAnimating //已經結束動畫
    
}

動畫顯示的時候我們需要第一步:當顯示在屏幕上的時候則調用DigitLabel的動畫,如果動畫的狀態即將的消失的時候則我們讓ChannelView在屏幕上停留3秒再消失

//MARK: -執行動畫
extension HJGiftChannelView {

    func performAnimation(){
        
        digitLabel.alpha = 1.0
        digitLabel.text = " x1 "
        
        UIView.animate(withDuration: 0.25, animations: {
            
            self.alpha = 1.0
            self.frame.origin.x = 0
            
        }) { (isFinished) in
            
            self.performDigitAnimation()
        }
    }
    
    func performDigitAnimation(){
    
        currentNumber += 1
        digitLabel.text = " x\(currentNumber) "
        
        digitLabel.ShowDigitAnimation {
        
            if self.currentCacheNumber > 0 {
            
                self.currentCacheNumber -= 1
            
                self.performDigitAnimation()
                
            }else {
            
                self.channelViewState = .willEnd
                self.perform(#selector(self.performEndAnimation), with: nil, afterDelay: 3.0)
            }
            
        }
           
    }

如果禮物在即將消失self.channelViewState = .willEnd的狀態下,用戶又點擊了贈送禮物,則需要重新啟動動畫并將之前的停留3秒狀態取消

 if channelViewState == .willEnd {
            
            performAnimation()
            // 取消延遲3秒
            NSObject.cancelPreviousPerformRequests(withTarget: self)

如果不是的上述的狀態則讓其加入我們的緩存中等待執行

extension HJGiftChannelView {
    //添加到緩存池
    func addOneToCache(){
        if channelViewState == .willEnd {
            
            performAnimation()
            // 取消延遲3秒
            NSObject.cancelPreviousPerformRequests(withTarget: self)
            
        } else {
        
            currentCacheNumber += 1
        }
        
    }

三 : 將ChannelView 添加到容器GiftContainerView

import UIKit

private let kChannelCount = 2
private let kChannelViewH : CGFloat = 40
private let kChannelMargin : CGFloat = 10

class HJGiftContainerView: UIView {

    fileprivate lazy var channelViews : [HJGiftChannelView] = [HJGiftChannelView]()
    fileprivate lazy var cacheGiftModels : [HJGiftChannelModel] = [HJGiftChannelModel]()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        setupUI()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    
}


//MARK: -設置UI
extension HJGiftContainerView {
    
   fileprivate func setupUI(){
    
        let w : CGFloat = frame.width
        let h : CGFloat = kChannelViewH
        let x : CGFloat = 0
        
        for i in 0..<kChannelCount {
            let  y : CGFloat = (h + kChannelMargin) * CGFloat(i)
            
           let channelView = HJGiftChannelView.loadFormNib()
            channelView.frame = CGRect(x: x, y: y, width:w, height: h)
            channelView.alpha = 0
            addSubview(channelView)
            
            channelViews.append(channelView)
            

判斷正在忙的ChanelView和新贈送的新禮物的用戶名稱禮物名稱 (username && giftname)是否相同

extension HJGiftContainerView {
   
    func showModel(_ giftModel : HJGiftChannelModel){
    
        // 1.判斷正在忙的ChanelView和贈送的新禮物的(username/giftname)
        if let channelView = checkUsingChanelView(giftModel) {
            
            channelView.addOneToCache()
            return
        }
        
    
        // 2. 判斷有沒有閑置的channelView
        if let channelView = checkIdleChannelView(){
        
            channelView.giftModel = giftModel
            
            return
        }
        
        // 3. 將數據加入緩存中
        cacheGiftModels.append(giftModel)
        
    }
    
   //檢查正在使用的channelView
    private func checkUsingChanelView(_ giftModel : HJGiftChannelModel) -> HJGiftChannelView? {
        
        for channelView in channelViews {
            
            if giftModel.isEqual(channelView.giftModel)
              && channelView.channelViewState != .endAnimating {
                
                return channelView
            }
        }
        
        return nil
    }
    
    //檢查有沒有閑置的channel
    private func checkIdleChannelView() -> HJGiftChannelView? {
        
        for channelView in channelViews {
            
            if channelView.channelViewState == .idle {
                return channelView
            }
            
        }
        
        return nil
    }

}

監聽ChannelView什么時候完成動畫,判斷緩存中是否有內容,通過reversed()反序遍歷的方式來確定我們的firstModel相同的模型放入到ChanelView緩存中繼續執行動畫

  channelView.complectionCallback = { channelView in
            
                // 1.取出緩存中的模型
                guard self.cacheGiftModels.count != 0 else {
                    return
                }
                
                // 2.取出緩存中的第一個模型數據
                let firstModel = self.cacheGiftModels.first!
                self.cacheGiftModels.removeFirst()
               
                // 3.讓閑置的channelView執行動畫
                channelView.giftModel = firstModel
                
                 // 4.將數組中剩余有和firstModel相同的模型放入到ChanelView緩存中
                for i in (0..<self.cacheGiftModels.count).reversed() {
                    
                    let giftModel = self.cacheGiftModels[i]
                  
                    if giftModel .isEqual(firstModel) {
                        
                        channelView.addOneToCache()
                        
                        self.cacheGiftModels.remove(at: i)
                    }
                }
                
            }
        }
        
    
    }

    
}

Demo下載

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,558評論 25 708
  • 發現 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,257評論 4 61
  • 蜜蜂的好伙伴是花朵,羊兒的好伙伴是青草,魚兒的好伙伴是水,大樹的好伙伴是小鳥,我的好伙伴是書。假如知識是一片草地的...
    楊藝萱閱讀 252評論 0 0
  • 2017年10月20號 蕙蘭的咖啡冥想 感恩: 感恩祖先傳承護佑,感恩古圣先賢的智慧傳承,感恩父母養育之恩,感恩母...
    蕙蘭坊閱讀 185評論 0 0
  • "我知道,我是想問,他們在干什么?" "他們想鉆進來,吃了我們." "不會吧,我靠,我想過很多種死法,就是沒被想過...
    笑君殺手閱讀 236評論 0 0