iOS - 禮物連擊動(dòng)畫(huà)

簡(jiǎn)單結(jié)構(gòu)分析:

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

Snip20170811_3.png

01.gif

一 :DigitLabel基本效果

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

    override func drawText(in rect: CGRect) {
        
        // 1.獲取當(dāng)前上下文
        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.數(shù)字的動(dòng)畫(huà)效果:首先使得數(shù)字動(dòng)畫(huà)放大多倍,然后再縮小,再回到原來(lái)的大小。
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的設(shè)計(jì)與實(shí)現(xiàn)

1.通過(guò)xib描述好View,并使用代碼完成基本的UI設(shè)置。
// MARK:- 設(shè)置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.我們的用戶(hù)名稱(chēng)(userName) 用戶(hù)頭像(userIcon) 禮物名稱(chēng)(GiftName) 禮物圖片(GiftURL)基本都是由外部的參數(shù)傳入進(jìn)來(lái),所以我們定義一個(gè)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
        
    }
    
    // 重寫(xiě)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. 通過(guò)定義好的GiftChannelModel給我們的GiftChannelView的UI屬性設(shè)置好基本的信息
 var giftModel : HJGiftChannelModel? {
    
        didSet{
        
            // 1. 模型校驗(yàn)
            guard let giftModel = giftModel else {
                return
            }
            // 2. 設(shè)置基本信息
            iconImageView.image = UIImage(named: giftModel.senderUrl)
            senderLabel.text = giftModel.senderName
            giftDescLabel.text = "送出禮物:【\(giftModel.GiftName)】"
            giftImageView.image = UIImage(named: giftModel.GiftUrl)
4. 執(zhí)行動(dòng)畫(huà),我們的動(dòng)畫(huà)效果是直接從屏幕的直接進(jìn)入到屏幕上 并且在屏幕上顯示三秒之后才開(kāi)始消失動(dòng)畫(huà),所以在我們需要知道禮物通道(ChannelView)的狀態(tài)是在animating //正在執(zhí)行動(dòng)畫(huà) idle //閑置的 willEnd //將要結(jié)束動(dòng)畫(huà) endAnimating //已經(jīng)結(jié)束動(dòng)畫(huà)所以定義一個(gè)枚舉 enum HJGiftChannelState
enum HJGiftChannelState {

    case idle  //閑置的
    case animating //正在執(zhí)行動(dòng)畫(huà)
    case willEnd  //將要結(jié)束動(dòng)畫(huà)
    case endAnimating //已經(jīng)結(jié)束動(dòng)畫(huà)
    
}

動(dòng)畫(huà)顯示的時(shí)候我們需要第一步:當(dāng)顯示在屏幕上的時(shí)候則調(diào)用DigitLabel的動(dòng)畫(huà),如果動(dòng)畫(huà)的狀態(tài)即將的消失的時(shí)候則我們讓ChannelView在屏幕上停留3秒再消失

//MARK: -執(zhí)行動(dòng)畫(huà)
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的狀態(tài)下,用戶(hù)又點(diǎn)擊了贈(zèng)送禮物,則需要重新啟動(dòng)動(dòng)畫(huà)并將之前的停留3秒狀態(tài)取消

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

如果不是的上述的狀態(tài)則讓其加入我們的緩存中等待執(zhí)行

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: -設(shè)置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和新贈(zèng)送的新禮物的用戶(hù)名稱(chēng)禮物名稱(chēng) (username && giftname)是否相同

extension HJGiftContainerView {
   
    func showModel(_ giftModel : HJGiftChannelModel){
    
        // 1.判斷正在忙的ChanelView和贈(zèng)送的新禮物的(username/giftname)
        if let channelView = checkUsingChanelView(giftModel) {
            
            channelView.addOneToCache()
            return
        }
        
    
        // 2. 判斷有沒(méi)有閑置的channelView
        if let channelView = checkIdleChannelView(){
        
            channelView.giftModel = giftModel
            
            return
        }
        
        // 3. 將數(shù)據(jù)加入緩存中
        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
    }
    
    //檢查有沒(méi)有閑置的channel
    private func checkIdleChannelView() -> HJGiftChannelView? {
        
        for channelView in channelViews {
            
            if channelView.channelViewState == .idle {
                return channelView
            }
            
        }
        
        return nil
    }

}

監(jiān)聽(tīng)ChannelView什么時(shí)候完成動(dòng)畫(huà),判斷緩存中是否有內(nèi)容,通過(guò)reversed()反序遍歷的方式來(lái)確定我們的firstModel相同的模型放入到ChanelView緩存中繼續(xù)執(zhí)行動(dòng)畫(huà)

  channelView.complectionCallback = { channelView in
            
                // 1.取出緩存中的模型
                guard self.cacheGiftModels.count != 0 else {
                    return
                }
                
                // 2.取出緩存中的第一個(gè)模型數(shù)據(jù)
                let firstModel = self.cacheGiftModels.first!
                self.cacheGiftModels.removeFirst()
               
                // 3.讓閑置的channelView執(zhí)行動(dòng)畫(huà)
                channelView.giftModel = firstModel
                
                 // 4.將數(shù)組中剩余有和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下載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,983評(píng)論 25 709
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,217評(píng)論 4 61
  • 蜜蜂的好伙伴是花朵,羊兒的好伙伴是青草,魚(yú)兒的好伙伴是水,大樹(shù)的好伙伴是小鳥(niǎo),我的好伙伴是書(shū)。假如知識(shí)是一片草地的...
    楊藝萱閱讀 276評(píng)論 0 0
  • 2017年10月20號(hào) 蕙蘭的咖啡冥想 感恩: 感恩祖先傳承護(hù)佑,感恩古圣先賢的智慧傳承,感恩父母養(yǎng)育之恩,感恩母...
    蕙蘭坊閱讀 207評(píng)論 0 0
  • "我知道,我是想問(wèn),他們?cè)诟墒裁?" "他們想鉆進(jìn)來(lái),吃了我們." "不會(huì)吧,我靠,我想過(guò)很多種死法,就是沒(méi)被想過(guò)...
    笑君殺手閱讀 256評(píng)論 0 0

友情鏈接更多精彩內(nèi)容