简单结构分析:
在做礼物点击的时候,我们先把需要显示在屏幕上的礼物放入到一个容器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..
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..
let giftModel = self.cacheGiftModels[i]
if giftModel .isEqual(firstModel) {
channelView.addOneToCache()
self.cacheGiftModels.remove(at: i)
}
}
}
}
}
}