UI定制 - 水波纹效果(中心向外扩散)
水波纹(中心向外扩散)
1 - 代码封装
// - RippleView.h
1 #import <UIKit/UIKit.h> 2 @interface RippleView : UIView 3 4 @property (nonatomic, assign) NSTimeInterval timeInterval;// 波浪轮推间隔 5 @property (nonatomic, assign) NSTimeInterval duration; // 动画完成时间 6 @property (nonatomic, assign) NSInteger waveCount;// 波浪圈数 7 @property (nonatomic, assign) CGFloat minRadius; // 半径 8 9 - (void)startAnimating;// 开启 10 - (void)stopAnimating; // 关闭 11 12 @end
// - RippleView.m
1 #import "RippleView.h" 2 @interface RippleView () 3 4 @property (nonatomic, assign) BOOL animating; 5 @property (nonatomic, strong) NSMutableArray<CAShapeLayer *> *shapeLayers; 6 7 @end 8 9 @implementation RippleView 10 11 // 默认状态 12 - (instancetype)init{ 13 if (self = [super init]) { 14 self.tintColor = [UIColor greenColor]; 15 self.timeInterval = .5; 16 self.duration = 4; 17 self.waveCount = 8; 18 self.minRadius = .2; 19 self.shapeLayers = [NSMutableArray array]; 20 } 21 return self; 22 } 23 24 - (void)layoutSubviews{ 25 [super layoutSubviews]; 26 [self _reloadShapeLayers]; 27 } 28 29 30 - (CAShapeLayer *)newShapeLayer{ 31 32 CGPoint center = CGPointMake(CGRectGetWidth([self bounds]) / 2., CGRectGetHeight([self bounds]) / 2); 33 UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, [self minRadius] * 2, [self minRadius] * 2) cornerRadius:[self minRadius]]; 34 35 CAShapeLayer *shapeLayer = [CAShapeLayer layer]; 36 shapeLayer.frame = CGRectMake(center.x - [self minRadius], center.y - [self minRadius], [self minRadius] * 2, [self minRadius] * 2); 37 shapeLayer.opacity = 0; 38 shapeLayer.lineWidth = 0.f; 39 shapeLayer.position = center; 40 shapeLayer.path = [path CGPath]; 41 shapeLayer.anchorPoint = CGPointMake(0.5, 0.5); 42 shapeLayer.fillColor = [[self tintColor] CGColor]; 43 shapeLayer.strokeColor = [[UIColor clearColor] CGColor]; 44 45 return shapeLayer; 46 } 47 48 - (CAAnimation *)newWaveAnimation{ 49 50 CGFloat maxRadius = MAX([self minRadius], MIN(CGRectGetWidth([self bounds]) / 2., CGRectGetHeight([self bounds]) / 2.)); 51 CGFloat scale = maxRadius / [self minRadius]; 52 53 CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"]; 54 scaleAnimation.removedOnCompletion = NO; 55 scaleAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; 56 scaleAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(scale, scale, 1)]; 57 58 CABasicAnimation *alphaAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; 59 alphaAnimation.removedOnCompletion= NO; 60 alphaAnimation.fromValue = @1; 61 alphaAnimation.toValue = @0; 62 63 CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; 64 animationGroup.removedOnCompletion = NO; 65 animationGroup.animations = @[scaleAnimation, alphaAnimation]; 66 animationGroup.duration = [self duration]; 67 animationGroup.repeatCount = NSIntegerMax; 68 animationGroup.removedOnCompletion = YES; 69 70 return animationGroup; 71 } 72 73 - (void)_reloadShapeLayers{ 74 75 for (CAShapeLayer *shapeLayer in [self shapeLayers]) { 76 [shapeLayer removeAllAnimations]; 77 [shapeLayer removeFromSuperlayer]; 78 } 79 80 [[self shapeLayers] removeAllObjects]; 81 82 for (NSInteger index = 0; index < [self waveCount]; index++) { 83 CAShapeLayer *shapeLayer = [self newShapeLayer]; 84 [[self layer] addSublayer:shapeLayer]; 85 [[self shapeLayers] addObject:shapeLayer]; 86 } 87 88 if ([self animating]) { 89 [self startAnimating]; 90 } else { 91 [self stopAnimating]; 92 } 93 } 94 95 - (void)_appendAnimationParameters:(NSArray *)parameters{ 96 CALayer *layer = [parameters firstObject]; 97 CAAnimation *animation = [parameters lastObject]; 98 99 if ([self animating]) { 100 [layer addAnimation:animation forKey:nil]; 101 } 102 } 103 104 // 开启动画 105 - (void)startAnimating{ 106 [self stopAnimating]; 107 108 self.animating = YES; 109 110 [[self shapeLayers] enumerateObjectsUsingBlock:^(CAShapeLayer * shapeLayer, NSUInteger index, BOOL *stop) { 111 CAAnimation *animation = [self newWaveAnimation]; 112 animation.removedOnCompletion = NO; 113 [self performSelector:@selector(_appendAnimationParameters:) withObject:@[shapeLayer, animation] afterDelay:index * [self timeInterval]]; 114 }]; 115 } 116 117 // 关闭动画 118 - (void)stopAnimating{ 119 self.animating = NO; 120 121 for (CAShapeLayer *shapeLayer in [self shapeLayers]) { 122 [shapeLayer removeAllAnimations]; 123 } 124 } 125 126 @end
2 - 如何使用
1 #import "ViewController.h" 2 #import "RippleView.h" 3 4 @implementation ViewController 5 6 - (void)viewDidLoad { 7 self.view.backgroundColor = [UIColor cyanColor]; 8 9 // 初始化 10 RippleView *test = [[RippleView alloc] init]; 11 test.frame = CGRectMake(100, 200, self.view.frame.size.width - 200, 120); 12 [self.view addSubview:test]; 13 [test startAnimating]; 14 15 // 自定制 16 test.tintColor = [UIColor redColor]; 17 test.duration = 4; // 动画时间 18 test.minRadius = .2;// 源点:初始波浪最小半径 19 test.timeInterval = 1;// 每个波浪间隔 1 秒后执行 20 test.waveCount = 4; // 4 个波浪 21 } 22 23 @end
运行效果
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)