點(diǎn)此下載源碼下載:源碼(會(huì)持續(xù)更新,歡迎star。保證炫酷,童叟無欺?。?/p>

從dribbble的設(shè)計(jì)師的作品中了解到City Guides上非常優(yōu)秀的動(dòng)畫效果。感嘆設(shè)計(jì)師很棒的設(shè)計(jì)的同時(shí),小編也在心里默想這些動(dòng)畫是怎么實(shí)現(xiàn)的。于是從App Store上下載了APP,使用然后仔細(xì)研究后便有了此篇文章。
City Guides中幾乎所有的界面展示與交互方式都是有動(dòng)畫效果的。小編也就分幾部分來實(shí)現(xiàn)動(dòng)畫效果。
這一篇要實(shí)現(xiàn)的動(dòng)畫效果如下:
第一個(gè)動(dòng)畫效果:

第二個(gè)動(dòng)畫效果:

本篇文章只講解實(shí)現(xiàn)思路,具體的可以參見源碼。
動(dòng)畫效果一
第一個(gè)動(dòng)畫效果,實(shí)際上包括三個(gè)部分動(dòng)畫效果。選中動(dòng)畫效果、動(dòng)畫轉(zhuǎn)場(chǎng)和切換動(dòng)畫效果。本篇文章中所有的動(dòng)畫,使用POP和Core Animation實(shí)現(xiàn)。
選中動(dòng)畫效果
此動(dòng)畫實(shí)現(xiàn)相對(duì)簡(jiǎn)單一些,中間部分是使用的UICollectionView,創(chuàng)建了四個(gè)cell。在可視的cell中實(shí)現(xiàn)被選中的cell放大,其余可視的cell縮放的同時(shí)透明度減少。使用POP實(shí)現(xiàn)的方法如下:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *selectedCell = (UICollectionViewCell *)[collectionView cellForItemAtIndexPath:indexPath];
for (UICollectionViewCell *cell in collectionView.visibleCells) {
if ([cell isEqual:selectedCell]) {
//選中的cell的放大
POPBasicAnimation *scaleAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
scaleAnimation.duration = 0.5;
scaleAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(1.05, 1.05)];
[cell.layer pop_addAnimation:scaleAnimation forKey:@"scaleAnimation"];
POPBasicAnimation *alphaAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewAlpha];
alphaAnimation.duration = 0.5;
alphaAnimation.toValue = @1.0;
[cell pop_addAnimation:alphaAnimation forKey:nil];
[alphaAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) {
// if (finished) {
//
// ZFMainTabBarController *mainTabBarVC = [[ZFMainTabBarController alloc] init];
// mainTabBarVC.modalPresentationStyle = UIModalPresentationOverCurrentContext;
// mainTabBarVC.transitioningDelegate = self;
//
// self.interactionViewController = [ZFInteractiveTransition new];
// [self presentViewController:mainTabBarVC animated:YES completion:NULL];
// }
}];
}
else{
//未選中的可視cell的縮放
POPBasicAnimation *scaleAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
scaleAnimation.duration = 0.5;
scaleAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(0.95, 0.95)];
[cell.layer pop_addAnimation:scaleAnimation forKey:@"scaleAnimation"];
POPBasicAnimation *alphaAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewAlpha];
alphaAnimation.duration = 0.5;
alphaAnimation.toValue = @0.7;
[cell pop_addAnimation:alphaAnimation forKey:nil];
}
}
}
此部分實(shí)現(xiàn)的效果圖:

動(dòng)畫轉(zhuǎn)場(chǎng)
此部分的轉(zhuǎn)場(chǎng)過渡動(dòng)畫效果,參考小編上篇文章講解的自定義轉(zhuǎn)場(chǎng)。里面涉及到手勢(shì)交互,下一篇文章再做詳細(xì)講解。
切換效果動(dòng)畫
實(shí)際上是點(diǎn)擊最上面的兩個(gè)button后,對(duì)應(yīng)的視圖動(dòng)畫效果。
切換到SLIDES后,UICollectionView整個(gè)視圖向左移,移出當(dāng)前屏幕。與此同時(shí)可視的UICollectionViewCell隨著變化,而且每個(gè)cell的變化有區(qū)別。區(qū)別是:移出的時(shí)候,第二個(gè)cell最后消失在視線里(偏移量最?。谒膫€(gè)cell旋轉(zhuǎn)幅度較大(角度最大)。
實(shí)現(xiàn)的方式:
-(void)showSlideLayout:(UIButton *)sender{
if (_SliderButton.selected) {
return;
}
[_scrollView setHidden:NO];
_SliderButton.selected = YES;
_GridButton.selected = NO;
_SliderButton.userInteractionEnabled = NO;
[self canEnableClick];
_backgroundLayer.frame = sender.frame;
int i = 0;
for (UICollectionViewCell *cell in _cityCollectView.visibleCells) {
NSArray *translationArray = @[@-300, @-200, @-600, @-600];
NSArray *angles = @[@(-15* M_PI/180), @(-30* M_PI/180), @(-15* M_PI/180), @(-60* M_PI/180)];
NSArray *scaleArray = @[@0, @0, @0, @-5];
//未選中的可視cell的縮放
POPBasicAnimation *rotationAnimation = [POPBasicAnimation easeInEaseOutAnimation];
rotationAnimation.property = [POPAnimatableProperty propertyWithName:kPOPLayerRotation];
rotationAnimation.duration = duration;
rotationAnimation.fromValue = @(0);
rotationAnimation.toValue = angles[i];
POPBasicAnimation *translationAnimation = [POPBasicAnimation easeInEaseOutAnimation];
translationAnimation.property = [POPAnimatableProperty propertyWithName:kPOPLayerTranslationX];
translationAnimation.duration = duration;
translationAnimation.fromValue = @(0);
translationAnimation.toValue = translationArray[i];
POPBasicAnimation *zPositionAnimation = [POPBasicAnimation easeInEaseOutAnimation];
zPositionAnimation.property = [POPAnimatableProperty propertyWithName:kPOPLayerZPosition];
zPositionAnimation.duration = duration;
zPositionAnimation.toValue = scaleArray[i];
[cell.layer pop_addAnimation:rotationAnimation forKey:@"rotationAnimation"];
[cell.layer pop_addAnimation:translationAnimation forKey:@"translationAnimation"];
[cell.layer pop_addAnimation:zPositionAnimation forKey:@"zPositionAnimation"];
i++;
}
// collectionView 移動(dòng)
CGRect fromFrame = _cityCollectView.frame;
CGRect toFrome = fromFrame;
fromFrame.origin.x -= fromFrame.size.width;
_cityCollectView.frame = fromFrame;
POPBasicAnimation *frameAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewFrame];
frameAnimation.name = @"showSliderView";
frameAnimation.duration = duration;
frameAnimation.fromValue = [NSValue valueWithCGRect:toFrome];
frameAnimation.toValue = [NSValue valueWithCGRect:fromFrame];
frameAnimation.delegate = self;
[_cityCollectView pop_addAnimation:frameAnimation forKey:@"frameAnimation"];
[frameAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) {
if (finished) {
[_cityCollectView setHidden:YES];
}
}];
}
切換到GRID后,UICollectionView整個(gè)視圖向右移,漸入到當(dāng)前屏幕。UICollectionViewCell變化與上面有些不一樣。第3個(gè)cell 和第4個(gè)cell最后完成動(dòng)畫,其動(dòng)畫變化較大。
-(void)showGridLayout:(UIButton *)sender{
if (_GridButton.selected) {
return;
}
[_cityCollectView setHidden:NO];
_backgroundLayer.frame = sender.frame;
_SliderButton.selected = NO;
_GridButton.selected = YES;
_GridButton.userInteractionEnabled = NO;
[self canEnableClick];
int i = 0;
for (UICollectionViewCell *cell in _cityCollectView.visibleCells) {
NSArray *translationArray = @[@-5, @0, @-300, @-100];
NSArray *angles = @[@(-2* M_PI/180), @(-5* M_PI/180), @(-30* M_PI/180), @(-10* M_PI/180)];
//未選中的可視cell的縮放
POPBasicAnimation *rotationAnimation = [POPBasicAnimation easeInEaseOutAnimation];
rotationAnimation.property = [POPAnimatableProperty propertyWithName:kPOPLayerRotation];
rotationAnimation.duration = duration;
rotationAnimation.fromValue = angles[i];
rotationAnimation.toValue = @(0);
POPBasicAnimation *translationAnimation = [POPBasicAnimation easeInEaseOutAnimation];
translationAnimation.property = [POPAnimatableProperty propertyWithName:kPOPLayerTranslationX];
translationAnimation.duration = duration;
translationAnimation.fromValue = translationArray[i];
translationAnimation.toValue = @(0);
[cell.layer pop_addAnimation:rotationAnimation forKey:@"rotationAnimation"];
[cell.layer pop_addAnimation:translationAnimation forKey:@"translationAnimation"];
i++;
}
// collectionView 移動(dòng)
CGRect fromFrame = _cityCollectView.frame;
CGRect toFrome = fromFrame;
toFrome.origin.x = 0;
POPBasicAnimation *frameAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewFrame];
frameAnimation.name = @"showGridView";
frameAnimation.duration = duration;
frameAnimation.fromValue = [NSValue valueWithCGRect:fromFrame];
frameAnimation.toValue = [NSValue valueWithCGRect:toFrome];
frameAnimation.delegate = self;
[_cityCollectView pop_addAnimation:frameAnimation forKey:@"frameAnimation"];
[frameAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) {
if (finished) {
[_scrollView setHidden:YES];
}
}];
}
以上兩個(gè)動(dòng)畫完成的過程中,仔細(xì)觀察SLIDES下對(duì)應(yīng)的視圖,UIScrollView有一些細(xì)微的變化。在Y方向上有一定的偏移量的變化。是根據(jù)UICollectionView移出的位置變化而改變。使用POPAnimationDelegate的方法,根據(jù)當(dāng)前執(zhí)行動(dòng)畫決定在Y方向偏移量是逐步增加或減少。
- (void)pop_animationDidApply:(POPAnimation *)anim
{
// NSLog(@"%@",anim.name );
CGRect currentValue = [[anim valueForKey:@"currentValue"] CGRectValue];
if ([anim.name isEqualToString:@"showSliderView"]) {
CGRect frame = _scrollView.frame;
CGFloat distanceY = 30 *(1 -fabs(currentValue.origin.x /_scrollView.frame.size.width));
frame.origin.x = _scrollView.frame.size.width + currentValue.origin.x;
frame.origin.y = CGRectGetMaxY(_SliderButton.frame) + 20. + distanceY ;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.0];
_scrollView.frame = frame;
[UIView commitAnimations];
}
else if([anim.name isEqualToString:@"showGridView"]){
CGRect frame = _scrollView.frame;
CGFloat distanceY = 30 * (1 - fabs(currentValue.origin.x /_scrollView.frame.size.width));
frame.origin.x = _scrollView.frame.size.width + currentValue.origin.x;
frame.origin.y = CGRectGetMaxY(_SliderButton.frame) + 20. + distanceY ;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.0];
_scrollView.frame = frame;
[UIView commitAnimations];
}
}
具體效果參見下圖:

動(dòng)畫效果二
此部分動(dòng)畫是使用UIScrollView來實(shí)現(xiàn),同樣可以使用UICollectionView來實(shí)現(xiàn)。此處是UICollectionView實(shí)現(xiàn)。本篇文章使用繼承UIScrollView的類來實(shí)現(xiàn)。只需要以下幾行代碼即可。是根據(jù)UIScrollView的子視圖偏移量的變化實(shí)現(xiàn)。參考以下代碼
#import "ZFTransformScrollView.h"
@implementation ZFTransformScrollView
-(void)layoutSubviews{
[super layoutSubviews];
CGFloat contentOffsetX = self.contentOffset.x;
for(UIView *view in self.subviews){
CATransform3D t1 = CATransform3DIdentity;
view.layer.transform = t1;
//計(jì)算每個(gè)cell的偏移量
CGFloat distanceFromCenterX = view.frame.origin.x - contentOffsetX;
CGFloat offsetRatio = distanceFromCenterX / CGRectGetWidth(self.frame);
CGFloat angle = offsetRatio * 30;
//前半部分
if (offsetRatio < 0) {
//offsetRation 為負(fù)
//沿y軸向右轉(zhuǎn) 沿著y的正方向看是逆時(shí)針
CATransform3D t2 = CATransform3DMakeRotation(DEGREES_TO_RADIANS(-angle), 0, 1,0 );
//沿x軸向里轉(zhuǎn) 沿著x的正方向看是逆時(shí)針
CATransform3D t3 = CATransform3DRotate(t2, DEGREES_TO_RADIANS(-5 * offsetRatio) , 1, 0, 0);
//沿y軸向下移動(dòng) 距離正
CATransform3D t4 = CATransform3DTranslate(t3, 0, -35 *offsetRatio, 0);
//實(shí)現(xiàn)透視投影 默認(rèn)是正交投影 以CGPointMake(0, 0)為觀察點(diǎn) 3D效果更明顯
view.layer.transform = CATransform3DPerspect(t4, CGPointMake(0, 0), 500);
}
else{
//給個(gè)起始位置 在正常位置以下以左的某段距離
t1 = CATransform3DMakeTranslation(60 * offsetRatio * 1.5, 100 * offsetRatio * 1.5, 0);
//沿y軸向左轉(zhuǎn) 沿著y的正方向看是順時(shí)針
CATransform3D t2 = CATransform3DRotate(t1,DEGREES_TO_RADIANS(-angle), 0, 1,0 );
//沿x軸向里轉(zhuǎn) 沿著x的正方向看是逆時(shí)針
CATransform3D t3 = CATransform3DRotate(t2, DEGREES_TO_RADIANS(5 * offsetRatio), 1, 0, 0);
//沿y軸向上移動(dòng) 距離正
CATransform3D t4 = CATransform3DTranslate(t3, 0, -35 *offsetRatio, 0);
//實(shí)現(xiàn)透視投影 默認(rèn)是正交投影 以CGPointMake(0, 0)為觀察點(diǎn) 3D效果更明顯
view.layer.transform = CATransform3DPerspect(t4, CGPointMake(0, 0), 500);
}
}
}
@end
其中CATransform3DPerspect實(shí)現(xiàn)方法和使用原理可以參考此篇文章iOS 3D UI---CALayer的transform擴(kuò)展
CATransform3D CATransform3DMakePerspective(CGPoint center, float disZ)
{
CATransform3D transToCenter = CATransform3DMakeTranslation(-center.x, -center.y, 0);
CATransform3D transBack = CATransform3DMakeTranslation(center.x, center.y, 0);
CATransform3D scale = CATransform3DIdentity;
scale.m34 = -1.0f/disZ;
return CATransform3DConcat(CATransform3DConcat(transToCenter, scale), transBack);
}
CATransform3D CATransform3DPerspect(CATransform3D t, CGPoint center, float disZ)
{
return CATransform3DConcat(t, CATransform3DMakePerspective(center, disZ));
}
后續(xù)實(shí)現(xiàn)內(nèi)容,請(qǐng)持續(xù)關(guān)注小編。

點(diǎn)此下載源碼下載:源碼(會(huì)持續(xù)更新,歡迎star)
擴(kuò)展閱讀
干貨系列之實(shí)現(xiàn)City Guides的動(dòng)畫效果(二)