iOS图标微动算法

Vic*_*320 20 iphone core-animation uiview ipad ios

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

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

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

imn*_*mnk 32


@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;
}
Run Code Online (Sandbox Code Playgroud)


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


Vic*_*320 11

好的,所以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 
}
Run Code Online (Sandbox Code Playgroud)


Pau*_*iel 6

@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)
}
Run Code Online (Sandbox Code Playgroud)


Dre*_*ack 5

为了完整起见,这里是我如何使用显式动画为我的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"];
}
Run Code Online (Sandbox Code Playgroud)


小智 5

Paul Popiel在上面给出了一个很好的答案,我永远感激他。我在他的代码中发现了一个小问题,那就是如果多次调用该例程,它就不能很好地工作 - 图层动画有时会丢失或停用。

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

这几乎是 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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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