我正在编写一个iPad应用程序,类似于Pages展示用户文档的方式(以实际文档的大图标形式呈现)。我还想模仿用户点击编辑按钮时产生的晃动行为。这与iPhone和iPad上在主屏幕上长按它们时图标所做的同样摇晃模式相同。
我已经在互联网上搜索了一些算法,但它们只会使视图来回晃动,或者根本不像Apple的晃动。每个图标都会稍微有所不同,似乎有一些随机性在其中。
是否有人拥有或知道可以重新创建相同摇晃模式(或非常接近)的代码?谢谢!!!
我正在编写一个iPad应用程序,类似于Pages展示用户文档的方式(以实际文档的大图标形式呈现)。我还想模仿用户点击编辑按钮时产生的晃动行为。这与iPhone和iPad上在主屏幕上长按它们时图标所做的同样摇晃模式相同。
我已经在互联网上搜索了一些算法,但它们只会使视图来回晃动,或者根本不像Apple的晃动。每个图标都会稍微有所不同,似乎有一些随机性在其中。
是否有人拥有或知道可以重新创建相同摇晃模式(或非常接近)的代码?谢谢!!!
@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分。
好的,所以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
}
@mientus 原始的苹果抖动代码使用Swift 4编写,可选参数可调整持续时间(即速度),位移(即位置变化)和角度(即旋转量)。
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)
}
Paul Popiel 在上面给出了一个非常好的答案,我永远感激他。但是,我发现他的代码有一个小问题,如果多次调用该函数,则图层动画可能会丢失或停止。
为什么要调用它多次?我通过 UICollectionView 实现它,当单元格被取消排队或移动时,需要重新建立 "wiggle"。使用 Paul 的原始代码,即使在取消排队和 willDisplay 回调中尝试重新建立 "wiggle",我的单元格经常会在滚动超出屏幕后停止晃动。然而,通过为这两个动画命名关键字,即使在单元格上两次调用,它也总是可靠的。
这几乎是所有 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()
}
}
为了完整起见,这是我如何使用显式动画为我的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"];
}
+ (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;
}
原始的苹果晃动动画:
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];
#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
}
为了让未来的读者受益,我觉得@Vic320提供的晃动效果有点机械化。与Keynote相比,这个效果太强了,不够自然(随机?)。所以在分享精神的基础上,这是我在我的UIView子类中构建的代码...我的视图控制器维护着这些对象的数组,当用户点击编辑按钮时,视图控制器会向每个对象发送startJiggling消息,当用户按下Done按钮时则发送stopJiggling消息。
- (void)startJiggling
{
// jiggling code based off the folks on stackoverflow.com:
// https://dev59.com/22w15IYBdhLWcg3wcrZC
#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)];
}
具体来说,请在OpenSpringBoard.m中设置setIconAnimation:(BOOL)isAnimating。这应该会给你一些实现的思路。
如果有人需要相同的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)
最好的做法是编写一个包装器来调用函数,这样即使您必须更改函数参数或函数名称,也不需要在代码中的每个位置都重新编写它。
options: [.Autoreverse, .Repeat, .AllowUserInteraction]
。 - Murray Sagal