直播APP常用动画效果

apqt8101 8年前
   <h3><strong>介绍</strong></h3>    <p>记录、总结开发遇到一些问题,大家一起交流学习。</p>    <p>这次带来,对直播APP的常用动画总结。</p>    <h3><strong>效果展示</strong></h3>    <p>下面是一个很多平台都有的入门豪华礼物动画——烟花。</p>    <p>一个复杂的礼物动画,首先是美术给出 <strong>gif实现草图</strong> 和 <strong>素材</strong> ,技术进行 <strong>动画剖析</strong> 和 <strong>图片压缩</strong> ,在程序中 <strong>加载图片</strong> 和 <strong>实现动画</strong> ,其中要注意 <strong>内存和CPU占用</strong> 。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/fe58a6f7563d199a9a5589569a13c6b0.gif"></p>    <h3><strong>图片压缩、加载与裁剪</strong></h3>    <p>1、图片压缩</p>    <p>美术给出的图片,即使是压缩过,仍存在较大的压缩空间,可以用 这里 或者更好的大小优化。</p>    <p>2、图片加载</p>    <p>主要有 -imageNamed: 和 -imageWithContentsOfFile: 两种方式。</p>    <p>AnimationImageCache 类是一个动画图片加载类,用单例实现且内部用NSCache持有引用。</p>    <p>注意,当收到内存不足警告时,NSCache会自动释放内存。所以每次访问NSCache,即使上一次已经加载过,也需要判断返回值是否为空。</p>    <p>3、图片裁剪</p>    <p>为了减少图片资源的大小,有时候会把多个帧动画做成连续的一张图。这时需要程序加载一整张资源图,并在相应的位置进行裁剪。</p>    <pre>  <code class="language-objectivec">    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);</code></pre>    <h3><strong>动画剖析与时间轴</strong></h3>    <p>下面这个是一个全屏类型的“天使”礼物动画,我们来剖析下这个动画的构成。</p>    <ul>     <li> <p>1、背景变暗,出现星空;</p> </li>     <li> <p>2、流星划过、月亮出现、云彩飘动;</p> </li>     <li> <p>3、两侧浮空岛震动,中间浮空岛出现;</p> </li>     <li> <p>4、背光出现,天使落下,翅膀扇动;</p> </li>     <li> <p>5、星星闪烁、凤凰出现;</p> </li>     <li> <p>6、渐隐消失;</p> </li>    </ul>    <p>时间轴实现</p>    <p>为了让动画按照时间顺序一一执行,可以把动画按时间和对象分成多个方法,通过GCD在指定的时间调用。</p>    <pre>  <code class="language-objectivec">    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];          }];      });</code></pre>    <h3><strong>常用动画效果</strong></h3>    <p>1、视图变暗、变大</p>    <p>alpha值属性是透明度,把背景设置成淡黑色,然后调整alpha可以达到背景渐变的视图效果;</p>    <p>UIView的transform是可以用仿射变换矩阵来控制平移、放大缩小等。</p>    <pre>  <code class="language-objectivec">    [UIView animateWithDuration:1.5 animations:^{          self.mBackgroundView.alpha = 0.5;          self.mAngelView.transform = CGAffineTransformMakeScale(1.2, 1.2);      }];</code></pre>    <p>2、匀速运动、交错效果</p>    <p>right是项目封装的一个属性,本质是对UIView的frame进行操作;</p>    <p>两朵云, 左边的朝右,右边的朝左,即可达到交错的效果。</p>    <pre>  <code class="language-objectivec">    [UIView animateWithDuration:TOTAL_TIME delay:0 options:UIViewAnimationOptionCurveLinear animations:^{          self.mAngelCloudView0.right += 250;          self.mAngelCloudView1.right -= 190;      } completion:nil];</code></pre>    <p>3、上下往返运动</p>    <p>CAKeyframeAnimation是关键帧动画,对layer的postion的y坐标进行操作;</p>    <p>设定好起始位置、经过位置,最后回到起始位置,即可实现上下往返的效果。</p>    <pre>  <code class="language-objectivec">    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"];</code></pre>    <p>4、闪烁效果</p>    <p>闪烁的本质是alpha的变化,但是UIView的block动画不好实现重复效果;</p>    <p>UIView的alpha对应的是layer的opacity属性,设定好起始、过度和结束的状态,实现闪烁的效果。</p>    <pre>  <code class="language-objectivec">    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"];</code></pre>    <p>5、贝塞尔曲线运动</p>    <p>贝塞尔曲线 是优化动画体验的很重要部分,比如说天上掉下来的羽毛,地上冒起来的气泡,空中飘荡的气球,都可以用贝塞尔曲线来绘制,从而获得很好的视觉体验;</p>    <p>本质还是关键帧动画,这次操作的属性是position,通过path属性来确定路径;</p>    <p>给贝塞尔曲线设定好目标点后,把path赋值给关键帧动画,再把动画添加到layer上即可;</p>    <pre>  <code class="language-objectivec">    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"];</code></pre>    <p>6、遮罩动画</p>    <p>遮罩效果可以实现彩虹出现、烟花爆炸、画卷打开等效果,通过改变遮罩的大小,影响原始图片的展示,达到动画的效果;</p>    <p>先新建一个CAShapeLayer,并设置为layer的遮罩;</p>    <p>新建一个动画,设定初始和结束状态并赋值给CAShapeLayer,完成一个遮罩动画。</p>    <pre>  <code class="language-objectivec">    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"];</code></pre>    <p>7、旋转效果</p>    <p>灯光扫动,花朵旋转等旋转效果,都可以transform的rotation.z属性来实现;</p>    <p>同样使用CAKeyframeAnimation实现,设定好初始、中间、结束状态,动画时间已经重复次数,并添加到layer,完成旋转效果;</p>    <pre>  <code class="language-objectivec">    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"];</code></pre>    <p>8、帧动画</p>    <p>某些复杂动画不是靠对原始图像操作进行操作就能实现,这时候就要用到 <strong>帧动画</strong> ;</p>    <p>帧动画有两种实现方式,一种是通过Timer(定时器),设定好时间间隔,手动替换图片;</p>    <p>另外一种是通过UIImageView的支持,实现帧动画。</p>    <p>UIImageView的帧动画没有回调,如果需要实现达到第几帧之后,开始另外的动画的效果,需要用第一种方法。</p>    <pre>  <code class="language-objectivec">    NSMutableArray                        *images = [NSMutableArray                    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];                </code></pre>    <h3><strong>总结</strong></h3>    <p>UIView的block动画中,completion持有的是强引用,需要避免造成循环引用。</p>    <p>但在调用完毕completion后,会释放引用。</p>    <p>天使动画的图片大小为900KB,运行时占内存15MB,播放完毕后,如果收到内存不足的警告会释放内存;</p>    <p>烟花动画的图片大小为400KB,运行时占用的内存为20MB,播放完毕后,会马上释放内存;</p>    <p>思考题</p>    <p>1、为什么烟花动画的图片大小比较小,运行时占用的内存反而更多?</p>    <p>2、播放完毕马上释放和收到内存不足警告再释放,两种图片加载方式的优缺点?</p>    <p> </p>    <p>来自:http://www.cocoachina.com/ios/20161114/18073.html</p>    <p> </p>