UI基础--自定义UISwitch

昌勇锐
2023-12-01

建一个动画管理类

.h

#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>


@interface LLAnimationManager : NSObject


/**
 *  init
 */
- (instancetype)initWithAnimationDuration:(CGFloat)animationDuration;


/**
 * the face layer move position
 */
- (CABasicAnimation *)moveAnimationWithFromPosition:(CGPoint)fromPosition toPosition:(CGPoint)toPosition;

/**
 * the layer background color animation
 */
- (CABasicAnimation *)backgroundColorAnimationFromValue:(NSValue *)fromValue toValue:(NSValue *)toValue;

/**
 * the eye layer move position
 */
- (CABasicAnimation *)eyeMoveAnimationFromValue:(NSValue *)fromValue toValue:(NSValue *)toValue;

/**
 * mouth key frame animation
 */
- (CAKeyframeAnimation *)mouthKeyFrameAnimationWidthOffSet:(CGFloat)offSet on:(BOOL)on;

/**
 *  eyes close and open key frame animation
 */
- (CAKeyframeAnimation *)eyesCloseAndOpenAnimationWithRect:(CGRect)rect;


@end

.m

#import "LLAnimationManager.h"
#import <QuartzCore/QuartzCore.h> 

@interface LLAnimationManager()

/**
 *  the duration is the face moving time not include spring animation
 */
@property (nonatomic, assign) CGFloat animationDuration;

@end



@implementation LLAnimationManager


/**
 *  init
 */
- (instancetype)initWithAnimationDuration:(CGFloat)animationDuration {
    self = [super init];
    if (self) {
        _animationDuration = animationDuration;
    }
    return self;
}

/**
 *  faceLayer move animation
 */
- (CABasicAnimation *)moveAnimationWithFromPosition:(CGPoint)fromPosition toPosition:(CGPoint)toPosition {
    CABasicAnimation *moveAnimation = [CABasicAnimation animationWithKeyPath:@"position"];
    moveAnimation.fromValue = [NSValue valueWithCGPoint:fromPosition];
    moveAnimation.toValue = [NSValue valueWithCGPoint:toPosition];
    moveAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    moveAnimation.duration = _animationDuration * 2 /3;
    moveAnimation.removedOnCompletion = NO;
    moveAnimation.fillMode = kCAFillModeForwards;
    return moveAnimation;
}

/**
 *  layer background color animation
 */
- (CABasicAnimation *)backgroundColorAnimationFromValue:(NSValue *)fromValue toValue:(NSValue *)toValue {
    CABasicAnimation *colorAnimation = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
    colorAnimation.fromValue = fromValue;
    colorAnimation.toValue = toValue;
    colorAnimation.duration = _animationDuration * 2 /3;
    colorAnimation.removedOnCompletion = NO;
    colorAnimation.fillMode = kCAFillModeForwards;
    return colorAnimation;

}

/**
 * the eyes layer move
 */
- (CABasicAnimation *)eyeMoveAnimationFromValue:(NSValue *)fromValue toValue:(NSValue *)toValue{
    CABasicAnimation *moveAnimation = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
    moveAnimation.fromValue = fromValue;
    moveAnimation.toValue = toValue;
    moveAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    moveAnimation.duration = _animationDuration / 3;
    moveAnimation.removedOnCompletion = NO;
    moveAnimation.fillMode = kCAFillModeForwards;
    return moveAnimation;
}


/**
 * mouth key frame animation
 */
- (CAKeyframeAnimation *)mouthKeyFrameAnimationWidthOffSet:(CGFloat)offSet on:(BOOL)on{
    CGFloat frameNumber = _animationDuration * 60 / 3;
    CGFloat frameValue = on ? offSet : 0;
    NSMutableArray *arrayFrame = [NSMutableArray array];
    for (int i = 0; i < frameNumber; i++) {
        if (on) {
            frameValue = frameValue - offSet / frameNumber;
        } else {
            frameValue = frameValue + offSet / frameNumber;
        }
        [arrayFrame addObject:@(frameValue)];
    }
    CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animationWithKeyPath:@"mouthOffSet"];
    keyAnimation.values = arrayFrame;
    keyAnimation.duration = _animationDuration / 4;
    if (!on && _animationDuration >= 1.f) {
        keyAnimation.beginTime = CACurrentMediaTime() + _animationDuration / 12;
    }
    keyAnimation.removedOnCompletion = NO;
    keyAnimation.fillMode = kCAFillModeForwards;
    return keyAnimation;
}

/**
 *  eyes close and open key frame animation
 */
