IOS新版iBooks吸引人的地方除了有干净整洁的界面、方便灵活的操作以及大容量的书籍容量以外,还有其优秀的用户交互,尤其是其动画的使用。打开一本书时书本缓慢放大并打开,关闭一本书后书本关闭并回到原位置。现在我们来实现这个简单的功能。
效果图:
用到的知识:
1、CAKeyframeAnimation的应用
2、如何在代理中区分两个不同的动画
3、坐标系转换
思路:
这个动画主要用到的是CAAnimation,并且是CAKeyframeAnimation,当用户点击书本时,设置一个UIImageView(为其加上tag方便以后取)并将其放在选中书本的位置上(使用坐标系转换),接着通过动画将其放大到全屏,完成后将其锚点设置为(0,0.5)并让其绕y轴选中π/2角度,将早已放在下面的textView(本app中是自定义的readView)展示出来。动画完成后将UIImageView的透明度设为0。这样就是书本的打开过程。关闭过程类似,根据tag取出UIImageView并将其旋转,然后设置frame到原来书本的位置(可以用私有变量记录该位置),最后removeFromSuperview即可。
代码:
首先是“准备阶段”
//BookReadView是展示textView和顶部底部附加view的自定义view CYZBookItemView *itemView = [notification object]; CYZBookReadView *readView = [[CYZBookReadView alloc] initWithFrame:CGRectMake(0, 0, self.view.width, self.view.height) ItemView:itemView]; //设置delegate是为了当用户从阅读界面返回时做出响应 readView.delegate = self; //先将其alpha设为0防止用户刚点击书本动画还在进行中时就出现这一界面 readView.alpha = 0.0f; [self.view addSubview:readView]; //展示动画的UIImageView UIImageView *showView = [[UIImageView alloc] initWithImage:itemView.bookImage.currentBackgroundImage]; //坐标系转换 _itemPosition = [itemView.superview convertRect:itemView.frame toView:self.view]; showView.frame = _itemPosition; //设置tag方便以后取出 showView.tag = 1001; showView.backgroundColor = [UIColor clearColor]; [self.view addSubview:showView];
- (void)convertRect:toView:方法:
格式[被转换者所在的View convertRect: 被转换者的frame或bounds toView:转换到的View];返回在目标View中的rect
例如,将当前视图的子视图viewA中的一个imageView转换到当前视图。可以如下调用:
[viewA convertRect:viewA.imageView.frame toRect:self.view];
或者[imageView.superView convertRect:imageView.frame toRect:self.view];
其中self指控制器。
- (void)convertRect:fromView:方法:
格式[要转换到的View convertRect: 被转换者的frame或bounds fromView:被转换者所在的View];返回在目标View中的rect。
还是上一个例子,可以这样写:
[self.view convertRect:viewA.imageView.frame fromView:viewA];
或者[self.view convertRect:imageView.frame fromView:imageView.superView];
其中self指控制器
接着准备工作完成了以后就可以开始动画了。一下是打开动画的部分
[UIView animateWithDuration:0.5f animations:^{ //将UIImageView放大为全屏 showView.frame = self.view.bounds; } completion:^(BOOL finished) { if (finished) { //展示出readView readView.alpha = 1.0f; //设置锚点 showView.layer.anchorPoint = CGPointMake(0, 0.5); #warning 不知道为什么,不加下面这句话showView会显示异常:即只显示一半 showView.frame = CGRectMake(0, 0, showView.width, showView.height); CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; //设置其绕y轴旋转 animation.keyPath = @"transform.rotation.y"; //设置持续时间,可以定义一常量来表示 animation.duration = 0.5f; animation.speed = 0.55f; animation.removedOnCompletion = NO; //旋转π/2度 [animation setValues:[NSArray arrayWithObjects:@0.0f, @-M_PI_2, nil]]; //设置代理便于回调 animation.delegate = self; //这里必须设置上key,为了接下来区分不同动画 [showView.layer addAnimation:animation forKey:@"rotateToOpenBook"]; } }];
回调中:
[showView setAlpha:0.0f];
关上书的动画大致类似
#pragma mark - CYZBookViewDelegate - (void)readViewDidBackWithItem:(CYZBookItemView *)item { //取出showView UIImageView *showView = (UIImageView *)[self.view viewWithTag:1001]; //将其显示出来 showView.alpha = 1.0f; showView.layer.anchorPoint = CGPointMake(0, 0.5f); showView.frame = CGRectMake(0, 0, showView.width, showView.height); CAKeyframeAnimation *animation = [CAKeyframeAnimation animation]; animation.keyPath = @"transform.rotation.y"; animation.duration = 0.5f; animation.speed = 0.55f; animation.removedOnCompletion = NO; //反向旋转 [animation setValues:[NSArray arrayWithObjects:@-M_PI_2, @0, nil]]; animation.delegate = self; //设置不同的key为了区分动画用 [showView.layer addAnimation:animation forKey:@"rotateToCloseBook"]; }
该动画的回调:
[UIView animateWithDuration:0.5f animations:^{ showView.frame = _itemPosition; } completion:^(BOOL finished) { [showView removeFromSuperview]; }];
这里的关键问题在于,两个动画都用到了回调,如何区分呢?就用我们之前设置的key来区分:
回调的完整代码:
#pragma mark - AnimationDelegate - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { UIImageView *showView = (UIImageView *)[self.view viewWithTag:1001]; if (flag) { if (showView && [[showView.layer animationForKey:@"rotateToOpenBook"] isEqual:anim]) { [showView setAlpha:0.0f]; } else if (showView && [anim isEqual:[showView.layer animationForKey:@"rotateToCloseBook"]]) { [UIView animateWithDuration:0.5f animations:^{ showView.frame = _itemPosition; } completion:^(BOOL finished) { [showView removeFromSuperview]; }]; } } }通过showView所在层layer的方法 animationForKey:获得自己设置的动画,与代理中的anim做比较即可区分。
另外,在stackOverflow上还看到一种方法:用kvc的方法为animation设值,并在代理中通过kvc的方法根据这个键取出值并进行判断,应该也是可以的。