簡單結構分析:
在做禮物點擊的時候,我們先把需要顯示在屏幕上的禮物放入到一個容器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
}
}
- 通過定義好的
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)
}
}
}
}
}
}