iOS图标微动算法

时间:2011-07-06 23:53:15

标签: iphone ios ipad uiview core-animation

我正在编写一个iPad应用程序,它提供类似于Pages呈现方式的用户文档(作为实际文档的大图标)。当用户点击编辑按钮时,我也想模仿摇晃行为。当您在iPhone和iPad上点按并按住图标时,这与图标在主屏幕上所做的抖动模式相同。

我搜索了互联网并找到了一些算法,但它们只是让视图来回摇摆,这完全不像Apple的摇摆。似乎有一些随机性,因为每个图标都有点不同。

有没有人知道某些代码可以重新创建相同的抖动模式(或者非常接近它的东西)?感谢!!!

11 个答案:

答案 0 :(得分:33)


@Vic320的答案很好,但我个人不喜欢翻译。 我编辑了他的代码以提供一个我个人觉得看起来更像是跳板摆动效果的解决方案。大多数情况下,它是通过添加一点随机性并专注于旋转而无需翻译来实现的:

#define degreesToRadians(x) (M_PI * (x) / 180.0)
#define kAnimationRotateDeg 1.0

- (void)startJiggling {
    NSInteger randomInt = arc4random_uniform(500);
    float r = (randomInt/500.0)+0.5;

    CGAffineTransform leftWobble = CGAffineTransformMakeRotation(degreesToRadians( (kAnimationRotateDeg * -1.0) - r ));
    CGAffineTransform rightWobble = CGAffineTransformMakeRotation(degreesToRadians( kAnimationRotateDeg + r ));

     self.transform = leftWobble;  // starting point

     [[self layer] setAnchorPoint:CGPointMake(0.5, 0.5)];

     [UIView animateWithDuration:0.1
                           delay:0
                         options:UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse
                       animations:^{ 
                                 [UIView setAnimationRepeatCount:NSNotFound];
                                 self.transform = rightWobble; }
                      completion:nil];
}
- (void)stopJiggling {
    [self.layer removeAllAnimations];
    self.transform = CGAffineTransformIdentity;
}


虽然信用额到期,@ Vic320的答案提供了此代码的基础,所以+1为此。

答案 1 :(得分:12)

好的,所以openspringboard代码对我来说不是很好,但是我确实允许我创建一些我认为更好的代码,但仍然不是完美但更好。如果有人有建议让这更好,我很乐意听到他们......(将此添加到您想要摇晃的视图的子类)

#define degreesToRadians(x) (M_PI * (x) / 180.0)
#define kAnimationRotateDeg 1.0
#define kAnimationTranslateX 2.0
#define kAnimationTranslateY 2.0

- (void)startJiggling:(NSInteger)count {

    CGAffineTransform leftWobble = CGAffineTransformMakeRotation(degreesToRadians( kAnimationRotateDeg * (count%2 ? +1 : -1 ) ));
    CGAffineTransform rightWobble = CGAffineTransformMakeRotation(degreesToRadians( kAnimationRotateDeg * (count%2 ? -1 : +1 ) ));
    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];
}

- (void)stopJiggling {
    [self.layer removeAllAnimations];
    self.transform = CGAffineTransformIdentity;  // Set it straight 
}

答案 2 :(得分:6)

为了完整性,以下是我使用显式动画为我的CALayer子类创作动画的方法 - 受其他答案的启发。

-(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"];
}

答案 3 :(得分:4)

@mientus Swift 4中的原始Apple Jiggle代码,带有可选参数来调整持续时间(即速度),位移(即位置变化)和度数(即旋转量)。

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)
}

答案 4 :(得分:3)

查看openspringboard project

特别是,setIconAnimation:(BOOL)是OpenSpringBoard.m中的动画。这应该会给你一些关于如何做到这一点的想法。

答案 5 :(得分:3)

为了将来出现的其他人的利益,我觉得@Vic320提供的微笑有点过于机械化,并且与主题演讲相比,它有点太强大而且不是有机的(随机?)。所以本着共享的精神,这里是我在UIView的子类中构建的代码...我的视图控制器保存了这些对象的数组,当用户点击编辑按钮时,视图控制器将startJiggling消息发送给每个,然后当用户按下完成按钮时,通过stopJiggling消息。

