想必之前QQ空間的點贊效果你們都知道吧,點贊以後按鈕周圍會有一圈爆裂的小圓點;還有微信的紅包雨表情動畫等,以及煙花,火焰效果。這些看似很炫酷的動畫可能讓咱們敬而遠之,可是其實iOS封裝的很好,利用簡單的幾行代碼就能完成很炫酷的動畫效果。因爲目前正在玩兒iOS動畫的內容,利用iOS的CAEmitterLayer結合CAEmitterCell可以達這些效果。不BB了,先上幾個效果圖。代碼已傳github EmitterAnimation。
馬丹 有沒有辦法一次性放不少個gif呀。。。。。。。git
CAEmitterLayer
與CAEmitterCell
CAEmitterLayer
是CALayer
的一個經常使用子類,CALayer
的子類有不少,若是能很好的使用它們會獲得一些意想不到的效果。CAEmitterLayer
就是其中之一,CAEmitterLayer
是用於實現基於Core Animation
的粒子發生器系統。github
所謂粒子,就是不少小顆粒,當讓QQ空間的點贊動畫的粒子也能夠不用CAEmitterLayer
粒子發生器來實現,不過這樣會麻煩不少。在粒子系統中,CAEmitterLayer
是用來發射粒子的,他所發射的粒子就是CAEmitterCell
(固然粒子也能夠發射粒子,也就是CAEmitterCell也能夠發射CAEmitterCel
l)。能夠認爲CAEmitterLayer
是CAEmitterCell
的工廠,經過不一樣的設置就會不斷的產生想要的粒子。數組
原理其實很簡單,可是動畫就是這樣,須要花時間去理解屬性,只有很好的用它的屬性,才能達到很炫酷的效果。查看API會發現CAEmitterLayer
和CAEmitterCell
的屬性都是不少的,而且有不少相同的屬性。在CAEmitterLayer
中,一些屬性決定了粒子從什麼樣的幾何特性上發射出來,這個幾何特性包括了位置,形狀,大小,而且還有一些渲染屬性,用於一些渲染的效果。另一些屬性CAEmitterLayer
和CAEmitterCell
都有的,在這裏可能會迷糊,可是API說明的很清楚,CAEmitterLayer的這些屬性會做爲CAEmitterCell
相同屬性的係數,舉個?,若是CAEmitterCell
的birthRate = 10
(每秒產生的粒子數量),其所屬的CAEmitterLayer
的birthRate = 2
,那麼在其餘參數默認的狀況下,這個CAEmitterCell總的每秒產生的粒子數量是10 * 2 = 20 。也就是每秒會產生20個這樣的粒子。微信
另外,會發現CAEmitterCell
的不少屬性都帶有一個Range
,好比scaleRange
、velocityRange
,這些決定粒子自身的一些特性的屬性大多都是以「中間值」 + 範圍(Range)的方式表示的。再舉個?,好比scale
= 0.5(縮放值)和scaleRange
= 0.2(縮放的範圍),那麼表示的實際CAEmitterCell
的縮放就是scale
±scaleRange
,即0.3~0.7這個範圍。post
初步瞭解了這些以後,咱們就能夠跟着代碼來實現實現一個紅包雨的功能。實現起來很簡單,只要設置好屬性就好了,這些屬性的詳細含義會在下面的篇幅仔細講解,先來試下一個小demo。就是最開始的第個效果圖。測試
CAEmitterLayer
以及它的一些模式,並添加到要顯示的view的圖層上,固然也能夠替換view
的圖層CAEmitterLayer
配置CAEmitterCell
。CAEmitterLayer
以及它的一些模式,並添加到要顯示的view
的圖層上,固然也能夠替換view
的圖層。這裏是直接添加到控制器的view
的layer
上的,這個view
的layer
是一個calyer
類型的,若是想替換掉calyer
,能夠自定義view
,並在view
裏面重寫以下方法便可實現替換// 替換view的layer + (Class)layerClass{return [CAEmitterLayer class];}
這裏並無替換,而是直接添加。固然爲了後面方便這裏把CAEmitterLayer
設置爲屬性。詳細代碼以下,能夠看註釋動畫
@interface ViewController () @property (nonatomic, strong) CAEmitterLayer * redpacketLayer; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self redpacketRain]; } /** * 紅包雨 */ - (void)redpacketRain{ // 1. 設置CAEmitterLayer CAEmitterLayer * redpacketLayer = [CAEmitterLayer layer]; [self.view.layer addSublayer:redpacketLayer]; self.redpacketLayer = redpacketLayer; redpacketLayer.emitterShape = kCAEmitterLayerLine; // 發射源的形狀 是枚舉類型 redpacketLayer.emitterMode = kCAEmitterLayerSurface; // 發射模式 枚舉類型 redpacketLayer.emitterSize = self.view.frame.size; // 發射源的size 決定了發射源的大小 redpacketLayer.emitterPosition = CGPointMake(self.view.bounds.size.width * 0.5, -10); // 發射源的位置 redpacketLayer.birthRate = 0.f; // 每秒產生的粒子數量的係數 }
到這裏CAEmitterLayer
的就設置完了,須要注意的是發射源的emitterPosition
這個屬性,是將它放在了頂部,實現從天上掉下來的效果。atom
CAEmitterLayer
配置CAEmitterCell
。在redpacketRain
方法裏面添加以下代碼。值得注意的是,粒子的內容contents
是一個CGImageRef
類型的,用UIImage
須要轉換爲CGImage
.// 2. 配置cell CAEmitterCell * snowCell = [CAEmitterCell emitterCell]; snowCell.contents = (id)[[UIImage imageNamed:@"red_paceket"] CGImage]; // 粒子的內容 是CGImageRef類型的 snowCell.birthRate = 10.f; // 每秒產生的粒子數量 snowCell.lifetime = 20.f; // 粒子的生命週期 snowCell.velocity = 8.f; // 粒子的速度 snowCell.yAcceleration = 1000.f; // 粒子再y方向的加速的 snowCell.scale = 0.5; // 粒子的縮放比例 redpacketLayer.emitterCells = @[snowCell]; // 粒子添加到CAEmitterLayer上
CAEmitterLayer
的birthRate
來實現動畫的開始和結束。- (IBAction)redpacketClick:(id)sender { [self.redpacketLayer setValue:@1.f forKeyPath:@"birthRate"]; [self performSelector:@selector(endRedpacketAnimation) withObject:nil afterDelay:2.f]; } - (void)endRedpacketAnimation{ [self.redpacketLayer setValue:@0.f forKeyPath:@"birthRate"]; }
CAEmitterLayer
與CAEmitterCell
的屬性詳解實現上面的小demo是否是很簡單?不到20行代碼而已,用到的屬性和也不多。那麼下雨下雪的效果也是這樣實現的,只不過是修改屬性值。可是這些效果不夠炫酷,要實現炫酷的效果得先了解各個屬性的含義,那麼接下來花大量的篇幅講解CAEmitterLayer
與CAEmitterCell
的屬性。spa
CAEmitterLayer
經常使用屬性@property(nullable, copy) NSArray<CAEmitterCell *> *emitterCells; // 用來裝粒子的數組 @property float birthRate; // 粒子產生係數,默認1.0 @property float lifetime; // 粒子的生命週期係數, 默認1.0 @property CGPoint emitterPosition; // 決定了粒子發射形狀的中心點 @property CGFloat emitterZPosition; @property CGSize emitterSize; // 發射源的尺寸大小 @property CGFloat emitterDepth; @property(copy) NSString *emitterShape; // 發射源的形狀 @property(copy) NSString *emitterMode; // 發射模式 @property(copy) NSString *renderMode; // 渲染模式 @property BOOL preservesDepth; @property float velocity; // 粒子速度係數, 默認1.0 @property float scale; // 粒子的縮放比例係數, 默認1.0 @property float spin; // 粒子的自旋轉速度係數, 默認1.0 @property unsigned int seed; // 隨機數發生器
CAEmitterLayer
裏面的API裏面的全部屬性都已經貼出來並做了說明,看看註釋並調試一下就能理解大部分,接下來重點說說一些經常使用的屬性調試
CAEmitterLayer
控制粒子發射位置和形狀的屬性CAEmitterLayer發射的粒子並非雜亂無章的,咱們能夠設置它發射粒子時的位置、幾何圖形等。經過如下屬性去配置:
emitterPosition
:決定發射源的中心點,若是好比上面的demo中設置的爲CGPointMake(self.view.bounds.size.width * 0.5, -10)
,那麼x軸方向上就是在view的中心點,而後再設置emitterSize = CGSizeMake(40, 0);
的話,那麼就是self.view.bounds.size.width * 0.5
的左右兩邊各20。API中還提供了一個emitterZPosition
,這個是用在三維座標中的,筆者暫時對三維的沒有研究。emitterSize
: 決定發射源的大小emitterShape
:表示粒子從什麼形狀發射出來,它並非表示粒子本身的形狀。是一個枚舉類型,提供以下類型可供選擇:kCAEmitterLayerPoint kCAEmitterLayerLine kCAEmitterLayerRectangle kCAEmitterLayerCuboid kCAEmitterLayerCircle kCAEmitterLayerSphere
kCAEmitterLayerPoint
:點形狀,發射源的形狀就是一個點,位置在上面position設置的位置kCAEmitterLayerLine
:線形狀,發射源的形狀是一條線,位置在rect的橫向的位於垂直方向中間那條kCAEmitterLayerRectangle
:矩形狀,發射源的形狀是一個矩形,就是上面生成的那個矩形rectkCAEmitterLayerCuboid
:立體矩形形狀(3D),發射源是一個立體矩形,這裏要生效的話須要設置z方向的數據,若是不設置就同矩形狀kCAEmitterLayerCircle
:圓形形狀,發射源是一個圓形,形狀爲矩形包裹的那個圓,二維的kCAEmitterLayerSphere
:立體圓形(3D),三維的圓形,一樣須要設置z方向數據,不設置則通二維同樣這些形狀能夠在調試的時候修改來看看有什麼不一樣,好比咱們設置的紅包效果,就是用的kCAEmitterLayerLine
,結合了emitterSize
來是實現從頂部掉下來的效果,而emitterSize
的height
實際上是被忽略的。接下來咱們看以下代碼的效果圖解:
redpacketLayer.emitterPosition = CGPointMake(100, 100); redpacketLayer.emitterSize = CGSizeMake(20, 0); redpacketLayer.emitterShape = kCAEmitterLayerLine;
emitterShape
的幾種模式其實很好理解,以emitterPosition
的點爲中心,而後做一個對應的形狀,如直線、圓形、矩形,在這個形狀上產生相應的粒子。
emitterMode
:發射模式,這個字段規定了在特定形狀上發射的具體形式是什麼。它的做用其實就是進一步決定發射的區域是在發射形狀的哪一部份。kCAEmitterLayerPoints kCAEmitterLayerOutline kCAEmitterLayerSurface kCAEmitterLayerVolume
kCAEmitterLayerPoints
:點模式,發射器是以點的形式發射粒子。發射點就是形狀的某個特殊的點,好比shap是一個點的話,那麼這個點就是中心點,若是是圓形,那麼就是圓心。kCAEmitterLayerOutline
:輪廓模式,從形狀的邊界上發射粒子。kCAEmitterLayerSurface
:表面模式,從形狀的表面上發射粒子。kCAEmitterLayerVolume
:是相對於3D形狀的「球體內」或「立方體內」發射,筆者暫時也不是很瞭解3D的。CAEmitterLayer
決定粒子係數的屬性birthRate
: 粒子產生係數,默認1.0;每一個粒子cell
的產生率乘以這個粒子產生係數,得出每一秒產生這個粒子的個數。 即:每秒粒子產生個數 = layer.birthRate * cell.birthRate ;lifetime
:粒子的生命週期係數,默認1.0。計算方式同上;velocity
:粒子速度係數, 默認1.0。計算方式同上;scale
:粒子的縮放比例係數, 默認1.0。計算方式同上;spin
:自旋轉速度係數, 默認1.0。計算方式同上;emitterCells
:用來裝粒子的數組。每種粒子就是一個CAEmitterCell
。在API中能夠看到CAEmitterCell
是服從CAMediatiming
協議的,能夠經過beginTime
來控制subCell的出現時機。CAEmitterCell
經常使用屬性@property(nullable, copy) NSString *name; // 粒子名字, 默認爲nil @property(getter=isEnabled) BOOL enabled; @property float birthRate; // 粒子的產生率,默認0 @property float lifetime; // 粒子的生命週期,以秒爲單位。默認0 @property float lifetimeRange; // 粒子的生命週期的範圍,以秒爲單位。默認0 @property CGFloat emissionLatitude;// 指定緯度,緯度角表明了在x-z軸平面座標系中與x軸之間的夾角,默認0: @property CGFloat emissionLongitude; // 指定經度,經度角表明了在x-y軸平面座標系中與x軸之間的夾角,默認0: @property CGFloat emissionRange; //發射角度範圍,默認0,以錐形分佈開的發射角度。角度用弧度制。粒子均勻分佈在這個錐形範圍內; @property CGFloat velocity; // 速度和速度範圍,二者默認0 @property CGFloat velocityRange; @property CGFloat xAcceleration; // x,y,z方向上的加速度份量,三者默認都是0 @property CGFloat yAcceleration; @property CGFloat zAcceleration; @property CGFloat scale; // 縮放比例, 默認是1 @property CGFloat scaleRange; // 縮放比例範圍,默認是0 @property CGFloat scaleSpeed; // 在生命週期內的縮放速度,默認是0 @property CGFloat spin; // 粒子的平均旋轉速度,默認是0 @property CGFloat spinRange; // 自旋轉角度範圍,弧度制,默認是0 @property(nullable) CGColorRef color; // 粒子的顏色,默認白色 @property float redRange; // 粒子顏色red,green,blue,alpha能改變的範圍,默認0 @property float greenRange; @property float blueRange; @property float alphaRange; @property float redSpeed; // 粒子顏色red,green,blue,alpha在生命週期內的改變速度,默認都是0 @property float greenSpeed; @property float blueSpeed; @property float alphaSpeed; @property(nullable, strong) id contents; // 粒子的內容,爲CGImageRef的對象 @property CGRect contentsRect; @property CGFloat contentsScale; @property(copy) NSString *minificationFilter; @property(copy) NSString *magnificationFilter; @property float minificationFilterBias; @property(nullable, copy) NSArray<CAEmitterCell *> *emitterCells; // 粒子裏面的粒子 @property(nullable, copy) NSDictionary *style;
CAEmitterCell
裏面的API裏面的大部分屬性做了說明,看看註釋並調試一下就能理解大部分,接下來重點說說一些經常使用的屬性。CAEmitterLayer
就是粒子的工廠,可是要實現效果就須要CAEmitterCell
的幫助。
CAEmitterCell
決定生命狀態的屬性lifetime
、lifetimeRange
:粒子在系統上的生命週期,即存活時間,單位是秒。配合lifetimeRage
來讓粒子生命週期均勻變化,以即可以讓粒子的出現和消失顯得更加離散。birthRate
:每秒鐘產生的粒子的數量,是浮點數。對於這個數量爲浮點數,在測試的時候能夠靈活使用它。好比你想看粒子的運動狀態,可是太多了可能會很迷糊,這時候你把birthRate = 0.1f
,其餘參數不變,就能看到單個粒子的運動狀態。 CAEmitterCell
決定內容的屬性contents
:爲CGImageRef
的對象。關於contents
會聯想到CALayer
了,在CALayer
中展現靜態的圖片是須要用到這個屬性。提供一張圖片,做爲粒子系統的粒子。可是由於粒子系統能夠給粒子上色,爲了作出好的顏色變換效果,一般提供的圖片爲純色的圖片,通常爲白色。name
:粒子的名字。初看沒什麼用,可是當CAEmitterLayer
裏面有不少個cell的時候,給每一個cell設置好名字,要修改一些屬性以達到動畫效果的改變等,就能夠經過KVC拿到這個cell的某個屬性。在後面的幾個demo中都用用到。CAEmitterCell
決定顏色狀態的屬性
粒子系統之因此能作出炫酷的效果,和它的色彩多樣化有必不可上的關係,在
CAEmitterCell
中提供了較多的顏色控制屬性這部分屬性讓你得到了控制粒子顏色,顏色變化範圍和速度的能力,你能夠憑藉它來完成一些漸變的效果或其它構建在它之上的酷炫效果。接下來就看看這些顏色屬性。
color
:color
是粒子的顏色屬性,這個顏色屬性的做用是給粒子上色,它的實現方法很簡單,就是將contents
自身的顏色的RGBA值 * color
的RGBA值,就獲得最終的粒子的顏色。爲了很好的計算,一般用白色的圖片做爲contents
,由於它的RGB都是255,轉換爲UIColor
中的component
就是1,用color乘上它就能獲得color
設置的顏色效果。redRange
、greenRange
、blueRange
、alphaRange
:這些是對應的color的RGBA的取值範圍,取值範圍爲0~1,好比以下設置中snowCell.color = [[UIColor colorWithRed:0.1 green:0.2 blue:0.3 alpha:0.5]CGColor]; snowCell.redRange = 0.1; snowCell.greenRange = 0.1; snowCell.blueRange = 0.1; snowCell.alphaRange = 0.1;
對應的RGBA的取值範圍就是:R(0~0.2)、G(0.1~0.3)、B(0.2~0.4)、A(0.4~0.6)。
redSpeed
、greenSpeed
、blueSpeed
、alphaSpeed
:這些是對應的是粒子的RGBA的變化速度,取值範圍爲0~1。表示每秒鐘的RGBA的變化率。這個變化率的計算方式其實很簡單,先看下面的幾行代碼:snowCell.lifetime = 20.f; // 粒子的生命週期 snowCell.color = [[UIColor colorWithRed:0.f green:1.f blue:1.f alpha:1.f]CGColor]; snowCell.redSpeed = 0.2;
這裏設置了粒子顏色的RGBA,以及redSpeed
,其餘的沒設置默認爲0。粒子的生命週期(lifetime
)爲20秒,那麼這個粒子從產生到消失的時間就是20秒。它的Red值爲0,redSpeed
爲0.2,那麼在粒子的這個生命週期內,粒子的每秒鐘的Rde值就會增長 0.2 * 255
,表如今外觀上的狀態就是粒子顏色在不斷變化,接近白色。最後粒子生命週期結束的時候,粒子的color
正好是RGBA(1,1,1,1)。固然個變化的速度也能夠負數,計算方式相同。好比要設置煙花的效果,那麼要讓在爆炸的過程當中顏色變化,就是經過這樣的設置達到的效果。
CAEmitterCell
決定飛行軌跡的屬性。
CAEmitterLayer
雖然控制了粒子的發射位置和形狀等,可是粒子的飛行同時也須要自身去決定,好比粒子發射的角度、發散的範圍,自轉屬性等。那麼接下來就說說這些屬性。
emissionLongitude
: 指定經度,經度角表明了在x-y軸平面座標系中與x軸之間的夾角,默認0,弧度制。順時針方向爲正。這樣解釋看起來很差懂,畫個圖就明白了。
粒子沿着X軸向右飛行,若是emissionLongtitude = 0
那麼粒子會沿着X軸向右飛行,若是想沿着Y軸向下飛行,那麼能夠設置emissionLongtitude = M_PI_2
。
emissionLatitude
:這個和emissionLongitude
的原理是同樣的,只不過是在三維平面上的x-z軸上與x軸的夾角。emissionRange
:發射角度範圍,默認0,以錐形分佈開的發射角度。角度用弧度制。粒子均勻分佈在這個錐形範圍內。在二維平面中,若想要以錐形的形式發射粒子,然粒子的發散範圍不是一條線,而是一個錐形區域(也能夠叫作扇形),那麼能夠經過emissionRange
來設置一個範圍。好比想沿Y軸向下成90度的錐形區域發散,那麼能夠經過以下代碼設置:snowCell.emissionLongitude = M_PI_2; snowCell.emissionRange = M_PI_4;
實現的效果以下:
能夠看到粒子是沿着Y軸向下成90度的一個發散角度。若是想實現火焰等效果。就能夠這樣,把角度調小一點便可。
velocity
、velocityRange
、xAcceleration
、yAcceleration
、 zAcceleration
:前面兩個是粒子的初速度和初速度的範圍,後面是三個分別是在x、y、z軸方向的加速度,這個很好理解,初中就應該知道加速度的概念,也就是每秒鐘速度的變化量。在放煙花的效果中,煙花飛上天的過程當中,模擬一個收重力影響的效果,就能夠經過yAcceleration
模擬一個重力加速度的效果。spin
,spinRange
:這兩個屬性很重要,是粒子的自轉屬性。在粒子被髮射出去以後,想要實現自轉,就須要用到他們。粒子的自轉是以弧度制來計算的,表示每秒鐘粒子自轉的弧度數。spin爲正數表明粒子是順時針旋轉的,爲負數的話就是逆時針選轉了。舉個?:粒子的生命週期就是20秒,那麼你想讓你的粒子這個生命週期內恰好自轉12周,若spinRange
爲0,那麼粒子的spin
值就應該爲((PI/180)360 2)/20,就獲得了每秒須要轉動的弧度數。CAEmitterCell
子粒子的屬性emitterCells
:看到CAEmitterCell
的這個屬性的時候或許會有些疑惑,不用驚訝,前面說過CAEmitterLayer
能夠產生cell,通用cell也能夠產生cell。那麼這個屬性就和CAEmitterLayer
中的emitterCells
同樣,也是一個數組。這裏有幾個須要注意的地方:
emissionLongtitude
和emissionLatitude
這兩個屬性的狀況。kCAEmitterLayerPoint
形狀上由父粒子的中心發射出來的。理解了CAEmitterLayer
與CAEmitterCell
的屬性以後,經過粒子系統實現一些炫酷的動畫效果就很簡單了。接下來對實現的幾個小demo效果做個思路分享,歡迎提供更好的方法~。
CAKeyframeAnimation
實現setHighlighted
方法去掉高亮狀態。提供兩張圖片,用於默認狀態和選中狀態。CAEmitterLayer
和粒子CAEmitterCell
。因爲CAEmitterLayer
的birthRate
默認爲1,CAEmitterCell
的birthRate
默認爲0,那麼先不設置這兩個屬性。給cell設置好name
,而後自定義一個動畫開始的方法,在這裏面經過KVC設置CAEmitterCell
的birthRate
以實現動畫/** * 開始動畫 */ - (void)startAnimation{ // 用KVC設置顆粒個數 [self.explosionLayer setValue:@1000 forKeyPath:@"emitterCells.explosionCell.birthRate"]; // 開始動畫 self.explosionLayer.beginTime = CACurrentMediaTime(); // 延遲中止動畫 [self performSelector:@selector(stopAnimation) withObject:nil afterDelay:0.15]; } /** * 動畫結束 */ - (void)stopAnimation{ // 用KVC設置顆粒個數 [self.explosionLayer setValue:@0 forKeyPath:@"emitterCells.explosionCell.birthRate"]; [self.explosionLayer removeAllAnimations]; }
setSelected
方法,在裏面經過關鍵幀動畫實現縮放。/** * 選中狀態 實現縮放 */ - (void)setSelected:(BOOL)selected{ [super setSelected:selected]; // 經過關鍵幀動畫實現縮放 CAKeyframeAnimation * animation = [CAKeyframeAnimation animation]; animation.keyPath = @"transform.scale"; if (selected) { // 從沒有點擊到點擊狀態 會有爆炸的動畫效果 animation.values = @[@1.5,@2.0, @0.8, @1.0]; animation.duration = 0.5; animation.calculationMode = kCAAnimationCubic; [self.layer addAnimation:animation forKey:nil]; // 讓放大動畫先執行完畢 再執行爆炸動畫 [self performSelector:@selector(startAnimation) withObject:nil afterDelay:0.25]; }else{ // 從點擊狀態normal狀態 無動畫效果 若是點贊以後立刻取消 那麼也立馬中止動畫 [self stopAnimation]; } }
還有幾個動畫的思路就不囉嗦了,實現起來都很簡單,關鍵是要去理解這些屬性的做用。另外動畫作起來即便費時間,要本身去理解屬性的做用的話須要花時間調試對比,其實用其餘方式也能夠實現這樣的效果,可是CAEmitterLayer
基於GPU,作這些效果的時候比較方便。demo已經上傳github了,提供了下雨、下雪、紅包雨、五彩小球、愛心、火焰、煙花等效果,值得一說的是,煙花須要三個cell,一個提供發射的shootCell
,一個用於爆炸的explodeCell
,還有一個用於火花的sparkCell
,這些都是父子關係,一個cell生命週期完了另一個再出來。這些能夠查看代碼體會,都有註釋~。EmitterAnimation