- (CAKeyframeAnimation *)eyesCloseAndOpenAnimationWithRect:(CGRect)rect {
    CGFloat frameNumber = _animationDuration * 180 / 9;         // 180 frame erver second
    CGFloat eyesX = rect.origin.x;
    CGFloat eyesY = rect.origin.y;
    CGFloat eyesWidth = rect.size.width;
    CGFloat eyesHeight = rect.size.height;
    NSMutableArray *arrayFrame = [NSMutableArray array];
    for (int i = 0; i < frameNumber; i++) {
        if (i < frameNumber / 3) {
            // close
            eyesHeight = eyesHeight - rect.size.height / (frameNumber / 3);
        } else if (i >= frameNumber / 3 && i < frameNumber * 2 / 3) {
            // zero
            eyesHeight = 0;
        } else {
            // open
            eyesHeight = eyesHeight + rect.size.height / (frameNumber / 3);
        }
        eyesY = (rect.size.height - eyesHeight) / 2;
        [arrayFrame addObject:[NSValue valueWithCGRect:CGRectMake(eyesX, eyesY, eyesWidth, eyesHeight)]];
    }
    CAKeyframeAnimation *keyAnimation = [CAKeyframeAnimation animationWithKeyPath:@"eyeRect"];
    keyAnimation.values = arrayFrame;
    keyAnimation.duration = _animationDuration / 3;
    keyAnimation.removedOnCompletion = NO;
    keyAnimation.fillMode = kCAFillModeForwards;
    return keyAnimation;
}

@end

创建一个类:继承CALayer

.h

#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>

@interface LLEyesLayer : CALayer

/**
 *  eye property
 */
@property (nonatomic, assign) CGRect eyeRect;

@property (nonatomic, assign) CGFloat eyeDistance;

@property (nonatomic, strong) UIColor *eyeColor;

@property (nonatomic, assign) BOOL isLiking;

@property (nonatomic, assign) CGFloat mouthOffSet;

@property (nonatomic, assign) CGFloat mouthY;

@end

.m

#import "LLEyesLayer.h"

@implementation LLEyesLayer

/**
 *  init layer
 *
 *  @return self
 */
- (instancetype)init {
    if (self = [super init]) {
        // 默认属性
        _eyeRect = CGRectMake(0, 0, 0, 0);
        _mouthOffSet = 0.f;
    }
    return self;
}

- (instancetype)initWithLayer:(LLEyesLayer *)layer {
    self = [super initWithLayer:layer];
    if (self) {
        self.eyeRect = layer.eyeRect;
        self.eyeDistance = layer.eyeDistance;
        self.eyeColor = layer.eyeColor;
        self.isLiking = layer.isLiking;
        self.mouthOffSet = layer.mouthOffSet;
        self.mouthY = layer.mouthY;
    }
    return self;
}

/**
 *  draw
 */
- (void)drawInContext:(CGContextRef)ctx {
    UIBezierPath *bezierLeft = [UIBezierPath bezierPathWithOvalInRect:_eyeRect];
    UIBezierPath *bezierRight = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(_eyeDistance, _eyeRect.origin.y, _eyeRect.size.width, _eyeRect.size.height)];
    
    
    UIBezierPath *bezierMouth = [UIBezierPath bezierPath];
    CGFloat mouthWidth = _eyeRect.size.width + _eyeDistance;
    if (_isLiking) {
        // funny mouth
        [bezierMouth moveToPoint:CGPointMake(0, _mouthY)];
        [bezierMouth addCurveToPoint:CGPointMake(mouthWidth, _mouthY) controlPoint1:CGPointMake(mouthWidth - _mouthOffSet * 3 / 4, _mouthY + _mouthOffSet / 2) controlPoint2:CGPointMake(mouthWidth - _mouthOffSet / 4, _mouthY + _mouthOffSet / 2)];
    } else {
        // boring mouth
        bezierMouth = [UIBezierPath bezierPathWithRect:CGRectMake(0, _mouthY, mouthWidth, _eyeRect.size.height / 4)];
    }

    [bezierMouth closePath];
    CGContextAddPath(ctx, bezierLeft.CGPath);
    CGContextAddPath(ctx, bezierRight.CGPath);
    CGContextAddPath(ctx, bezierMouth.CGPath);
    CGContextSetFillColorWithColor(ctx, _eyeColor.CGColor);
    CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);
    CGContextFillPath(ctx);
}


/**
 * key animation
 */
