最终效果:
AnimatedNavigationController.h
// // AnimatedNavigationController.h // 20_帅哥no微博 // // Created by beyond on 14-8-10. // Copyright (c) 2014年 com.beyond. All rights reserved. // 继承自导航控制器,但是多了一个功能,可以监听手势,进行动画切换 #import <UIKit/UIKit.h> @interface AnimatedNavigationController : UINavigationController @end
AnimatedNavigationController.m
// // AnimatedNavigationController.m // 20_帅哥no微博 // // Created by beyond on 14-8-10. // Copyright (c) 2014年 com.beyond. All rights reserved. // #import "AnimatedNavigationController.h" // 截图用到 #import <QuartzCore/QuartzCore.h> #import "BeyondViewController.h" @interface AnimatedNavigationController () { // 屏幕截图 UIImageView *_screenshotImgView; // 截图上面的黑色半透明遮罩 UIView *_coverView; // 存放所有截图 NSMutableArray *_screenshotImgs; } @end @implementation AnimatedNavigationController - (void)viewDidLoad { [super viewDidLoad]; // 1,创建Pan手势识别器,并绑定监听方法 UIPanGestureRecognizer *panGestureRec = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panGestureRec:)]; // 为导航控制器的view添加Pan手势识别器 [self.view addGestureRecognizer:panGestureRec]; // 2.创建截图的ImageView _screenshotImgView = [[UIImageView alloc] init]; // app的frame是除去了状态栏高度的frame _screenshotImgView.frame = [UIScreen mainScreen].applicationFrame; //(0 20; 320 460); // 3.创建截图上面的黑色半透明遮罩 _coverView = [[UIView alloc] init]; // 遮罩的frame就是截图的frame _coverView.frame = _screenshotImgView.frame; // 遮罩为黑色 _coverView.backgroundColor = [UIColor blackColor]; // 4.存放所有的截图数组初始化 _screenshotImgs = [NSMutableArray array]; } // 重写push方法,在push之前 先截取图片 - (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated { // 只有在导航控制器里面有子控制器的时候才需要截图 if (self.viewControllers.count >= 1) { // 调用自定义方法,使用上下文截图 [self screenShot]; } // 截图完毕之后,才调用父类的push方法 [super pushViewController:viewController animated:YES]; } // 使用上下文截图,并使用指定的区域裁剪,模板代码 - (void)screenShot { // 将要被截图的view,即窗口的根控制器的view(必须不含状态栏,默认ios7中控制器是包含了状态栏的) BeyondViewController *beyondVC = (BeyondViewController *)self.view.window.rootViewController; // 背景图片 总的大小 CGSize size = beyondVC.view.frame.size; // 开启上下文,使用参数之后,截出来的是原图(YES 0.0 质量高) UIGraphicsBeginImageContextWithOptions(size, YES, 0.0); // 要裁剪的矩形范围 CGRect rect = CGRectMake(0, -20.8, size.width, size.height + 20 ); //注:iOS7以后renderInContext:由drawViewHierarchyInRect:afterScreenUpdates:替代 [beyondVC.view drawViewHierarchyInRect:rect afterScreenUpdates:NO]; // 从上下文中,取出UIImage UIImage *snapshot = UIGraphicsGetImageFromCurrentImageContext(); // 添加截取好的图片到图片数组 [_screenshotImgs addObject:snapshot]; // 千万记得,结束上下文(移除栈顶的基于当前位图的图形上下文) UIGraphicsEndImageContext(); } // 监听手势的方法,只要是有手势就会执行 - (void)panGestureRec:(UIPanGestureRecognizer *)panGestureRec { // 如果当前显示的控制器已经是根控制器了,不需要做任何切换动画,直接返回 if(self.topViewController == self.viewControllers[0]) return; // 判断pan手势的各个阶段 switch (panGestureRec.state) { case UIGestureRecognizerStateBegan: // 开始拖拽阶段 [self dragBegin]; break; case UIGestureRecognizerStateEnded: // 结束拖拽阶段 [self dragEnd]; break; default: // 正在拖拽阶段 [self dragging:panGestureRec]; break; } } #pragma mark 开始拖动,添加图片和遮罩 - (void)dragBegin { // 重点,每次开始Pan手势时,都要添加截图imageview 和 遮盖cover到window中 [self.view.window insertSubview:_screenshotImgView atIndex:0]; [self.view.window insertSubview:_coverView aboveSubview:_screenshotImgView]; // 并且,让imgView显示截图数组中的最后(最新)一张截图 _screenshotImgView.image = [_screenshotImgs lastObject]; } // 默认的将要进行缩放的截图的初始比例 #define kDefaultScale 0.6 // 默认的将要变透明的遮罩的初始透明度(全黑) #define kDefaultAlpha 1.0 // 当拖动的距离,占了屏幕的总宽高的3/4时, 就让imageview完全显示,遮盖完全消失 #define kTargetTranslateScale 0.75 #pragma mark 正在拖动,动画效果的精髓,进行缩放和透明度变化 - (void)dragging:(UIPanGestureRecognizer *)pan { // 得到手指拖动的位移 CGFloat offsetX = [pan translationInView:self.view].x; // 只允许往右边拖,禁止向左拖 if (offsetX < 0) offsetX = 0; // 让整个view都平移 // 挪动整个导航view self.view.transform = CGAffineTransformMakeTranslation(offsetX, 0); // 计算目前手指拖动位移占屏幕总的宽高的比例,当这个比例达到3/4时, 就让imageview完全显示,遮盖完全消失 double currentTranslateScaleX = offsetX/self.view.frame.size.width; // 让imageview缩放,默认的比例+(当前平衡比例/目标平衡比例)*(1-默认的比例) double scale = kDefaultScale + (currentTranslateScaleX/kTargetTranslateScale) * (1 - kDefaultScale); // 已经达到原始大小了,就可以了,不用放得更大了 if (scale > 1) scale = 1; _screenshotImgView.transform = CGAffineTransformMakeScale(scale, scale); // 让遮盖透明度改变,直到减为0,让遮罩完全透明,默认的比例-(当前平衡比例/目标平衡比例)*默认的比例 double alpha = kDefaultAlpha - (currentTranslateScaleX/kTargetTranslateScale) * kDefaultAlpha; _coverView.alpha = alpha; } #pragma mark 结束拖动,判断结束时拖动的距离作相应的处理,并将图片和遮罩从父控件上移除 - (void)dragEnd { // 取出挪动的距离 CGFloat translateX = self.view.transform.tx; // 取出宽度 CGFloat width = self.view.frame.size.width; if (translateX <= width * 0.5) { // 如果手指移动的距离还不到屏幕的一半,往左边挪 (弹回) [UIView animateWithDuration:0.3 animations:^{ // 重要~~让被右移的view弹回归位,只要清空transform即可办到 self.view.transform = CGAffineTransformIdentity; // 让imageView大小恢复默认的scale _screenshotImgView.transform = CGAffineTransformMakeScale(kDefaultScale, kDefaultScale); // 让遮盖的透明度恢复默认的alpha 1.0 _coverView.alpha = kDefaultAlpha; } completion:^(BOOL finished) { // 重要,动画完成之后,每次都要记得 移除两个view,下次开始拖动时,再添加进来 [_screenshotImgView removeFromSuperview]; [_coverView removeFromSuperview]; }]; } else { // 如果手指移动的距离还超过了屏幕的一半,往右边挪 [UIView animateWithDuration:0.3 animations:^{ // 让被右移的view完全挪到屏幕的最右边,结束之后,还要记得清空view的transform self.view.transform = CGAffineTransformMakeTranslation(width, 0); // 让imageView缩放置为1 _screenshotImgView.transform = CGAffineTransformMakeScale(1, 1); // 让遮盖alpha变为0,变得完全透明 _coverView.alpha = 0; } completion:^(BOOL finished) { // 重要~~让被右移的view完全挪到屏幕的最右边,结束之后,还要记得清空view的transform,不然下次再次开始drag时会出问题,因为view的transform没有归零 self.view.transform = CGAffineTransformIdentity; // 移除两个view,下次开始拖动时,再加回来 [_screenshotImgView removeFromSuperview]; [_coverView removeFromSuperview]; // 执行正常的Pop操作:移除栈顶控制器,让真正的前一个控制器成为导航控制器的栈顶控制器 [self popViewControllerAnimated:NO]; // 重要~记得这时候,可以移除截图数组里面最后一张没用的截图了 [_screenshotImgs removeLastObject]; }]; } } @end
iOS_20_微博自定义可动画切换的导航控制器
时间: 2024-10-11 18:27:40