iOS-仿京东商品详情(半模态视图,背景缩小)
2021-02-02 本文已影响0人
香橙柚子
京东详情页动画是一个半模态视图,简单地说就是背景缩小,再弹出一个页面出来。目前已知的还有小米商城,也采用这种动画效果。
先看效果图:(免费给茅台做一波广告)
京东详情页.gif有两种实现方法
第一种就是将视图View旋转,
第二种就是对当前视图进行截图,然后将截图旋转。
两种方法各有你利弊,页面简单,使用地方少可以直接将试图旋转,页面复杂的话,那就截图吧,原来的页面不处理。
两种方法实现逻辑是一样的,今天我们采用第二种方法:截图。。。
我们先分析一下吧:
- 对当前页面进行截图
- 做一个背景,进行遮挡,此时不处理任何事件
- 进行3D旋转
- 弹出底部弹框
- 点击背景进行还原
- 还原过程(底部弹出框小时,3D旋转,将截图放置最底层隐藏起来)
初始化
我们创建一个UIView的视图来完成这个动画:JJSemiModalView
。
//弹出半屏框
@property (strong, nonatomic) UIView *contentView;
//弹出框背景,点击可复原
@property (strong, nonatomic) UIControl *closeControl;
//缩小视图,背面缩小的是一个图片
@property (strong, nonatomic) UIImageView *maskImageView;
//外部传入,是一个VC
@property (nonatomic, strong) UIViewController *baseViewController;
- (instancetype)initWithSize:(CGSize)size andBaseViewController:(UIViewController *)baseViewController;
这里是将这个动画封装起来了,所以需要传入一个展示控制器。创建需要的属性。
初始化的时候需要将动画视图放最下面,隐藏起来。当做动画的时候再放置最上层。
if (baseViewController) {
[baseViewController.view insertSubview:self atIndex:0];
[baseViewController.view sendSubviewToBack:self];
}
截取当前屏幕页面,用于展示动画。
- (UIImage *)screenSnapshotWindow{
UIView *view = [UIApplication sharedApplication].keyWindow;
UIGraphicsBeginImageContextWithOptions(view.frame.size, NO, 0);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImage * img = [[UIImage alloc] initWithData:UIImageJPEGRepresentation(image, 0.8)];
return img;
}
展示动画:截图后赋值,并将此视图展示到最前面。
CATransform3D t = CATransform3DIdentity;
t.m34 = -0.004;
self.maskImageView.layer.transform = t;
self.maskImageView.layer.zPosition = -10000;
self.maskImageView.image = [self screenSnapshotWindow];
if (self.baseViewController) {
[self.baseViewController.view bringSubviewToFront:self];
}
self.closeControl.userInteractionEnabled = YES;
[UIView animateWithDuration:0.5 animations:^{
self.maskImageView.alpha = 0.25;
CGFloat screenW = CGRectGetWidth(self.frame);
CGSize size = self.contentView.bounds.size;
self.contentView.frame = CGRectMake((screenW - size.width)/2.0,
[[UIScreen mainScreen] bounds].size.height - size.height,
size.width,
size.height);
}];
[UIView animateWithDuration:0.25 animations:^{
self.maskImageView.layer.transform = CATransform3DRotate(t, 7/90.0 * M_PI_2, 1, 0, 0);
if (self.baseViewController) {
if (self.baseViewController.navigationController) {
[self transNavigationBarToHide:YES];
}
}
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.25f animations:^{
self.maskImageView.layer.transform = CATransform3DTranslate(t, 0, -50, -50);
}];
}];
点击隐藏:
CATransform3D t = CATransform3DIdentity;
t.m34 = -0.004;
[self.maskImageView.layer setTransform:t];
self.closeControl.userInteractionEnabled = NO;
[UIView animateWithDuration:0.5f animations:^{
self.maskImageView.alpha = 1;
self.contentView.frame = CGRectMake(0, self.frame.size.height, self.contentView.frame.size.width, self.contentView.frame.size.height);
} completion:^(BOOL finished) {
if (self.semiModalViewDidCloseBlock) {
self.semiModalViewDidCloseBlock();
}
if (self.baseViewController) {
if (self.baseViewController.navigationController) {
[self transNavigationBarToHide:NO];
}
[self.baseViewController.view sendSubviewToBack:self];
}
}];
[UIView animateWithDuration:0.25f animations:^{
self.maskImageView.layer.transform = CATransform3DTranslate(t, 0, -50, -50);
self.maskImageView.layer.transform = CATransform3DRotate(t, 7/90.0 * M_PI_2, 1, 0, 0);
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.25f animations:^{
self.maskImageView.layer.transform = CATransform3DTranslate(t, 0, 0, 0);
}];
}];
调用方法:
self.narrowedModalView = [[JJSemiModalView alloc] initWithSize:CGSizeMake(self.view.bounds.size.width, 300) andBaseViewController:self];
[self.narrowedModalView open];
demo效果如下:
demo效果详细代码:
JJSemiModalView.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
typedef void (^SemiModalViewWillCloseBlock)(void);
typedef void (^SemiModalViewDidCloseBlock)(void);
@interface JJSemiModalView : UIView
@property (nonatomic, copy) SemiModalViewWillCloseBlock semiModalViewWillCloseBlock;
@property (nonatomic, copy) SemiModalViewDidCloseBlock semiModalViewDidCloseBlock;
@property (nonatomic, assign) BOOL narrowedOff;
- (instancetype)initWithSize:(CGSize)size andBaseViewController:(UIViewController *)baseViewController;
- (void)close;
- (void)open;
@end
NS_ASSUME_NONNULL_END
JJSemiModalView.m
//
// JJSemiModalView.m
// JJSemiModalViewExample
//
// Created by 网络 on 2021/1/28.
//
#import "JJSemiModalView.h"
@interface JJSemiModalView ()
//弹出半屏框
@property (strong, nonatomic) UIView *contentView;
//弹出框背景,点击可复原
@property (strong, nonatomic) UIControl *closeControl;
//缩小视图,背面缩小的是一个图片
@property (strong, nonatomic) UIImageView *maskImageView;
//外部传入,是一个VC
@property (nonatomic, strong) UIViewController *baseViewController;
@end
@implementation JJSemiModalView
- (instancetype)initWithSize:(CGSize)size andBaseViewController:(UIViewController *)baseViewController{
self = [super init];
if (self) {
self.frame = [UIScreen mainScreen].bounds;
self.backgroundColor = [UIColor blackColor];
[self addSubview:self.maskImageView];
[self addSubview:self.closeControl];
[self addSubview:self.contentView];
self.contentView.frame = [self contentViewFrameWithSize:size];
self.baseViewController = baseViewController;
if (baseViewController) {
[baseViewController.view insertSubview:self atIndex:0];
[baseViewController.view sendSubviewToBack:self];
}
}
return self;
}
- (void)close{
if (self.semiModalViewWillCloseBlock) {
self.semiModalViewWillCloseBlock();
}
if (self.narrowedOff) {
self.closeControl.userInteractionEnabled = NO;
[UIView animateWithDuration:0.25f animations:^{
self.maskImageView.alpha = 1;
self.contentView.frame = CGRectMake(0,
self.frame.size.height,
self.contentView.frame.size.width,
self.contentView.frame.size.height);
} completion:^(BOOL finished) {
if (self.semiModalViewDidCloseBlock) {
self.semiModalViewDidCloseBlock();
}
if (self.baseViewController) {
[self.baseViewController.view sendSubviewToBack:self];
if (self.baseViewController.navigationController) {
self.baseViewController.navigationController.navigationBarHidden = NO;
}
}
}];
} else {
CATransform3D t = CATransform3DIdentity;
t.m34 = -0.004;
[self.maskImageView.layer setTransform:t];
self.closeControl.userInteractionEnabled = NO;
[UIView animateWithDuration:0.5f animations:^{
self.maskImageView.alpha = 1;
self.contentView.frame = CGRectMake(0, self.frame.size.height, self.contentView.frame.size.width, self.contentView.frame.size.height);
} completion:^(BOOL finished) {
if (self.semiModalViewDidCloseBlock) {
self.semiModalViewDidCloseBlock();
}
if (self.baseViewController) {
if (self.baseViewController.navigationController) {
[self transNavigationBarToHide:NO];
}
[self.baseViewController.view sendSubviewToBack:self];
}
}];
[UIView animateWithDuration:0.25f animations:^{
self.maskImageView.layer.transform = CATransform3DTranslate(t, 0, -50, -50);
self.maskImageView.layer.transform = CATransform3DRotate(t, 7/90.0 * M_PI_2, 1, 0, 0);
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.25f animations:^{
self.maskImageView.layer.transform = CATransform3DTranslate(t, 0, 0, 0);
}];
}];
}
}
- (void)open{
if (self.narrowedOff) {
self.maskImageView.image = [self screenSnapshotWindow];
if (self.baseViewController) {
[self.baseViewController.view bringSubviewToFront:self];
}
self.closeControl.userInteractionEnabled = YES;
[UIView animateWithDuration:0.25 animations:^{
self.maskImageView.alpha = 0.25;
CGFloat screenW = CGRectGetWidth(self.frame);
CGSize size = self.contentView.bounds.size;
self.contentView.frame = CGRectMake((screenW - size.width)/2.0,
[[UIScreen mainScreen] bounds].size.height - size.height,
size.width,
size.height);
if (self.baseViewController) {
if (self.baseViewController.navigationController) {
self.baseViewController.navigationController.navigationBarHidden = YES;
}
}
}];
} else {
CATransform3D t = CATransform3DIdentity;
t.m34 = -0.004;
self.maskImageView.layer.transform = t;
self.maskImageView.layer.zPosition = -10000;
self.maskImageView.image = [self screenSnapshotWindow];
if (self.baseViewController) {
[self.baseViewController.view bringSubviewToFront:self];
}
self.closeControl.userInteractionEnabled = YES;
[UIView animateWithDuration:0.5 animations:^{
self.maskImageView.alpha = 0.25;
CGFloat screenW = CGRectGetWidth(self.frame);
CGSize size = self.contentView.bounds.size;
self.contentView.frame = CGRectMake((screenW - size.width)/2.0,
[[UIScreen mainScreen] bounds].size.height - size.height,
size.width,
size.height);
}];
[UIView animateWithDuration:0.25 animations:^{
self.maskImageView.layer.transform = CATransform3DRotate(t, 7/90.0 * M_PI_2, 1, 0, 0);
if (self.baseViewController) {
if (self.baseViewController.navigationController) {
[self transNavigationBarToHide:YES];
}
}
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.25f animations:^{
self.maskImageView.layer.transform = CATransform3DTranslate(t, 0, -50, -50);
}];
}];
}
}
- (void)transNavigationBarToHide:(BOOL)hide{
if (hide) {
CGRect frame = self.baseViewController.navigationController.navigationBar.frame;
[self setNavigationBarOriginY:-frame.size.height animated:NO];
} else {
[self setNavigationBarOriginY:[self getStatusBarHight] animated:NO];
}
}
- (void)setNavigationBarOriginY:(CGFloat)y animated:(BOOL)animated{
CGFloat statusBarHeight = [self getStatusBarHight]; //状态栏高度
UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
UIView *appBaseView = keyWindow.rootViewController.view;
CGRect viewControllerFrame = [appBaseView convertRect:appBaseView.bounds toView:keyWindow];
CGFloat overwrapStatusBarHeight = statusBarHeight - viewControllerFrame.origin.y;
CGRect frame = self.baseViewController.navigationController.navigationBar.frame;
frame.origin.y = y;
CGFloat navBarHiddenRatio = overwrapStatusBarHeight > 0 ? (overwrapStatusBarHeight - frame.origin.y) / overwrapStatusBarHeight : 0;
CGFloat alpha = MAX(1.f - navBarHiddenRatio, 0.000001f);
[UIView animateWithDuration:animated ? 0.1 : 0 animations:^{
self.baseViewController.navigationController.navigationBar.frame = frame;
NSUInteger index = 0;
for (UIView *view in self.baseViewController.navigationController.navigationBar.subviews) {
index++;
if (index == 1 || view.hidden || view.alpha <= 0.0f) continue;
view.alpha = alpha;
}
UIColor *tintColor = self.baseViewController.navigationController.navigationBar.tintColor;
if (tintColor) {
self.baseViewController.navigationController.navigationBar.tintColor = [tintColor colorWithAlphaComponent:alpha];
}
}];
}
- (UIImage *)screenSnapshotWindow{
UIView *view = [UIApplication sharedApplication].keyWindow;
UIGraphicsBeginImageContextWithOptions(view.frame.size, NO, 0);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImage * img = [[UIImage alloc] initWithData:UIImageJPEGRepresentation(image, 0.8)];
return img;
}
- (CGRect)contentViewFrameWithSize:(CGSize)size{
if (size.height > [UIScreen mainScreen].bounds.size.height) {
size.height = [UIScreen mainScreen].bounds.size.height;
}
if (size.width > [UIScreen mainScreen].bounds.size.width) {
size.width = [UIScreen mainScreen].bounds.size.width;
}
return CGRectMake((CGRectGetWidth(self.frame)-size.width)/2.0, CGRectGetHeight(self.frame), size.width, size.height);
}
- (CGFloat)getStatusBarHight {
CGFloat statusBarHeight = 0;
if (@available(iOS 13.0, *)) {
UIStatusBarManager *statusBarManager = [UIApplication sharedApplication].windows.firstObject.windowScene.statusBarManager;
statusBarHeight = statusBarManager.statusBarFrame.size.height;
} else {
statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;
}
return statusBarHeight;
}
- (UIView *)contentView{
if (_contentView == nil) {
_contentView = [[UIView alloc] initWithFrame:CGRectZero];
_contentView.backgroundColor = [UIColor redColor];
}
return _contentView;
}
- (UIControl *)closeControl{
if (_closeControl == nil) {
_closeControl = [[UIControl alloc] initWithFrame:self.bounds];
_closeControl.userInteractionEnabled = NO;
[_closeControl addTarget:self action:@selector(close) forControlEvents:UIControlEventTouchUpInside];
}
return _closeControl;
}
- (UIImageView *)maskImageView{
if (_maskImageView == nil) {
_maskImageView = [[UIImageView alloc] initWithFrame:self.bounds];
}
return _maskImageView;
}
@end