+(BOOL)needsDisplayForKey:(NSString *)key{
    if ([key isEqual:@"mouthOffSet"]) {
        return YES;
    }
    if ([key isEqual:@"eyeRect"]) {
        return YES;
    }
    return [super needsDisplayForKey:key];
}

@end

创建一个开关,继承:UIView

.h

#import <UIKit/UIKit.h>

@protocol LLSwitchDelegate;

IB_DESIGNABLE @interface LLSwitch : UIView


/**
 *  switch on color
 */
@property (nonatomic, strong) IBInspectable UIColor *onColor;

/**
 *  switch off color
 */
@property (nonatomic, strong) IBInspectable UIColor *offColor;

/**
 *  face on and off color
 */
@property (nonatomic, strong) IBInspectable UIColor *faceColor;

/**
 *  the duration is the face moving time
 */
@property (nonatomic, assign) IBInspectable CGFloat animationDuration;


/**
 *  the switch status is or isn't on
 */
@property (nonatomic, assign) IBInspectable BOOL on;

@property (nonatomic, weak) IBOutlet id <LLSwitchDelegate> delegate;

@end


#pragma mark LLSwitch delegate
@protocol LLSwitchDelegate <NSObject>

@optional


- (void)didTapLLSwitch:(LLSwitch *)llSwitch;


- (void)animationDidStopForLLSwitch:(LLSwitch *)llSwitch;

@end

.m

#import "LLSwitch.h"
#import "LLAnimationManager.h"
#import "LLEyesLayer.h"


NSString * const FaceMoveAnimationKey = @"FaceMoveAnimationKey";
NSString * const BackgroundColorAnimationKey = @"BackgroundColorAnimationKey";
NSString * const EyesMoveStartAnimationKey = @"EyesMoveStartAnimationKey";
NSString * const EyesMoveEndAnimationKey = @"EyesMoveEndAnimationKey";
NSString * const EyesMoveBackAnimationKey = @"EyesMoveBackAnimationKey";
NSString * const MouthFrameAnimationKey = @"MouthFrameAnimationKey";
NSString * const EyesCloseAndOpenAnimationKey = @"EyesCloseAndOpenAnimationKey";

@interface LLSwitch()

/**
 *  switch background view
 */
@property (nonatomic, strong) UIView *backgroundView;

/**
 *  face layer
 */
@property (nonatomic, strong) CAShapeLayer *circleFaceLayer;

/**
 *  paddingWidth
 */
@property (nonatomic, assign) CGFloat paddingWidth;

/**
 *  eyes layer
 */
@property (nonatomic, strong) LLEyesLayer *eyesLayer;

/**
 *  face radius
 */
@property (nonatomic, assign) CGFloat circleFaceRadius;

/**
 *  the faceLayer move distance
 */
@property (nonatomic, assign) CGFloat moveDistance;

/**
 *  handler layer animation manager
 */
@property (nonatomic, strong) LLAnimationManager *animationManager;


/**
 *  whether is animated
 */
@property (nonatomic, assign) BOOL isAnimating;

@property (nonatomic, assign) CGFloat faceLayerWidth;



@end


@implementation LLSwitch

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self initSetUpView];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self initSetUpView];
    }
    return self;
}


- (void)initSetUpView {
    
    /**
     *  check the switch width and height
     */
    NSAssert(self.frame.size.width >= self.frame.size.height, @"switch width must be tall!");
    
    /**
     *  init property
     */
    _onColor = [UIColor colorWithRed:73/255.0 green:182/255.0 blue:235/255.0 alpha:1.f];
    _offColor = [UIColor colorWithRed:211/255.0 green:207/255.0 blue:207/255.0 alpha:1.f];
    _faceColor = [UIColor whiteColor];
    _paddingWidth = self.frame.size.height * 0.1;
    _circleFaceRadius = (self.frame.size.height - 2 * _paddingWidth) / 2;
    _animationDuration = 1.2f;
    _animationManager = [[LLAnimationManager alloc] initWithAnimationDuration:_animationDuration];
    _moveDistance = self.frame.size.width - _paddingWidth * 2 - _circleFaceRadius * 2;
    _on = NO;
    _isAnimating = NO;
    
    /**
     *  setting init property
     */
    self.backgroundView.backgroundColor = _offColor;
    self.circleFaceLayer.fillColor = _faceColor.CGColor;
    self.faceLayerWidth = self.circleFaceLayer.frame.size.width;
    [self.eyesLayer setNeedsDisplay];
    
    [self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapSwitch)]];
}


#pragma mark set property

- (void)setBackgroundColor:(UIColor *)backgroundColor {
    return;
}

