Popping笔记。Github上搜索"Popping"即可下载源代码。
Flat Button
首先我们来分析完成这个效果都有哪些事情发生。
1.屏幕中央有一button,写着"Log in"。
2.点击按钮,左上角会出现networkActicityIndicator,并且navigation上的右面也出现activityIndicator。
3.等待一段时间,且此时button不可点击。
4.短暂时间过后,activityIndicator都消失。同时,button左右摇晃,从button的中央弹出一个label,注意这个label有两个动画效果:一个是从小到大,一个是在竖直方向上移动,且都有一些抖动效果。
5.此时button可以点击了。
6.点击button,label回到button的中央,同样有两个动画效果:从大到小和竖直移动,不过这次不再抖动,都是迅速回去。
7.重复。
分析完毕。我们来看代码:
首先看FlatButton.h(.m)这两个文件。
.h 文件:
只有一个类方法,可以知道是用来创建这个FlatButton的。
.m 文件:
+ (instancetype)button
{
return [self buttonWithType:UIButtonTypeCustom];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
用来创建FlatButton,注意这里返回了一个UIButtonTypeCustom类型的button,如果使用系统默认的button type,那么有些字体之类的是无法修改的(?)。
接下来到setup方法:
- (void)setup
{
self.backgroundColor = self.tintColor;
self.layer.cornerRadius = 4.f;
[self setTitleColor:[UIColor whiteColor]
forState:UIControlStateNormal];
self.titleLabel.font = [UIFont fontWithName:@"Avenir-Medium"
size:22];
[self addTarget:self action:@selector(scaleToSmall)
forControlEvents:UIControlEventTouchDown | UIControlEventTouchDragEnter];
[self addTarget:self action:@selector(scaleAnimation)
forControlEvents:UIControlEventTouchUpInside];
[self addTarget:self action:@selector(scaleToDefault)
forControlEvents:UIControlEventTouchDragExit];
}
设置button的颜色,圆角之类的,注意这里修改了titleLabel的字体和大小,如果在刚才的button方法中返回的不是[self buttonWithType:UIButtonTypeCustom],而是UIBUttonTypeSystem,那么字体和大小修改的效果是不会出现的。
接着我们为button添加事件:
如果鼠标按下去或者按着鼠标进入button的bounds内,则让button缩小。
如果完成一次点击事件(按下去抬起来),则让button进行动画(一次正常-小-正常的抖动,即我们点击button看到的动画效果)。
如果按着鼠标离开,则让button恢复正常大小。
下面是三个触发的方法:
- (void)scaleToSmall
{
POPBasicAnimation *scaleAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
scaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(0.95f, 0.95f)];
[self.layer pop_addAnimation:scaleAnimation forKey:@"layerScaleSmallAnimation"];
}
- (void)scaleAnimation
{
POPSpringAnimation *scaleAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
scaleAnimation.velocity = [NSValue valueWithCGSize:CGSizeMake(3.f, 3.f)];
scaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(1.f, 1.f)];
scaleAnimation.springBounciness = 18.0f;
[self.layer pop_addAnimation:scaleAnimation forKey:@"layerScaleSpringAnimation"];
}
- (void)scaleToDefault
{
POPBasicAnimation *scaleAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
scaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(1.f, 1.f)];
[self.layer pop_addAnimation:scaleAnimation forKey:@"layerScaleDefaultAnimation"];
}
你可能注意到了,开始的时候我们这样做:
#import
我们导入了facebook的开源代码库---POP,它可以让我们做出很多炫酷的动画,非常方便。可以去github上搜索,有兴趣可以多多了解,popping就是基于POP的。
scaleToSmall方法和scaleToDefault方法类似,顾名思义,前者是让button变小,后者让button恢复正常。
创建一个POPBasicAnimation,设置将对kPOPLayerScaleXY进行动画,scaleToSmall中将其x,y方向上都变为原大小的0.95倍,scaleToDefult则让button恢复正常大小。
scaleAnimation中,创建一个POPSpringAnimation,这个就是实现抖动效果的类。设置其velocity,toValue以及springBounciness属性,让其在变为正常大小的时候附有抖动效果。注意,此时velocity属性是这样赋值的:[NSValue valueWithCGSize:CGSizeMake(3.f, 3.f)]。这是因为我们当前设置的是kPOPLayerScaleXY,则需要对x,y方向分别设置值。那么如果我们对kPOPLayerPositionX设置,则可以这样positionAnimation.velocity = @2000;
(后面可以见到)。
现在来看ButtonViewController.m文件
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self addButton];
[self addLabel];
[self addActivityIndicatorView];
}
开始时候我们增加一个button(就是我们的FlatButton),一个label(红色字的),一个activityIndicator(Navigation右面那个)。
#pragma mark - Private Instance methods
- (void)addButton
{
self.button = [FlatButton button];
self.button.backgroundColor = [UIColor customBlueColor];
self.button.translatesAutoresizingMaskIntoConstraints = NO;
[self.button setTitle:@"Log in" forState:UIControlStateNormal];
[self.button addTarget:self action:@selector(touchUpInside:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.button];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.button
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.f
constant:0.f]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.button
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1.f
constant:0.f]];
}
- (void)addLabel
{
self.errorLabel = [UILabel new];
self.errorLabel.font = [UIFont fontWithName:@"Avenir-Light" size:18];
self.errorLabel.textColor = [UIColor customRedColor];
self.errorLabel.translatesAutoresizingMaskIntoConstraints = NO;
self.errorLabel.text = @"Just a serious login error.";
self.errorLabel.textAlignment = NSTextAlignmentCenter;
[self.view insertSubview:self.errorLabel belowSubview:self.button];
[self.view addConstraint:[NSLayoutConstraint
constraintWithItem:self.errorLabel
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.button
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0.f]];
[self.view addConstraint:[NSLayoutConstraint
constraintWithItem:self.errorLabel
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual toItem:self.button
attribute:NSLayoutAttributeCenterY
multiplier:1
constant:0]];
self.errorLabel.layer.transform = CATransform3DMakeScale(0.5f, 0.5f, 1.f);
}
- (void)addActivityIndicatorView
{
self.activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
UIBarButtonItem *item = [[UIBarButtonItem alloc] initWithCustomView:self.activityIndicatorView];
self.navigationItem.rightBarButtonItem = item;
}
addButton方法中,创建了一个button,设置各种属性并监听点击事件,注意将其translatesAutoresizingMaskIntoConstraints = NO,这是因为我们接下来需要addConstraints,所以不用AutoresizingMask。
接下来我们添加了两个constraint:分别是让button中点的x坐标和view中点的x坐标相等,button中点的y坐标和view中点的y坐标相等(让button处于view中点)。
addLabel方法中除了与上面相似的内容外,我们将errorLabel插到button的后面了,这样用户就看不到label。可是实际上label会有多出的部分,button无法将其挡住,所以我们设置了errorLabel.layer.transform = CATransform3DMakeScale(0.5f, 0.5f, 1.f),即将label横纵向都缩小到原来的一半,这样button就可以完全将其挡住。
addActivityIndicatorView没什么好说的,创建一个activityIndicatorView,创建一个UIBarButtonItem内容就是这个view,并将其设置成navigationItem.rightBarButtonItem。
- (void)touchUpInside:(FlatButton *)button
{
[self hideLabel];
[self.activityIndicatorView startAnimating];
button.userInteractionEnabled = NO;
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[self.activityIndicatorView stopAnimating];
[self shakeButton];
[self showLabel];
});
}
点击按钮触发touchUpInside:方法。首先是[self hideLabel],这个在第一次点击的时候没有意义,但是以后每次点击,都会首先将上次弹出的label隐藏。如果不这样,label就会一直在了。我们来看看这个方法都做了什么:
- (void)hideLabel
{
POPBasicAnimation *layerScaleAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
layerScaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(0.5f, 0.5f)];
[self.errorLabel.layer pop_addAnimation:layerScaleAnimation forKey:@"layerScaleAnimation"];
POPBasicAnimation *layerPositionAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerPositionY];
layerPositionAnimation.toValue = @(self.button.layer.position.y);
[self.errorLabel.layer pop_addAnimation:layerPositionAnimation forKey:@"layerPositionAnimation"];
}
很简单,就像我们开始分析的那样,两个动画:缩小并回到button的中心位置(其初始位置),注意这里用的是POPBasicAnimation,即没有任何抖动之类的效果,只是快速的边缩小边回到button的中点。
回到touchUpInside:中,在隐藏Label之后,让navigation上的activityIndicatorView和左上角的都开始动画,并且button不允许点击。此时另起queue,在dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5f * NSEC_PER_SEC)这些时间之后,回到main queue中调整UI。首先关闭activityIndicatorView,接着[self shakeButton]来让button抖动代表有错误,之后弹出label。
- (void)shakeButton
{
POPSpringAnimation *positionAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPositionX];
positionAnimation.velocity = @2000;
positionAnimation.springBounciness = 20;
[positionAnimation setCompletionBlock:^(POPAnimation *animation, BOOL finished) {
self.button.userInteractionEnabled = YES;
}];
[self.button.layer pop_addAnimation:positionAnimation forKey:@"positionAnimation"];
}
- (void)showLabel
{
self.errorLabel.layer.opacity = 1.0;
POPSpringAnimation *layerScaleAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
layerScaleAnimation.springBounciness = 18;
layerScaleAnimation.toValue = [NSValue valueWithCGSize:CGSizeMake(1.f, 1.f)];
[self.errorLabel.layer pop_addAnimation:layerScaleAnimation forKey:@"labelScaleAnimation"];
POPSpringAnimation *layerPositionAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPositionY];
layerPositionAnimation.toValue = @(self.button.layer.position.y + self.button.intrinsicContentSize.height);
layerPositionAnimation.springBounciness = 12;
[self.errorLabel.layer pop_addAnimation:layerPositionAnimation forKey:@"layerPositionAnimation"];
}
shakeButton方法中,创建一个POPSpringAnimation,注意此时要对kPOPLayerPositionX进行动画,所以在设置velocity的时候直接设置@2000(一个值不再是x,y两个方向两个值),在动画完成之后设置button可以点击。注意这里并没有设置toValue的值,如果不设置velocity的值,button是不会抖动的。
showLabel方法和hideLabel相似,不过创建的是POPSpringAnimation,让其弹出时带有抖动效果。
结束。