- (void)startJiggling
{
    // jiggling code based off the folks on stackoverflow.com:
    // http://stackoverflow.com/questions/6604356/ios-icon-jiggle-algorithm

#define degreesToRadians(x) (M_PI * (x) / 180.0)
#define kAnimationRotateDeg 0.1
    jiggling = YES;
    [self wobbleLeft];
}

- (void)wobbleLeft
{
    if (jiggling) {
        NSInteger randomInt = arc4random()%500;
        float r = (randomInt/500.0)+0.5;

        CGAffineTransform leftWobble = CGAffineTransformMakeRotation(degreesToRadians( (kAnimationRotateDeg * -1.0) - r ));
        CGAffineTransform rightWobble = CGAffineTransformMakeRotation(degreesToRadians( kAnimationRotateDeg + r ));

        self.transform = leftWobble;  // starting point

        [UIView animateWithDuration:0.1
                      delay:0
                    options:UIViewAnimationOptionAllowUserInteraction
                 animations:^{ self.transform = rightWobble; }
                 completion:^(BOOL finished) { [self wobbleRight]; }
          ];
    }
}

- (void)wobbleRight
{
    if (jiggling) {

        NSInteger randomInt = arc4random()%500;
        float r = (randomInt/500.0)+0.5;

        CGAffineTransform leftWobble = CGAffineTransformMakeRotation(degreesToRadians( (kAnimationRotateDeg * -1.0) - r ));
        CGAffineTransform rightWobble = CGAffineTransformMakeRotation(degreesToRadians( kAnimationRotateDeg + r ));

        self.transform = rightWobble;  // starting point

        [UIView animateWithDuration:0.1
                      delay:0
                    options:UIViewAnimationOptionAllowUserInteraction
                 animations:^{ self.transform = leftWobble; }
                 completion:^(BOOL finished) { [self wobbleLeft]; }
         ];
    }

}
- (void)stopJiggling
{
    jiggling = NO;
    [self.layer removeAllAnimations];
    [self setTransform:CGAffineTransformIdentity];
    [self.layer setAnchorPoint:CGPointMake(0.5, 0.5)];
}

答案 6 :(得分:3)

我对Apple Stringboard进行了逆向设计,并修改了一点动画,下面的代码非常好。