- (void)setOffColor:(UIColor *)offColor {
    _offColor = offColor;
    if (!_on) {
        _backgroundView.backgroundColor = offColor;
        _eyesLayer.eyeColor = offColor;
        [self.eyesLayer setNeedsDisplay];
    }
}

- (void)setOnColor:(UIColor *)onColor {
    _onColor = onColor;
    if (_on) {
        _backgroundView.backgroundColor = onColor;
        _eyesLayer.eyeColor = onColor;
        [self.eyesLayer setNeedsDisplay];
    }
}

- (void)setFaceColor:(UIColor *)faceColor {
    _faceColor = faceColor;
    _circleFaceLayer.fillColor = faceColor.CGColor;
}

- (void)setAnimationDuration:(CGFloat)animationDuration {
    _animationDuration = animationDuration;
    _animationManager = [[LLAnimationManager alloc] initWithAnimationDuration:_animationDuration];
}

- (void)setOn:(BOOL)on {
    _on = on;
    if (on) {
        self.backgroundView.backgroundColor = _onColor;
        self.circleFaceLayer.position = CGPointMake(self.circleFaceLayer.position.x + _moveDistance, self.circleFaceLayer.position.y);
        self.eyesLayer.eyeColor = _onColor;
        self.eyesLayer.isLiking = YES;
        self.eyesLayer.mouthOffSet = _eyesLayer.frame.size.width;
        [self.eyesLayer needsDisplay];
    }
}


#pragma mark GestureRecognizer
- (void)handleTapSwitch {
    if (_isAnimating) {
        return;
    }
    _isAnimating = YES;
    // faceLayer
    CABasicAnimation *moveAnimation = [_animationManager moveAnimationWithFromPosition:_circleFaceLayer.position toPosition:_on ? CGPointMake(_circleFaceLayer.position.x - _moveDistance, _circleFaceLayer.position.y) : CGPointMake(_circleFaceLayer.position.x + _moveDistance, _circleFaceLayer.position.y)];
    moveAnimation.delegate = self;
    [_circleFaceLayer addAnimation:moveAnimation forKey:FaceMoveAnimationKey];
    
    // backfroundView
    CABasicAnimation *colorAnimation = [_animationManager backgroundColorAnimationFromValue:(id)(_on ? _onColor : _offColor).CGColor toValue:(id)(_on ? _offColor : _onColor).CGColor];
    [_backgroundView.layer addAnimation:colorAnimation forKey:BackgroundColorAnimationKey];
    
    // eyesLayer
    CABasicAnimation *rotationAnimation = [_animationManager eyeMoveAnimationFromValue:@(0) toValue:@(_on ? -_faceLayerWidth : _faceLayerWidth)];
    rotationAnimation.delegate = self;
    [_eyesLayer addAnimation:rotationAnimation forKey:EyesMoveStartAnimationKey];
    _circleFaceLayer.masksToBounds = YES;
    if (_on) {
        [self eyesKeyFrameAnimationStart];
    }
    
    
    // start delegate
    if ([self.delegate respondsToSelector:@selector(didTapLLSwitch:)]) {
        [self.delegate didTapLLSwitch:self];
    }
    
}



#pragma mark Init

/**
 *  init backgroundView
 *
 *  @return backgroundView
 */
- (UIView *)backgroundView {
    if (!_backgroundView) {
        _backgroundView = [[UIView alloc] init];
        _backgroundView.frame = self.bounds;
        _backgroundView.layer.cornerRadius = self.frame.size.height / 2;
        _backgroundView.layer.masksToBounds = YES;
        [self addSubview:_backgroundView];
    }
    return _backgroundView;
}


/**
 *  init circleFaceLayer
 *
 *  @return circleFaceLayer
 */
- (CAShapeLayer *)circleFaceLayer {
    if (!_circleFaceLayer) {
        _circleFaceLayer = [CAShapeLayer layer];
        [_circleFaceLayer setFrame:CGRectMake(_paddingWidth, _paddingWidth, _circleFaceRadius * 2, _circleFaceRadius *2)];
        UIBezierPath *circlePath = [UIBezierPath bezierPathWithOvalInRect:_circleFaceLayer.bounds];
        _circleFaceLayer.path = circlePath.CGPath;
        [self.backgroundView.layer addSublayer:_circleFaceLayer];
    }
    return _circleFaceLayer;
}


/**
 *  eyes and mouth layer
 *
 *  @return eyesLayer
 */
