iOS icon jiggle algorithm

前端 未结 11 845
执笔经年
执笔经年 2020-12-12 16:31

I am writing an iPad app that presents user documents similar to the way Pages presents them (as large icons of the actual document). I also want to mimic the jiggling beha

相关标签:
11条回答
  • 2020-12-12 16:55

    For completeness, here is how I animated my CALayer subclass — inspired by the other answers — using an explicit animation.

    -(void)stopJiggle 
    {
        [self removeAnimationForKey:@"jiggle"];
    }
    
    -(void)startJiggle 
    {
        const float amplitude = 1.0f; // degrees
        float r = ( rand() / (float)RAND_MAX ) - 0.5f;
        float angleInDegrees = amplitude * (1.0f + r * 0.1f);
        float animationRotate = angleInDegrees / 180. * M_PI; // Convert to radians
    
        NSTimeInterval duration = 0.1;
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
        animation.duration = duration;
        animation.additive = YES;
        animation.autoreverses = YES;
        animation.repeatCount = FLT_MAX;
        animation.fromValue = @(-animationRotate);
        animation.toValue = @(animationRotate);
        animation.timeOffset = ( rand() / (float)RAND_MAX ) * duration;
        [self addAnimation:animation forKey:@"jiggle"];
    }
    
    0 讨论(0)
  • 2020-12-12 16:57

    @mientus Original Apple Jiggle code in Swift 4, with optional parameters to adjust the duration (i.e. speed), displacement (i.e. position change) and degrees (i.e. rotation amount).

    private func degreesToRadians(_ x: CGFloat) -> CGFloat {
        return .pi * x / 180.0
    }
    
    func startWiggle(
        duration: Double = 0.25,
        displacement: CGFloat = 1.0,
        degreesRotation: CGFloat = 2.0
        ) {
        let negativeDisplacement = -1.0 * displacement
        let position = CAKeyframeAnimation.init(keyPath: "position")
        position.beginTime = 0.8
        position.duration = duration
        position.values = [
            NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement)),
            NSValue(cgPoint: CGPoint(x: 0, y: 0)),
            NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: 0)),
            NSValue(cgPoint: CGPoint(x: 0, y: negativeDisplacement)),
            NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement))
        ]
        position.calculationMode = "linear"
        position.isRemovedOnCompletion = false
        position.repeatCount = Float.greatestFiniteMagnitude
        position.beginTime = CFTimeInterval(Float(arc4random()).truncatingRemainder(dividingBy: Float(25)) / Float(100))
        position.isAdditive = true
    
        let transform = CAKeyframeAnimation.init(keyPath: "transform")
        transform.beginTime = 2.6
        transform.duration = duration
        transform.valueFunction = CAValueFunction(name: kCAValueFunctionRotateZ)
        transform.values = [
            degreesToRadians(-1.0 * degreesRotation),
            degreesToRadians(degreesRotation),
            degreesToRadians(-1.0 * degreesRotation)
        ]
        transform.calculationMode = "linear"
        transform.isRemovedOnCompletion = false
        transform.repeatCount = Float.greatestFiniteMagnitude
        transform.isAdditive = true
        transform.beginTime = CFTimeInterval(Float(arc4random()).truncatingRemainder(dividingBy: Float(25)) / Float(100))
    
        self.layer.add(position, forKey: nil)
        self.layer.add(transform, forKey: nil)
    }
    
    0 讨论(0)
  • 2020-12-12 16:59

    In case anyone needs the same code in Swift

    class Animation {
    
        static func wiggle(_ btn: UIButton) {
            btn.startWiggling()
        }
    } 
    
    extension UIView {
    
    func startWiggling() {
    
        let count = 5
        let kAnimationRotateDeg = 1.0
    
        let leftDegrees = (kAnimationRotateDeg * ((count%2 > 0) ? +5 : -5)).convertToDegrees()
        let leftWobble = CGAffineTransform(rotationAngle: leftDegrees)
    
        let rightDegrees = (kAnimationRotateDeg * ((count%2 > 0) ? -10 : +10)).convertToDegrees()
        let rightWobble = CGAffineTransform(rotationAngle: rightDegrees)
    
        let moveTransform = rightWobble.translatedBy(x: -2.0, y: 2.0)
        let concatTransform = rightWobble.concatenating(moveTransform)
    
        self.transform = leftWobble
    
        UIView.animate(withDuration: 0.1,
                       delay: 0.1,
                       options: [.allowUserInteraction, .repeat, .autoreverse],
                       animations: {
                        UIView.setAnimationRepeatCount(3)
                        self.transform = concatTransform
        }, completion: { success in
            self.layer.removeAllAnimations()
            self.transform = .identity
        })
    }
    }
    

    Just Call

        Animation.wiggle(viewToBeAnimated)
    

    It is always best to write a wrapper over the functions you are calling so that even if you have to change the function arguments or may be the name of the function, it does not take you to rewrite it everywhere in the code.

    0 讨论(0)
  • 2020-12-12 17:01

    Check out the openspringboard project.

    In particular, setIconAnimation:(BOOL)isAnimating in OpenSpringBoard.m. That should give you some ideas on how to do this.

    0 讨论(0)
  • 2020-12-12 17:05

    So I'm sure I'll get yelled at for writing messy code (there are probably simpler ways to do this that I am not aware of because I'm a semi-beginner), but this is a more random version of Vic320's algorithm that varies the amount of rotation and translation. It also decides randomly which direction it will wobble first, which gives a much more random look if you have multiple things wobbling simultaneously. If efficiency is a big problem for you, do not use. This is just what I came up with with the way that I know how to do it.

    For anyone wondering you need to #import <QuartzCore/QuartzCore.h> and add it to your linked libraries.

    #define degreesToRadians(x) (M_PI * (x) / 180.0)
    
    - (void)startJiggling:(NSInteger)count {
        double kAnimationRotateDeg = (double)(arc4random()%5 + 5) / 10;
        double kAnimationTranslateX = (arc4random()%4);
        double kAnimationTranslateY = (arc4random()%4);
    
        CGAffineTransform leftWobble = CGAffineTransformMakeRotation(degreesToRadians( kAnimationRotateDeg * (count%2 ? +1 : -1 ) ));
        CGAffineTransform rightWobble = CGAffineTransformMakeRotation(degreesToRadians( kAnimationRotateDeg * (count%2 ? -1 : +1 ) ));
        int leftOrRight = (arc4random()%2);
        if (leftOrRight == 0){
            CGAffineTransform moveTransform = CGAffineTransformTranslate(rightWobble, -kAnimationTranslateX, -kAnimationTranslateY);
            CGAffineTransform conCatTransform = CGAffineTransformConcat(rightWobble, moveTransform);
            self.transform = leftWobble;  // starting point
    
            [UIView animateWithDuration:0.1
                                  delay:(count * 0.08)
                                options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse
                             animations:^{ self.transform = conCatTransform; }
                             completion:nil];
        } else if (leftOrRight == 1) {
            CGAffineTransform moveTransform = CGAffineTransformTranslate(leftWobble, -kAnimationTranslateX, -kAnimationTranslateY);
            CGAffineTransform conCatTransform = CGAffineTransformConcat(leftWobble, moveTransform);
            self.transform = rightWobble;  // starting point
    
            [UIView animateWithDuration:0.1
                                  delay:(count * 0.08)
                                options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse
                             animations:^{ self.transform = conCatTransform; }
                             completion:nil];
        }
    }
    
    - (void)stopJiggling {
        [self.layer removeAllAnimations];
        self.transform = CGAffineTransformIdentity;  // Set it straight 
    }
    
    0 讨论(0)
  • 2020-12-12 17:07

    Here is the Swift 4.2 version of @mientus' code (which is itself an update of Paul Popiel's version), as an extension of CALayer:

    extension CALayer {
    
        private enum WigglingAnimationKey: String {
            case position = "wiggling_position_animation"
            case transform = "wiggling_transform_animation"
        }
    
        func startWiggling() {
            let duration = 0.25
            let displacement = 1.0
            let negativeDisplacement = displacement * -1
            let rotationAngle = Measurement(value: 2, unit: UnitAngle.degrees)
    
            // Position animation
            let positionAnimation = CAKeyframeAnimation(keyPath: #keyPath(CALayer.position))
            positionAnimation.beginTime = 0.8
            positionAnimation.duration = duration
            positionAnimation.values = [
                NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement)),
                NSValue(cgPoint: CGPoint.zero),
                NSValue(cgPoint: CGPoint(x: 0, y: negativeDisplacement)),
                NSValue(cgPoint: CGPoint(x: negativeDisplacement, y: negativeDisplacement))
            ]
            positionAnimation.calculationMode = .linear
            positionAnimation.isRemovedOnCompletion = false
            positionAnimation.repeatCount = .greatestFiniteMagnitude
            positionAnimation.beginTime = CFTimeInterval(Float(Int.random(in: 0...25)) / 100)
            positionAnimation.isAdditive = true
    
            // Rotation animation
            let transformAnimation = CAKeyframeAnimation(keyPath: #keyPath(CALayer.transform))
            transformAnimation.beginTime = 2.6
            transformAnimation.duration = duration
            transformAnimation.valueFunction = CAValueFunction(name: .rotateZ)
            transformAnimation.values = [
                CGFloat(rotationAngle.converted(to: .radians).value * -1),
                CGFloat(rotationAngle.converted(to: .radians).value),
                CGFloat(rotationAngle.converted(to: .radians).value * -1)
            ]
            transformAnimation.calculationMode = .linear
            transformAnimation.isRemovedOnCompletion = false
            transformAnimation.repeatCount = .greatestFiniteMagnitude
            transformAnimation.isAdditive = true
            transformAnimation.beginTime = CFTimeInterval(Float(Int.random(in: 0...25)) / 100)
    
            self.add(positionAnimation, forKey: WigglingAnimationKey.position.rawValue)
            self.add(transformAnimation, forKey: WigglingAnimationKey.transform.rawValue)
        }
    
        func stopWiggling() {
            self.removeAnimation(forKey: WigglingAnimationKey.position.rawValue)
            self.removeAnimation(forKey: WigglingAnimationKey.transform.rawValue)
        }
    }
    

    Usage (where anyLayer is a CALayer):

    // Start animating.
    anyLayer.startWiggling()
    // Stop animating.
    anyLayer.stopWiggling()
    
    0 讨论(0)
提交回复
热议问题