前段时间帮某某做了一个动画效果,今天分享给大家。关于动画的基础知识,这里不会细说,如果您还没有核心动画的基础知识,请先阅读相关文章,了解核心动画如何使用,然后再继续阅读本篇文章。
本篇文章,涉及到以下知识点:
温馨提示:新手不适合阅读本篇文章哦,不过可以初步阅读了解一下!
如果没有了解过动画的基础知识,可先看看笔者之前的一些文章:
本篇文章不深入讲基础知识,只讲如何实现及实现的要点,并放出关键代码。对于伸手党,请不要私聊我要完整的源代码。如果您正好在项目中有这样的需求,可以尝试根据本篇文章讲解动手做一个!
从动画效果可以看出来,有平移、旋转、关键帧动画,同时还有渐变进度条充满的动画。另外还要注意移动的距离。请忽略样式丑陋的问题~
从动画效果可以看出来,个人变成了6个,且是平分的,比上面的效果图多了缩放的动画!这个缩放动画,生成GIF图的效果真丑,跟手机运行起来看到的差别比较大!
这里是封装里了通用的组件,如果是在项目中使用,可以轻松调用且可以复用。从动画效果可以看到,这个整体是由以下几个部分组成的:
所以,我们设计成三个类,分别是:
假设叫叶子。那么每个叶子就有相同的特性,需要知道自己的归属:
可设置以下几个位置属性:
@property (nonatomic, assign) CGPoint startPoint; @property (nonatomic, assign) CGPoint endPoint; @property (nonatomic, assign) CGPoint nearPoint; @property (nonatomic, assign) CGPoint farPoint;
这个类只需要有相关属性即可!
假设中大圆。大圆主要需要属性以下特性:
可执行的操作:
对于这个大圆类,细读如何添加圆形进度条。
底部是一个白色的圆环,当白色圆环填满时,表示0%。那么白色圆环可以用什么来实现呢?其实CAShapeLayer就是非常好的,它可以添加圆形路径来实现的,记得设置填充色为透明哦,不然连中间的内容也看不见了。代码如下:
self.outLayer = [CAShapeLayer layer]; CGRect rect = {kLineWidth / 2, kLineWidth / 2, frame.size.width - kLineWidth, frame.size.height - kLineWidth}; UIBezierPath *path = [UIBezierPathbezierPathWithOvalInRect:rect]; self.outLayer.strokeColor = [UIColor whiteColor].CGColor; self.outLayer.lineWidth = kLineWidth; self.outLayer.fillColor = [UIColor clearColor].CGColor; self.outLayer.lineCap = kCALineCapRound; self.outLayer.path = path.CGPath; [self.layeraddSublayer:self.outLayer];
因为要设置为白色圆环,所以画笔颜色设置为白色,线宽就设置为圆环的大小。这样就可以初步形成了带有白色圆环的底色了。此时就是0%。
下面所创建的图层,会用于设置渐变颜色图层的mask,这样才能显示中间的内容,而不是渐变的图层。它的大小与白底圆环一样大小:
self.progressLayer = [CAShapeLayer layer]; self.progressLayer.frame = self.bounds; self.progressLayer.fillColor = [UIColor clearColor].CGColor; self.progressLayer.strokeColor = [UIColor whiteColor].CGColor; self.progressLayer.lineWidth = kLineWidth; self.progressLayer.lineCap = kCALineCapRound; self.progressLayer.path = path.CGPath;
要实现渐变图层,可以通过CAGradientLayer来创建,这里的颜色是随意指定的,所以效果不太协调,大家可自由调整。这里呢使用了两个渐变图层,然后放到一个大的渐变图层中,两个小的图层各占一半。 当添加了mask后,就只有进度这一部分渐变可显示了。
CAGradientLayer *gradientLayer1 = [CAGradientLayer layer]; gradientLayer1.frame = CGRectMake(0, 0, width / 2, height); CGColorRef red = [UIColor redColor].CGColor; CGColorRef purple = [UIColor purpleColor].CGColor; CGColorRef yellow = [UIColor yellowColor].CGColor; CGColorRef orange = [UIColor orangeColor].CGColor; [gradientLayer1setColors:@[(__bridgeid)red, (__bridgeid)purple, (__bridgeid)yellow, (__bridgeid)orange]]; [gradientLayer1setLocations:@[@0.3, @0.6, @0.8, @1.0]]; [gradientLayer1setStartPoint:CGPointMake(0.5, 1)]; [gradientLayer1setEndPoint:CGPointMake(0.5, 0)]; CAGradientLayer *gradientLayer2 = [CAGradientLayer layer]; gradientLayer2.frame = CGRectMake(width / 2, 0, width / 2, height); CGColorRef blue = [UIColor brownColor].CGColor; CGColorRef purple1 = [UIColor purpleColor].CGColor; CGColorRef r1 = [UIColor yellowColor].CGColor; CGColorRef o1 = [UIColor orangeColor].CGColor; [gradientLayer2setColors:@[(__bridgeid)blue, (__bridgeid)purple1, (__bridgeid)r1, (__bridgeid)o1]]; [gradientLayer2setLocations:@[@0.3, @0.6, @0.8, @1.0]]; [gradientLayer2setStartPoint:CGPointMake(0.5, 0)]; [gradientLayer2setEndPoint:CGPointMake(0.5, 1)]; CAGradientLayer *gradientLayer = [CAGradientLayer layer]; gradientLayer.frame = self.bounds; [gradientLayeraddSublayer:gradientLayer1]; [gradientLayer1addSublayer:gradientLayer2]; gradientLayer.mask = self.progressLayer; [self.layeraddSublayer:gradientLayer];
注意,一定要设置gradientLayer.mask = self.progressLayer;这样才能显示中间的内容,如果不设置mask,那么就只有渐变图层了。
在需要更新进度的时候,可以调用这个API来更新进度,带有动画效果。
- (void)updateProgressWithNumber:(NSUInteger)number { [CATransaction begin]; [CATransactionsetAnimationTimingFunction:[CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseIn]]; [CATransactionsetAnimationDuration:0.5]; self.progressLayer.strokeEnd = number / 100.0; self.percentLabel.text = [NSStringstringWithFormat:@"%@%%", @(number)]; [CATransaction commit]; }
这个类就是圆形菜单类了,整合前两个。它主要具备以下特性:
可执行的操作:
当更换所有的叶子时,需要调整所有叶子的位置:
- (void)setMenuItems:(NSArray *)menuItems { if (_menuItems != menuItems) { _menuItems = menuItems; for (UIView *v in self.subviews) { if (v.tag >= 1000) { [v removeFromSuperview]; } } // add the menu buttons int count = (int)[menuItemscount]; CGFloat cnt = 1; for (int i = 0; i < count; i ++) { HYBCurveItemView *item = [menuItemsobjectAtIndex:i]; item.tag = 1000 + i; item.startPoint = self.startPoint; CGFloat pi = M_PI / count; CGFloat endRadius = item.bounds.size.width / 2 + self.endDistance + _mainView.bounds.size.width / 2; CGFloat nearRadius = item.bounds.size.width / 2 + self.nearDistance + _mainView.bounds.size.width / 2; CGFloat farRadius = item.bounds.size.width / 2 + self.farDistance + _mainView.bounds.size.width / 2; item.endPoint = CGPointMake(self.startPoint.x + endRadius* sinf(pi* cnt), self.startPoint.y - endRadius* cosf(pi* cnt)); item.nearPoint = CGPointMake(self.startPoint.x + nearRadius* sinf(pi* cnt), self.startPoint.y - nearRadius* cosf(pi* cnt)); item.farPoint = CGPointMake(self.startPoint.x + farRadius* sinf(pi* cnt), self.startPoint.y - farRadius* cosf(pi* cnt)); item.center = item.startPoint; [selfaddSubview:item]; cnt += 2; } [selfbringSubviewToFront:_mainView]; } }
其中,这几个属性带有默认值(分别表示起点、最近点、最远点、展开后最终停留点):
self.startPoint = self.center; // 修改这时的参数来调整大圆与圆之间的距离 self.nearDistance = 30; self.farDistance = 60; self.endDistance = 30;
对于上面的点的计算,主要是一点点的数学知识,需要懂得象限与角度的关系。
调用下面的方法来展开或者收起。这里会遍历所有的叶子,让每个叶子都添加对应的动画变换,就可以看到动画轨迹了:
- (void)expend:(BOOL)isExpend { _isExpend = isExpend; [self.menuItemsenumerateObjectsUsingBlock:^(HYBCurveItemView *obj, NSUInteger idx, BOOL * _Nonnullstop) { if (self.scale) { if (isExpend) { obj.transform = CGAffineTransformIdentity; } else { obj.transform = CGAffineTransformMakeScale(0.001, 0.001); } } [selfaddRotateAndPostisionForItem:objtoShow:isExpend]; }]; }
接下来是最关键的动画核心代码:
- (void)addRotateAndPostisionForItem:(HYBCurveItemView *)itemtoShow:(BOOL)show { if (show) { CABasicAnimation *scaleAnimation = nil; if (self.scale) { scaleAnimation = [CABasicAnimationanimationWithKeyPath:@"transform.scale"]; scaleAnimation.fromValue = [NSNumbernumberWithFloat:0.2]; scaleAnimation.toValue = [NSNumbernumberWithFloat:1.0]; scaleAnimation.duration = 0.5f; scaleAnimation.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseInEaseOut]; } CAKeyframeAnimation *rotateAnimation = [CAKeyframeAnimationanimationWithKeyPath:@"transform.rotation.z"]; rotateAnimation.values = @[@(M_PI), @(0.0)]; rotateAnimation.duration = 0.5f; rotateAnimation.keyTimes = @[@(0.3), @(0.4)]; CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimationanimationWithKeyPath:@"position"]; positionAnimation.duration = 0.5f; CGMutablePathRef path = CGPathCreateMutable(); CGPathMoveToPoint(path, NULL, item.startPoint.x, item.startPoint.y); CGPathAddLineToPoint(path, NULL, item.farPoint.x, item.farPoint.y); CGPathAddLineToPoint(path, NULL, item.nearPoint.x, item.nearPoint.y); CGPathAddLineToPoint(path, NULL, item.endPoint.x, item.endPoint.y); positionAnimation.path = path; CGPathRelease(path); CAAnimationGroup *animationgroup = [CAAnimationGroup animation]; if (self.scale) { animationgroup.animations = @[scaleAnimation, positionAnimation, rotateAnimation]; } else { animationgroup.animations = @[positionAnimation, rotateAnimation]; } animationgroup.duration = 0.5f; animationgroup.fillMode = kCAFillModeForwards; animationgroup.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseIn]; [item.layeraddAnimation:animationgroupforKey:@"Expand"]; item.center = item.endPoint; } else { CABasicAnimation *scaleAnimation = nil; if (self.scale) { scaleAnimation = [CABasicAnimationanimationWithKeyPath:@"transform.scale"]; scaleAnimation.fromValue = [NSNumbernumberWithFloat:1.0]; scaleAnimation.toValue = [NSNumbernumberWithFloat:0.2]; scaleAnimation.duration = 0.5f; scaleAnimation.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseInEaseOut]; } CAKeyframeAnimation *rotateAnimation = [CAKeyframeAnimationanimationWithKeyPath:@"transform.rotation.z"]; rotateAnimation.values = @[@0, @(M_PI * 2), @(0)]; rotateAnimation.duration = 0.5f; rotateAnimation.keyTimes = @[@0, @(0.4), @(0.5)]; CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimationanimationWithKeyPath:@"position"]; positionAnimation.duration = 0.5f; CGMutablePathRef path = CGPathCreateMutable(); CGPathMoveToPoint(path, NULL, item.endPoint.x, item.endPoint.y); CGPathAddLineToPoint(path, NULL, item.farPoint.x, item.farPoint.y); CGPathAddLineToPoint(path, NULL, item.startPoint.x, item.startPoint.y); positionAnimation.path = path; CGPathRelease(path); CAAnimationGroup *animationgroup = [CAAnimationGroup animation]; if (self.scale) { animationgroup.animations = @[scaleAnimation, positionAnimation, rotateAnimation]; } else { animationgroup.animations = @[positionAnimation, rotateAnimation]; } animationgroup.duration = 0.5f; animationgroup.fillMode = kCAFillModeForwards; animationgroup.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseIn]; [item.layeraddAnimation:animationgroupforKey:@"Close"]; item.center = item.startPoint; } }
展开时缩放动画可以通过修改transform.scale来实现,这是x、y方向都缩放了。也就是在刚出来时,缩放不断变大到最终大小。
scaleAnimation = [CABasicAnimationanimationWithKeyPath:@"transform.scale"]; scaleAnimation.fromValue = [NSNumbernumberWithFloat:0.2]; scaleAnimation.toValue = [NSNumbernumberWithFloat:1.0];
我们旋转的是z轴,而不是x、y轴,通过transform.rotation.z来实现。这里使用的是关键帧来实现,其中设置values及keyTimes的个数是对应的:
CAKeyframeAnimation *rotateAnimation = [CAKeyframeAnimationanimationWithKeyPath:@"transform.rotation.z"]; rotateAnimation.values = @[@(M_PI), @(0.0)]; rotateAnimation.duration = 0.5f; rotateAnimation.keyTimes = @[@(0.3), @(0.4)];
通过position可以实现平移动画,这里也是使用关键帧来实现,因为需要到path。通过添加路径,来实现起点、最近、最远、最终停留的路径:
CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimationanimationWithKeyPath:@"position"]; positionAnimation.duration = 0.5f; CGMutablePathRef path = CGPathCreateMutable(); CGPathMoveToPoint(path, NULL, item.startPoint.x, item.startPoint.y); CGPathAddLineToPoint(path, NULL, item.farPoint.x, item.farPoint.y); CGPathAddLineToPoint(path, NULL, item.nearPoint.x, item.nearPoint.y); CGPathAddLineToPoint(path, NULL, item.endPoint.x, item.endPoint.y); positionAnimation.path = path; CGPathRelease(path);
上面创建了好几种动画,那么要实现组合,就需要通过CAAnimationGroup来实现了。然后添加到叶子中的动画,就是组合动画:
CAAnimationGroup *animationgroup = [CAAnimationGroup animation]; if (self.scale) { animationgroup.animations = @[scaleAnimation, positionAnimation, rotateAnimation]; } else { animationgroup.animations = @[positionAnimation, rotateAnimation]; } animationgroup.duration = 0.5f; animationgroup.fillMode = kCAFillModeForwards; animationgroup.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseIn]; [item.layeraddAnimation:animationgroupforKey:@"Expand"]; item.center = item.endPoint;
关于收起的动画也差不多,就不说了!
本篇文章主要是想教大家如何去设计一个动画及UI控件,当然笔者所讲的并不一定是最好的,也许你就能想出更简单更优秀的办法来实现。
本篇文章的源代码不放出,只放出关键代码。对于学习能力比较强,比较爱研究的小伙伴已经足够了。如果非要源代码,可私聊我购买!
联系方式 | 关注 | 备注 |
---|---|---|
付费解答群 | 347363861(付费解答群) | 有需求或者私活可入群私聊 |
标哥博客iOS交流群 | 552095943(新)|259290340(新) | 群里很活跃,定期清理 |
标哥博客iOS交流群 | 324400294(满)|494669518(满)|494669518(满)|250351140(满) | 群里很活跃,定期清理 |
微信公众号 | iOSDevShares | 关注公众号阅读好文章 |
新浪微博 | @标哥的技术博客 | 关注微博动态 |
GITHUB | CoderJackyHuang | 文章Demo都在GITHUB |
联系标哥 | 关于标哥 | 保持活跃在最前线 |
版权声明:本文为【标哥的技术博客】原创出品,欢迎转载,转载时请注明出处!