- (LLEyesLayer *)eyesLayer {
    if (!_eyesLayer) {
        _eyesLayer = [LLEyesLayer layer];
        _eyesLayer.eyeRect = CGRectMake(0, 0, _faceLayerWidth / 6, _circleFaceLayer.frame.size.height * 0.22);
        _eyesLayer.eyeDistance = _faceLayerWidth / 3;
        _eyesLayer.eyeColor = _offColor;
        _eyesLayer.isLiking = NO;
        _eyesLayer.mouthY = _eyesLayer.eyeRect.size.height * 7 / 4;
        _eyesLayer.frame = CGRectMake(_faceLayerWidth / 4, _circleFaceLayer.frame.size.height * 0.28, _faceLayerWidth / 2, _circleFaceLayer.frame.size.height * 0.72);
    //    _eyesLayer.backgroundColor = [UIColor redColor].CGColor;
        [self.circleFaceLayer addSublayer:_eyesLayer];

    }
    return  _eyesLayer;
}

#pragma mark AnimationDelegate
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    if (flag) {
        
        // start eyes ending animation
        if (anim == [_eyesLayer animationForKey:EyesMoveStartAnimationKey]) {
            _eyesLayer.eyeColor = _on ?  _offColor: _onColor;
            _eyesLayer.isLiking = !_on;
            [_eyesLayer setNeedsDisplay];
            CABasicAnimation *rotationAnimation = [_animationManager eyeMoveAnimationFromValue:@(_on ? _faceLayerWidth : -_faceLayerWidth) toValue:@(_on ? -_faceLayerWidth / 6 :  _faceLayerWidth / 6)];
            rotationAnimation.delegate = self;
            [_eyesLayer addAnimation:rotationAnimation forKey:EyesMoveEndAnimationKey];
            
            if (!_on) {
                [self eyesKeyFrameAnimationStart];
            }
        }
        
        // start eyes back animation
        if (anim == [_eyesLayer animationForKey:EyesMoveEndAnimationKey]) {
            CABasicAnimation *rotationAnimation = [_animationManager eyeMoveAnimationFromValue:@(_on ? -_faceLayerWidth / 6 :  _faceLayerWidth / 6) toValue:@(0)];
            rotationAnimation.delegate = self;
            [_eyesLayer addAnimation:rotationAnimation forKey:EyesMoveBackAnimationKey];
            
            if (!_on) {
                CAKeyframeAnimation *eyesKeyFrameAnimation = [_animationManager eyesCloseAndOpenAnimationWithRect:_eyesLayer.eyeRect];
                [_eyesLayer addAnimation:eyesKeyFrameAnimation forKey:EyesCloseAndOpenAnimationKey];
            }
        }
        
        // eyes back animation end
        if (anim == [_eyesLayer animationForKey:EyesMoveBackAnimationKey]) {
            [_eyesLayer removeAllAnimations];
            _eyesLayer.mouthOffSet = _on ? 0 : _eyesLayer.frame.size.width;
            
            if (_on) {
                _circleFaceLayer.position = CGPointMake(_circleFaceLayer.position.x - _moveDistance, _circleFaceLayer.position.y);
                _on = NO;
            } else {
                _circleFaceLayer.position = CGPointMake(_circleFaceLayer.position.x + _moveDistance, _circleFaceLayer.position.y);
                _on = YES;
            }
            _isAnimating = NO;
            
            
            // stop delegate
            if ([self.delegate respondsToSelector:@selector(animationDidStopForLLSwitch:)]) {
                [self.delegate animationDidStopForLLSwitch:self];
            }
        }
    }
}

/**
 *  add mouth keyFrameAnimation
 */
- (void)eyesKeyFrameAnimationStart {
    CAKeyframeAnimation *keyAnimation = [_animationManager mouthKeyFrameAnimationWidthOffSet:_eyesLayer.frame.size.width on:_on];
    [_eyesLayer addAnimation:keyAnimation forKey:MouthFrameAnimationKey];
}

- (void)dealloc {
    self.delegate = nil;
}


@end

在需要的地方使用即可:

#import "ViewController.h"
#import "LLSwitch.h"

@interface ViewController () <LLSwitchDelegate>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    LLSwitch *llSwitch = [[LLSwitch alloc] initWithFrame:CGRectMake(100, 100, 120, 60)];
    [self.view addSubview:llSwitch];
    llSwitch.delegate = self;
}

-(void)didTapLLSwitch:(LLSwitch *)llSwitch {
    NSLog(@"start");
}

- (void)animationDidStopForLLSwitch:(LLSwitch *)llSwitch {
    NSLog(@"stop");
}

@end

 

转载于:https://www.cnblogs.com/LzwBlog/p/5864570.html

 类似资料: