震动效果

简介:

震动效果

 

效果

 

源码

https://github.com/rFlex/SCViewShaker

https://github.com/YouXianMing/Animations



//
//  UIView+Shake.h
//  Animations
//
//  Created by YouXianMing on 16/2/25.
//  Copyright © 2016年 YouXianMing. All rights reserved.
//

#import <UIKit/UIKit.h>

// https://github.com/rFlex/SCViewShaker

#define kDefaultShakeOptions  (SCShakeOptionsDirectionHorizontal | SCShakeOptionsForceInterpolationExpDown | SCShakeOptionsAtEndComplete)
#define kDefaultShakeForce    (0.075)
#define kDefaultShakeDuration (0.5)
#define kDefaultShakeIterationDuration (0.03)

typedef enum : NSUInteger {
    
    SCShakeOptionsDirectionRotate                = 0,
    SCShakeOptionsDirectionHorizontal            = 1,
    SCShakeOptionsDirectionVertical              = 2,
    SCShakeOptionsDirectionHorizontalAndVertical = 3,
    SCShakeOptionsForceInterpolationNone         = 4,
    SCShakeOptionsForceInterpolationLinearUp     = 8,
    SCShakeOptionsForceInterpolationLinearDown   = 16,
    SCShakeOptionsForceInterpolationExpUp        = 32,
    SCShakeOptionsForceInterpolationExpDown      = 64,
    SCShakeOptionsForceInterpolationRandom       = 128,
    SCShakeOptionsAtEndRestart                   = 256,
    SCShakeOptionsAtEndComplete                  = 512,
    SCShakeOptionsAtEndContinue                  = 1024,
    SCShakeOptionsAutoreverse                    = 2048
    
} SCShakeOptions;

typedef void(^ShakeCompletionHandler)();

@interface UIView (Shake)

/**
 *  Returns whether the view is currently shaking or not.
 */
@property (readonly, nonatomic) BOOL isShaking;

/**
 *  Shake the view using the default options. The view will be shaken for a short amount of time.
 */
- (void)shake;

/*
 Shake the view using the specified options.
 |shakeOptions| is an enum of options that can be activated by using the OR operator (like SCShakeOptionsDirectionRotate | SCShakeOptionsForceInterpolationNone).
 
 |force| is the coefficient of force to apply on each shake iteration (typically between 0 and 1 even though nothing prevents you for setting a higher value if you want).
 
 |duration| is the total duration of the shaking motion. This may be ignored depending of the options you set.
 iterationDuration is how long each shake iteration will last. You may want to set a very low value (like 0.02) if you want a proper shake effect.
 
 |completionHandler|, if not null, is the block that will be invoked when the shake finishes.
 */
- (void)shakeWithOptions:(SCShakeOptions)shakeOptions
                   force:(CGFloat)force duration:(CGFloat)duration
       iterationDuration:(CGFloat)iterationDuration
       completionHandler:(ShakeCompletionHandler)completionHandler;

/**
 *  End the current shaking action, if any
 */
- (void)endShake;

@end


//
//  UIView+Shake.m
//  Animations
//
//  Created by YouXianMing on 16/2/25.
//  Copyright © 2016年 YouXianMing. All rights reserved.
//

#import "UIView+Shake.h"
#import <objc/runtime.h>

#define HAS_OPT(options, option) ((options & option) == option)

@interface SCShakeInfo : NSObject

@property (assign, nonatomic)   CGAffineTransform baseTransform;
@property (assign, nonatomic)   BOOL shaking;
@property (assign, nonatomic)   SCShakeOptions options;
@property (assign, nonatomic)   CGFloat force;
@property (assign, nonatomic)   CGFloat duration;
@property (assign, nonatomic)   CGFloat iterationDuration;
@property (assign, nonatomic)   CFTimeInterval startTime;
@property (strong, nonatomic)   ShakeCompletionHandler completionHandler;
@property (assign, nonatomic)   BOOL reversed;
@property (readonly, nonatomic) CGFloat completionRatio;

@end

@implementation SCShakeInfo

- (CGFloat)completionRatio {
    
    return (CACurrentMediaTime() - self.startTime) / self.duration;
}

@end

@implementation UIView (Shake)

static const char *ShakeInfoKey = "ShakeInfo";

- (void)shake {
    
    [self shakeWithOptions:kDefaultShakeOptions
                     force:kDefaultShakeForce
                  duration:kDefaultShakeDuration
         iterationDuration:kDefaultShakeIterationDuration
         completionHandler:nil];
}

- (void)shakeWithOptions:(SCShakeOptions)shakeOptions
                   force:(CGFloat)force
                duration:(CGFloat)duration
       iterationDuration:(CGFloat)iterationDuration
       completionHandler:(ShakeCompletionHandler)completionHandler {
    
    SCShakeInfo *shakeInfo = [self shakeInfo];
    
    shakeInfo.options           = shakeOptions;
    shakeInfo.force             = force;
    shakeInfo.startTime         = CACurrentMediaTime();
    shakeInfo.duration          = duration;
    shakeInfo.iterationDuration = iterationDuration;
    shakeInfo.completionHandler = completionHandler;
    
    if (!shakeInfo.shaking) {
        
        shakeInfo.baseTransform = self.transform;
        shakeInfo.shaking       = YES;
        
        [self _doAnimation:1];
    }
}

- (CGFloat)_getInterpolationRatio:(CGFloat)completionRatio options:(SCShakeOptions)options {
    
    CGFloat (*interpFunc)(CGFloat) = nil;
    
    if (HAS_OPT(options, SCShakeOptionsForceInterpolationRandom)) {
        
        interpFunc =& InterpolateRandom;
        
    } else if (HAS_OPT(options, SCShakeOptionsForceInterpolationExpDown)) {
        
        interpFunc =& InterpolateExpDown;
        
    } else if (HAS_OPT(options, SCShakeOptionsForceInterpolationExpUp)) {
        
        interpFunc =& InterpolateExpUp;
        
    } else if (HAS_OPT(options, SCShakeOptionsForceInterpolationLinearDown)) {
        
        interpFunc =& InterpolateLinearDown;
        
    } else if (HAS_OPT(options, SCShakeOptionsForceInterpolationLinearUp)) {
        
        interpFunc =& InterpolateLinearUp;
        
    } else {
        
        interpFunc =& InterpolateNone;
    }
    
    return interpFunc(completionRatio);
}

- (void)_animate:(CGFloat)force shakeInfo:(SCShakeInfo *)shakeInfo {
    
    CGAffineTransform baseTransform = shakeInfo.baseTransform;
    SCShakeOptions    options       = shakeInfo.options;
    
    if (HAS_OPT(options, SCShakeOptionsDirectionHorizontalAndVertical)) {
        
        if (arc4random_uniform(2) == 1) {
            
            self.transform = CGAffineTransformTranslate(baseTransform, 0, force * self.bounds.size.height);
            
        } else {
            
            self.transform = CGAffineTransformTranslate(baseTransform, force * self.bounds.size.width, 0);
        }
        
    } else if (HAS_OPT(options, SCShakeOptionsDirectionVertical)) {
        
        self.transform = CGAffineTransformTranslate(baseTransform, 0, force * self.bounds.size.height);
        
    } else if (HAS_OPT(options, SCShakeOptionsDirectionHorizontal)) {
        
        self.transform = CGAffineTransformTranslate(baseTransform, force * self.bounds.size.width, 0);
        
    } else {
        
        self.transform = CGAffineTransformRotate(baseTransform, force * M_PI_2);
    }
}

