iOS 那些年好看的UISwitch控件的效果
距离上次更新又过了好久(懒懒懒😪)
今天来写一篇关于UISwitch(开关控件)个人觉得不错的交互动画
先上图,这是在别的地方看到的效果,拿来实现以下~
UISwitch_示例.gif
本文直接讲如何实现这个效果,代码上没有做太多的优化,直接继承类UISwitch
所以可能会出现类似
sender.subviews[0].subviews.lastObject.frame
这种蛋疼的代码(见谅见谅~),所以你要先理解系统自己的UISwitch控件的层级关系,后文虽然会指出我用的是哪个View
,但自己先去Debug view hierarchy里面看看会更便于理解。(这里先上一张图便于简单的理解)
这个被我改成黑色背景的View
就是上文那段蛋疼的代码sender.subviews[0].subviews.lastObject
其实说白了就是UISwitch
的那个小圆点
接下来讲解如何实现gif的效果
1.获取小圆点的坐标(考虑到用户体验和交互友好性,动画扩散和收缩的起始位置应该用小圆点作为参考中心)
2.扩散动画
3.动画结束后修改真实背景色
.h
#import <UIKit/UIKit.h>
@interface AnimationSwitch : UISwitch
@end
为了达到点击反馈的效果(注意 这里用到的是UIControlEventValueChanged
)
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self addTarget:self action:@selector(animationSwitchValueChanged:) forControlEvents:UIControlEventValueChanged];
}
return self;
}
animationSwitchValueChanged:
- (void)animationSwitchValueChanged:(AnimationSwitch *)sender {
if (sender.on) {
[self circleOpen:[sender.subviews[0] convertRect:sender.subviews[0].subviews.lastObject.frame toView:self.superview]];
}
else {
[self circleClose:[sender.subviews[0] convertRect:sender.subviews[0].subviews.lastObject.frame toView:self.superview]];
}
}
这里说到convertRect:<#(CGRect)#> toView:<#(nullable UIView *)#>
函数,如果不明白就去搜一下,这个方法的作用就是拿到小圆点在父View的坐标,然后调用动画的方法。
分割线,划重点了!!!
这个动画放大的圆要放多大?(接下来是数学题。。。别问我为什么,我也是在模模糊糊的记忆力翻出来的 emmmmmm)
所以。。计算这个radius
CGFloat radius = sqrtf(powf((startFrame.origin.x+startFrame.size.width/2.f), 2)+powf((startFrame.origin.y+startFrame.size.height/2.f), 2));
动画部分代码 注释都写在代码里 不明白可以留言问我~
- (void)circleOpen:(CGRect)startFrame {
//@Protocol 在这个Demo中的作用是在改变背景颜色的时候,改变文字颜色,避免颜色相近导致看不清的问题
if ([self.switchDelegate respondsToSelector:@selector(willBeginAnimte)]) {
[self.switchDelegate willBeginAnimte];
}
//用于给动画CAShapeLayer作为载体的View
self.presentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.superview.bounds.size.width, self.superview.bounds.size.height)];
self.presentView.backgroundColor = self.onTintColor;
[self.superview insertSubview:self.presentView atIndex:0];
//通过传入的frame计算UIBezierPath的起始位置
UIBezierPath *maskStartBP = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(startFrame.origin.x+startFrame.size.width/2.f, startFrame.origin.y+startFrame.size.height/2.f, 0, 0)];
CGFloat radius = sqrtf(powf((startFrame.origin.x+startFrame.size.width/2.f), 2)+powf((startFrame.origin.y+startFrame.size.height/2.f), 2));
//通过radius计算UIBezierPath的结束位置 关于CGRectInset的作用不明白的可以自行搜索简书
UIBezierPath *maskFinalBP = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(startFrame, -radius, -radius)];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.path = maskFinalBP.CGPath;
maskLayer.backgroundColor = (__bridge CGColorRef)([UIColor whiteColor]);
self.presentView.layer.mask = maskLayer;
//动画
CABasicAnimation *maskLayerAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
maskLayerAnimation.fromValue = (__bridge id)(maskStartBP.CGPath);
maskLayerAnimation.toValue = (__bridge id)((maskFinalBP.CGPath));
maskLayerAnimation.duration = 0.3f;
maskLayerAnimation.delegate = self;
maskLayerAnimation.repeatCount = 1;
maskLayerAnimation.removedOnCompletion = NO;
maskLayerAnimation.fillMode = kCAFillModeForwards;
[maskLayer addAnimation:maskLayerAnimation forKey:@"path"];
}
- (void)circleClose:(CGRect)startFrame {
if ([self.switchDelegate respondsToSelector:@selector(willEndAnimte)]) {
[self.switchDelegate willEndAnimte];
}
self.presentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.superview.bounds.size.width, self.superview.bounds.size.height)];
self.presentView.backgroundColor = self.onTintColor;
[self.superview insertSubview:self.presentView atIndex:0];
self.superview.backgroundColor = [UIColor whiteColor];
UIBezierPath *maskStartBP = [UIBezierPath bezierPathWithOvalInRect:startFrame];
CGFloat radius = sqrtf(powf((startFrame.origin.x+startFrame.size.width/2.f), 2)+powf((startFrame.origin.y+startFrame.size.height/2.f), 2));
UIBezierPath *maskFinalBP = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(startFrame, -radius, -radius)];
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.path = maskFinalBP.CGPath;
maskLayer.backgroundColor = (__bridge CGColorRef)([UIColor whiteColor]);
self.presentView.layer.mask = maskLayer;
CABasicAnimation *maskLayerAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
maskLayerAnimation.fromValue = (__bridge id)(maskFinalBP.CGPath);
maskLayerAnimation.toValue = (__bridge id)((maskStartBP.CGPath));
maskLayerAnimation.duration = 0.3f;
maskLayerAnimation.delegate = self;
maskLayerAnimation.repeatCount = 1;
maskLayerAnimation.removedOnCompletion = NO;
maskLayerAnimation.fillMode = kCAFillModeForwards;
[maskLayer addAnimation:maskLayerAnimation forKey:@"path"];
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
self.presentView.layer.mask = nil;
[self.presentView removeFromSuperview];
if (self.on) {
self.superview.backgroundColor = self.onTintColor;
}
}
遵循Protocol完成修改字的颜色
- (void)willBeginAnimte {
self.titleLabel.textColor = [UIColor whiteColor];
}
- (void)willEndAnimte {
self.titleLabel.textColor = RGB(36, 173, 251);
}
到此基本就完成了
附上Demo链接
里面有各种各样奇奇怪怪的东西。。都是自己在网上看到自己觉得好看的效果乱写的Demo~,如果有需要一片文章来讲解的也可以留言告诉我。
就酱。
最后附上我自己做的效果图
效果图_UISwitch.gif