iOS技术程序员

iOS 那些年好看的UISwitch控件的效果

2018-01-15  本文已影响327人  ______Dx

距离上次更新又过了好久(懒懒懒😪)
今天来写一篇关于UISwitch(开关控件)个人觉得不错的交互动画

先上图,这是在别的地方看到的效果,拿来实现以下~


UISwitch_示例.gif

本文直接讲如何实现这个效果,代码上没有做太多的优化,直接继承类UISwitch
所以可能会出现类似

sender.subviews[0].subviews.lastObject.frame

这种蛋疼的代码(见谅见谅~),所以你要先理解系统自己的UISwitch控件的层级关系,后文虽然会指出我用的是哪个View,但自己先去Debug view hierarchy里面看看会更便于理解。(这里先上一张图便于简单的理解)

Tip.png

这个被我改成黑色背景的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)

Math_Tip.png

所以。。计算这个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
上一篇下一篇

猜你喜欢

热点阅读