介紹
記錄赏寇、總結(jié)開發(fā)遇到一些問題嫁赏,大家一起交流學(xué)習(xí)。
這次帶來脖隶,對直播APP的常用動畫總結(jié)扁耐。
直播Live
效果展示
下面是一個很多平臺都有的入門豪華禮物動畫——煙花。
一個復(fù)雜的禮物動畫产阱,首先是美術(shù)給出gif實現(xiàn)草圖和素材婉称,技術(shù)進(jìn)行動畫剖析和圖片壓縮,在程序中加載圖片和實現(xiàn)動畫构蹬,其中要注意內(nèi)存和CPU占用王暗。
圖片壓縮、加載與裁剪
1庄敛、圖片壓縮
美術(shù)給出的圖片俗壹,即使是壓縮過,仍存在較大的壓縮空間藻烤,可以用這里或者更好的大小優(yōu)化绷雏。
2、圖片加載
主要有-imageNamed:
和 -imageWithContentsOfFile:
兩種方式隐绵。
AnimationImageCache
類是一個動畫圖片加載類之众,用單例實現(xiàn)且內(nèi)部用NSCache持有引用。
注意依许,當(dāng)收到內(nèi)存不足警告時棺禾,NSCache會自動釋放內(nèi)存。所以每次訪問NSCache峭跳,即使上一次已經(jīng)加載過膘婶,也需要判斷返回值是否為空。
3蛀醉、圖片裁剪
為了減少圖片資源的大小悬襟,有時候會把多個幀動畫做成連續(xù)的一張圖。這時需要程序加載一整張資源圖拯刁,并在相應(yīng)的位置進(jìn)行裁剪脊岳。
UIImage* sourceImage = [UIImage imageNamed:@"image/animation/gift_boat"];
CGSize sourceSize = sourceImage.size;
CGImageRef cgimage = CGImageCreateWithImageInRect(sourceImage.CGImage,
CGRectMake(0, 0, const_position_boat_x, sourceSize.height));
gWaveFrameImage = [UIImage imageWithCGImage:cgimage];
CGImageRelease(cgimage);
cgimage = CGImageCreateWithImageInRect(sourceImage.CGImage,
CGRectMake(const_position_boat_x, 0, const_position_boat_width, sourceSize.height));
gBoatFrameImage = [UIImage imageWithCGImage:cgimage];
CGImageRelease(cgimage);
cgimage = CGImageCreateWithImageInRect(sourceImage.CGImage,
CGRectMake(const_position_boat_x + const_position_boat_width, 0, sourceSize.width - const_position_boat_x - const_position_boat_width, sourceSize.height));
gShadowFrameImage = [UIImage imageWithCGImage:cgimage];
CGImageRelease(cgimage);
動畫剖析與時間軸
下面這個是一個全屏類型的“天使”禮物動畫,我們來剖析下這個動畫的構(gòu)成垛玻。
- 1割捅、背景變暗,出現(xiàn)星空帚桩;
- 2亿驾、流星劃過、月亮出現(xiàn)账嚎、云彩飄動莫瞬;
- 3儡蔓、兩側(cè)浮空島震動,中間浮空島出現(xiàn)疼邀;
- 4喂江、背光出現(xiàn),天使落下檩小,翅膀扇動开呐;
- 5、星星閃爍规求、鳳凰出現(xiàn)筐付;
- 6、漸隱消失阻肿;
時間軸實現(xiàn)
為了讓動畫按照時間順序一一執(zhí)行瓦戚,可以把動畫按時間和對象分成多個方法,通過GCD在指定的時間調(diào)用丛塌。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self playMeteorAnimation];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self playLandAnimation];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self playLightAnimation];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(7.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self playStarAnimation];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(TOTAL_TIME * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
@weakify(self);
[UIView animateWithDuration:0.5 animations:^{
self.alpha = 0;
} completion:^(BOOL finished) {
@strongify(self);
[self removeFromSuperview];
[self callBackManager];
}];
});
常用動畫效果
1较解、視圖變暗、變大
alpha值屬性是透明度赴邻,把背景設(shè)置成淡黑色印衔,然后調(diào)整alpha可以達(dá)到背景漸變的視圖效果;
UIView的transform是可以用仿射變換矩陣來控制平移姥敛、放大縮小等奸焙。
[UIView animateWithDuration:1.5 animations:^{
self.mBackgroundView.alpha = 0.5;
self.mAngelView.transform = CGAffineTransformMakeScale(1.2, 1.2);
}];
2、勻速運動彤敛、交錯效果
right是項目封裝的一個屬性与帆,本質(zhì)是對UIView的frame進(jìn)行操作;
兩朵云墨榄, 左邊的朝右玄糟,右邊的朝左,即可達(dá)到交錯的效果袄秩。
[UIView animateWithDuration:TOTAL_TIME delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
self.mAngelCloudView0.right += 250;
self.mAngelCloudView1.right -= 190;
} completion:nil];
3阵翎、上下往返運動
CAKeyframeAnimation是關(guān)鍵幀動畫,對layer的postion的y坐標(biāo)進(jìn)行操作之剧;
設(shè)定好起始位置贮喧、經(jīng)過位置,最后回到起始位置猪狈,即可實現(xiàn)上下往返的效果。
CAKeyframeAnimation *upDownAnimation;
upDownAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position.y"];
upDownAnimation.values = @[@(self.mAngelLandView1.layer.position.y), @(self.mAngelLandView1.layer.position.y + 5), @(self.mAngelLandView1.layer.position.y)];
upDownAnimation.duration = 2;
upDownAnimation.fillMode = kCAFillModeBoth;
upDownAnimation.calculationMode = kCAAnimationCubic;
upDownAnimation.repeatCount = HUGE_VALF;
[self.mAngelLandView1.layer addAnimation:upDownAnimation forKey:@"upDownAnimation"];
4辩恼、閃爍效果
閃爍的本質(zhì)是alpha的變化雇庙,但是UIView的block動畫不好實現(xiàn)重復(fù)效果谓形;
UIView的alpha對應(yīng)的是layer的opacity屬性,設(shè)定好起始疆前、過度和結(jié)束的狀態(tài)寒跳,實現(xiàn)閃爍的效果。
CAKeyframeAnimation *opacityAnimation;
opacityAnimation = [CAKeyframeAnimation animationWithKeyPath:@"opacity"];
opacityAnimation.values = @[@(0), @(1), @(0)];
opacityAnimation.duration = 1.5;
opacityAnimation.fillMode = kCAFillModeBoth;
opacityAnimation.calculationMode = kCAAnimationCubic;
opacityAnimation.repeatCount = HUGE_VALF;
[self.mAngelStarView.layer addAnimation:opacityAnimation forKey:@"opacityAnimation"];
5竹椒、貝塞爾曲線運動
貝塞爾曲線是優(yōu)化動畫體驗的很重要部分童太,比如說天上掉下來的羽毛,地上冒起來的氣泡胸完,空中飄蕩的氣球书释,都可以用貝塞爾曲線來繪制,從而獲得很好的視覺體驗赊窥;
本質(zhì)還是關(guān)鍵幀動畫爆惧,這次操作的屬性是position,通過path屬性來確定路徑锨能;
給貝塞爾曲線設(shè)定好目標(biāo)點后扯再,把path賦值給關(guān)鍵幀動畫,再把動畫添加到layer上即可址遇;
UIImage *image = [[AnimationImageCache shareInstance] getImageWithName:@"gift_castle_hot_air_balloon3.png"];
UIImageView *hotAirBalloonView0 = [[UIImageView alloc] initWithFrame:CGRectMake(50, 100, image.size.width / 2, image.size.height / 2)];
[self addSubview:hotAirBalloonView0];
[hotAirBalloonView0 setImage:image];
// 飄動
CGPoint position = CGPointMake(self.width, hotAirBalloonView0.top);
CGFloat duration = 5;
CAKeyframeAnimation *positionAnimate = [CAKeyframeAnimation animationWithKeyPath:@"position"];
positionAnimate.repeatCount = 1;
positionAnimate.duration = duration;
positionAnimate.fillMode = kCAFillModeForwards;
positionAnimate.removedOnCompletion = NO;
UIBezierPath *sPath = [UIBezierPath bezierPath];
[sPath moveToPoint:position];
[sPath addCurveToPoint:CGPointMake(-image.size.width / 2, position.y) controlPoint1:CGPointMake(self.width / 3 * 2, position.y - 60) controlPoint2:CGPointMake(self.width / 3, position.y + 60)];
positionAnimate.path = sPath.CGPath;
[hotAirBalloonView0.layer addAnimation:positionAnimate forKey:@"positionAnimate"];
6熄阻、遮罩動畫
遮罩效果可以實現(xiàn)彩虹??出現(xiàn)、煙花爆炸倔约、畫卷打開等效果秃殉,通過改變遮罩的大小,影響原始圖片的展示跺株,達(dá)到動畫的效果复濒;
先新建一個CAShapeLayer,并設(shè)置為layer的遮罩乒省;
新建一個動畫巧颈,設(shè)定初始和結(jié)束狀態(tài)并賦值給CAShapeLayer,完成一個遮罩動畫袖扛。
UIBezierPath *maskStartPath = [UIBezierPath bezierPathWithRect:CGRectMake(CGRectGetWidth(rainbowView1.bounds), 0, CGRectGetWidth(rainbowView1.bounds), CGRectGetHeight(rainbowView1.bounds))];
UIBezierPath *maskFinalPath = [UIBezierPath bezierPathWithRect:rainbowView1.bounds];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
rainbowView1.layer.mask = maskLayer;
maskLayer.path = maskFinalPath.CGPath;
CABasicAnimation *maskLayerAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
maskLayerAnimation.fromValue = (__bridge id)maskStartPath.CGPath;
maskLayerAnimation.toValue = (__bridge id)maskFinalPath.CGPath;
maskLayerAnimation.removedOnCompletion = NO;
maskLayerAnimation.duration = 2.0;
maskLayerAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[maskLayer addAnimation:maskLayerAnimation forKey:@"maskLayerAnimation"];
7砸泛、旋轉(zhuǎn)效果
燈光掃動,花朵旋轉(zhuǎn)等旋轉(zhuǎn)效果蛆封,都可以transform的rotation.z屬性來實現(xiàn)唇礁;
同樣使用CAKeyframeAnimation實現(xiàn),設(shè)定好初始惨篱、中間盏筐、結(jié)束狀態(tài),動畫時間已經(jīng)重復(fù)次數(shù)砸讳,并添加到layer琢融,完成旋轉(zhuǎn)效果界牡;
CAKeyframeAnimation* rotationAnimation;
rotationAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"];
rotationAnimation.values = @[@(M_PI / 12), @(M_PI / 3), @(M_PI / 12)];
rotationAnimation.duration = 2.5;
rotationAnimation.fillMode = kCAFillModeBoth;
rotationAnimation.calculationMode = kCAAnimationCubic;
// rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = HUGE_VALF;
[self.mLightLeftView.layer addAnimation:rotationAnimation forKey:@"rotationAnimation"];
8、幀動畫
某些復(fù)雜動畫不是靠對原始圖像操作進(jìn)行操作就能實現(xiàn)漾抬,這時候就要用到幀動畫宿亡;
幀動畫有兩種實現(xiàn)方式,一種是通過Timer(定時器)纳令,設(shè)定好時間間隔挽荠,手動替換圖片;
另外一種是通過UIImageView的支持平绩,實現(xiàn)幀動畫圈匆。
UIImageView的幀動畫沒有回調(diào),如果需要實現(xiàn)達(dá)到第幾幀之后馒过,開始另外的動畫的效果臭脓,需要用第一種方法。
NSMutableArray<UIImage *> *images = [NSMutableArray<UIImage *> array];
for (int i = 0; i < 6; ++i) {
UIImage *img = [[AnimationImageCache shareInstance] getDriveImageWithName:[NSString stringWithFormat:@"gift_animation_angel_phoenix%d.png", i]];
if (img) {
[images addObject:img];
}
}
self.mAngelPhoenixView.image = [[AnimationImageCache shareInstance] getDriveImageWithName:@"gift_animation_angel_phoenix0.png"];
self.mAngelPhoenixView.animationImages = images;
self.mAngelPhoenixView.animationDuration = 0.8;
self.mAngelPhoenixView.animationRepeatCount = 1;
[self.mAngelPhoenixView startAnimating];
動畫的性能優(yōu)化
直播APP的性能優(yōu)化-禮物篇
iOS開發(fā)-視圖渲染與性能優(yōu)化
都有腹忽,不再贅述来累。
總結(jié)
UIView的block動畫中,completion持有的是強引用窘奏,需要避免造成循環(huán)引用嘹锁。
但在調(diào)用完畢completion后,會釋放引用着裹。
天使動畫的圖片大小為900KB领猾,運行時占內(nèi)存15MB,播放完畢后骇扇,如果收到內(nèi)存不足的警告會釋放內(nèi)存摔竿;
煙花動畫的圖片大小為400KB,運行時占用的內(nèi)存為20MB少孝,播放完畢后继低,會馬上釋放內(nèi)存;
思考題??
1稍走、為什么煙花動畫的圖片大小比較小袁翁,運行時占用的內(nèi)存反而更多?
2婿脸、播放完畢馬上釋放和收到內(nèi)存不足警告再釋放粱胜,兩種圖片加載方式的優(yōu)缺點?