+ (CAAnimationGroup *)jiggleAnimation {
CAKeyframeAnimation *position = [CAKeyframeAnimation animation];
position.keyPath = @"position";
position.values = @[
                    [NSValue valueWithCGPoint:CGPointZero],
                    [NSValue valueWithCGPoint:CGPointMake(-1, 0)],
                    [NSValue valueWithCGPoint:CGPointMake(1, 0)],
                    [NSValue valueWithCGPoint:CGPointMake(-1, 1)],
                    [NSValue valueWithCGPoint:CGPointMake(1, -1)],
                    [NSValue valueWithCGPoint:CGPointZero]
                    ];
position.timingFunctions = @[
                             [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                             [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
                             ];
position.additive = YES;

CAKeyframeAnimation *rotation = [CAKeyframeAnimation animation];
rotation.keyPath = @"transform.rotation";
rotation.values = @[
                    @0,
                    @0.03,
                    @0,
                    [NSNumber numberWithFloat:-0.02]
                    ];
rotation.timingFunctions = @[
                             [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut],
                             [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
                             ];

CAAnimationGroup *group = [[CAAnimationGroup alloc] init];
group.animations = @[ position, rotation ];
group.duration = 0.3;
group.repeatCount = HUGE_VALF;
group.beginTime = arc4random() % 30 / 100.f;
return group;
}

原创Apple摇晃动画:

CAKeyframeAnimation *position = [CAKeyframeAnimation animation];
position.beginTime = 0.8;
position.duration = 0.25;
position.values = @[[NSValue valueWithCGPoint:CGPointMake(-1, -1)],
                    [NSValue valueWithCGPoint:CGPointMake(0, 0)],
                    [NSValue valueWithCGPoint:CGPointMake(-1, 0)],
                    [NSValue valueWithCGPoint:CGPointMake(0, -1)],
                    [NSValue valueWithCGPoint:CGPointMake(-1, -1)]];
position.calculationMode = @"linear";
position.removedOnCompletion = NO;
position.repeatCount = CGFLOAT_MAX;
position.beginTime = arc4random() % 25 / 100.f;
position.additive = YES;
position.keyPath = @"position";

CAKeyframeAnimation *transform = [CAKeyframeAnimation animation];
transform.beginTime = 2.6;
transform.duration = 0.25;
transform.valueFunction = [CAValueFunction functionWithName:kCAValueFunctionRotateZ];
transform.values = @[@(-0.03525565),@(0.03525565),@(-0.03525565)];
transform.calculationMode = @"linear";
transform.removedOnCompletion = NO;
transform.repeatCount = CGFLOAT_MAX;
transform.additive = YES;
transform.beginTime = arc4random() % 25 / 100.f;
transform.keyPath = @"transform";

[self.dupa.layer addAnimation:position forKey:nil];
[self.dupa.layer addAnimation:transform forKey:nil];

答案 7 :(得分:2)

所以我确信我会因为编写凌乱的代码而大吼大叫(有可能是更简单的方法,我不知道因为我是半初学者),但这是一个更随机的版本Vic320的算法可以改变旋转和平移的数量。它还会随机决定它首先会摆动哪个方向,如果你有多个东西同时摆动,这会给出更随机的外观。如果效率对您来说是个大问题,请不要使用。这就是我想知道如何做到的方式。

如果有人想知道您需要#import <QuartzCore/QuartzCore.h>并将其添加到您的链接库中。

#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 
}

答案 8 :(得分:2)

Paul Popiel为此提供了一个很好的答案,对此我深表歉意。我发现他的代码有一个小问题,那就是,如果多次调用该例程,它将无法正常工作-有时会丢失或停用图层动画。

为什么多次调用它?我正在通过UICollectionView实现它,并且当单元出列或移动时,我需要重新建立摆动。使用Paul的原始代码,即使我尝试在出队和willDisplay回调中重新建立摆动,但如果它们离开屏幕滚动,我的单元常常会停止摆动。但是,通过给两个动画命名为key,即使在一个单元格上调用了两次,它也始终可以可靠地工作。

这几乎是所有带有上述小补丁的Paul的代码,此外,我已将其创建为UIView的扩展,并添加了与Swift 4兼容的stopWiggle。

private func degreesToRadians(_ x: CGFloat) -> CGFloat {
    return .pi * x / 180.0
}

extension UIView {
    func startWiggle() {
        let duration: Double = 0.25
        let displacement: CGFloat = 1.0
        let 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: "bounce")
        self.layer.add(transform, forKey: "wiggle")
    }

    func stopWiggle() {
        self.layer.removeAllAnimations()
        self.transform = .identity
    }
}

如果它节省了其他人在UICollectionView中实现此功能的时间,则需要在其他地方确保摆动在移动和滚动时保持不变。首先,一个例程开始摆动所有被调用的单元格:

func wiggleAllVisibleCells() {
    if let visible = collectionView?.indexPathsForVisibleItems {
        for ip in visible {
            if let cell = collectionView!.cellForItem(at: ip) {
                cell.startWiggle()
            }
        }
    }
}

随着新单元格的显示(通过移动或滚动),我重新建立了摆动:

override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    // Make sure cells are all still wiggling
    if isReordering {
        cell.startWiggle()
    }
}

答案 9 :(得分:0)

这里是@mientus'代码的 Swift 4.2 版本(其本身是Paul Popiel's version的更新),是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)
    }
}

用法(其中anyLayerCALayer

// Start animating.
anyLayer.startWiggling()
// Stop animating.
anyLayer.stopWiggling()

答案 10 :(得分:0)

万一有人需要在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
    })
}
}

只需致电

    Animation.wiggle(viewToBeAnimated)

总是最好在要调用的函数上编写包装器,以便即使您必须更改函数参数或可能是函数的名称,也不必花很多时间在代码中重写它。