- (void)_doAnimation:(CGFloat)direction {
    
    SCShakeInfo *shakeInfo  = [self shakeInfo];
    SCShakeOptions options  = shakeInfo.options;
    CGFloat completionRatio = shakeInfo.completionRatio;
    
    if (completionRatio > 1) {
        
        completionRatio = 1;
    }
    
    if (shakeInfo.reversed) {
        
        completionRatio = 1 - completionRatio;
    }
    
    CGFloat interpolationRatio = [self _getInterpolationRatio:completionRatio options:options];
    CGFloat force              = shakeInfo.force * interpolationRatio * direction;
    CGFloat iterationDuration  = shakeInfo.iterationDuration;
    
    [UIView animateWithDuration:iterationDuration animations:^{
        
        [self _animate:force shakeInfo:shakeInfo];
        
    } completion:^(BOOL finished) {
        
        if (shakeInfo.shaking) {
            
            BOOL shouldRecurse = YES;
            if (shakeInfo.completionRatio > 1) {
                
                if (HAS_OPT(shakeInfo.options, SCShakeOptionsAutoreverse)) {
                    
                    shakeInfo.reversed = !shakeInfo.reversed;
                }
                
                if (shakeInfo.reversed || HAS_OPT(shakeInfo.options, SCShakeOptionsAtEndRestart)) {
                    
                    shakeInfo.startTime = CACurrentMediaTime();
                    
                } else if (!HAS_OPT(shakeInfo.options, SCShakeOptionsAtEndContinue)) {
                    
                    shouldRecurse = NO;
                    [self endShake];
                }
            }
            
            if (shouldRecurse) {
                
                [self _doAnimation:direction * -1];
            }
        }
    }];
}

- (void)endShake {
    
    SCShakeInfo *shakeInfo = [self shakeInfo];
    
    if (shakeInfo.shaking) {
        
        shakeInfo.shaking                        = NO;
        self.transform                           = shakeInfo.baseTransform;
        ShakeCompletionHandler completionHandler = shakeInfo.completionHandler;
        shakeInfo.completionHandler              = nil;
        
        if (completionHandler != nil) {
            
            completionHandler();
        }
    }
}

- (BOOL)isShaking {
    
    return [self shakeInfo].shaking;
}

- (SCShakeInfo *)shakeInfo {
    
    SCShakeInfo *shakeInfo = objc_getAssociatedObject(self, ShakeInfoKey);
    
    if (shakeInfo == nil) {
        
        shakeInfo = [SCShakeInfo new];
        objc_setAssociatedObject(self, ShakeInfoKey, shakeInfo, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    return shakeInfo;
}

#pragma Interpolations functions

static CGFloat InterpolateLinearUp(CGFloat input) {
    
    return input;
}

static CGFloat InterpolateLinearDown(CGFloat input) {
    
    return 1 - input;
}

static CGFloat Exp(CGFloat a, int power) {
    
    if (a < 0.5f) {
        
        return (float)pow(a * 2, power) / 2;
        
    } else {
    
        return (float)pow((a - 1) * 2, power) / (power % 2 == 0 ? -2 : 2) + 1;
    }
}

static CGFloat InterpolateExpUp(CGFloat input) {
    
    return Exp(input, 4);
}

static CGFloat InterpolateExpDown(CGFloat input) {
    
    return Exp(1 - input, 4);
}

static CGFloat InterpolateNone(CGFloat input) {
    
    return 1;
}

static CGFloat InterpolateRandom(CGFloat input) {
    
    CGFloat randNb = arc4random_uniform(10000);
    return randNb / 10000.0;
}

@end


目录
相关文章
|
12月前
macbookpro触摸板突然失灵按压无震动反馈和回弹感
macbookpro触摸板突然失灵按压无震动反馈和回弹感
515 0
|
11月前
(4)(4.6.6) 罗盘校准
(4)(4.6.6) 罗盘校准
96 0
距离感应器黑屏,如何一直亮屏
距离感应器黑屏,如何一直亮屏
50 0
|
Web App开发 机器学习/深度学习 算法
AliAGC 自动增益控制算法:解决复杂场景下的音量问题
极大改善不同环境、设备、场景下音量不统一的问题
9724 0
AliAGC 自动增益控制算法:解决复杂场景下的音量问题
定阻喇叭与定压喇叭区别
  定压功放是为了使传输距离增大而设计的一种功放,主要用在经共广播系统中,如学校,工厂以及其他公共场所的广播,都是以定压的方式来传输的.其输出方式有100V 70V,输出的音乐信号基本是都是单声道,在选配喇叭上,要求喇叭上要接有线间变压器.功放和阻抗这间不要求阻抗的匹配.定阻功放是以家庭影院功放为代表.在选配喇叭上,要求喇叭的阻抗和功放的功放要匹配.比如功放的阻抗为8欧.那么音箱的阻抗就要为8欧.选配的喇叭上不需要有变压器.   简单的说。
4840 0
失控的手机相机
进入“失控”赛道的国产手机,是被什么绑架了?
340 0