项目中,也写过类似"视频全屏"的功能, 前一阵子读到今日头条 的一篇技术文章,详细介绍三种旋转方法差异优劣最终择取。文章从技术角度看写的非常好,从用户角度看,也用过多家有视频功能的app,今日头条的体验的确很优。特别值得学习特此参考写了一个视频全屏小功能
实现方法:配合重写当前的ViewController的shouldAutorotate方法,返回NO 并且控制 状态栏的展示 然后 通过 animation旋转动画处理UI相对布局
(1)组织类别方法 UINavigationController+Rotation 目的视频旋转 状态栏也要旋转
// // UINavigationController+Rotation.h // SectionDemo // // Created by HF on 17/4/1. // Copyright © 2017年 HF-Liqun. All rights reserved. // #import@interface UINavigationController (Rotation) @end // // UINavigationController+Rotation.m // SectionDemo // // Created by HF on 17/4/1. // Copyright © 2017年 HF-Liqun. All rights reserved. // #import "UINavigationController+Rotation.h" @implementation UINavigationController (Rotation) - (BOOL)shouldAutorotate { return [[self.viewControllers lastObject] shouldAutorotate]; } - (NSUInteger)supportedInterfaceOrientations { return [[self.viewControllers lastObject] supportedInterfaceOrientations]; } - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { return [[self.viewControllers lastObject] preferredInterfaceOrientationForPresentation]; } @end
(2)视频UI HFMovieView
// // HFMovieView.h // SectionDemo // // Created by HF on 17/4/1. // Copyright © 2017年 HF-Liqun. All rights reserved. // #import#import "HFPlayerView.h" typedef NS_ENUM(NSUInteger, MovieViewState) { MovieViewStateSmall, MovieViewStateAnimating, MovieViewStateFullscreen, }; @interface HFMovieView : UIView /** 视频播放对象 */ @property (nonatomic, strong)HFPlayerView *videoView; /** 记录小屏时的parentView */ @property (nonatomic, weak) UIView *movieViewParentView; /** 记录小屏时的frame */ @property (nonatomic, assign) CGRect movieViewFrame; @property (nonatomic, assign) MovieViewState state; @end
// // HFMovieView.m // SectionDemo // // Created by HF on 17/4/1. // Copyright © 2017年 HF-Liqun. All rights reserved. // #import "HFMovieView.h" @implementation HFMovieView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { //videoView [self addSubview:self.videoView]; //others UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)]; [self.videoView addGestureRecognizer:tapGestureRecognizer]; } return self; } #pragma mark - event - (void)handleTapGesture:(UITapGestureRecognizer *)sender { if (sender.state == UIGestureRecognizerStateEnded) { if (self.state == MovieViewStateSmall) { [self enterFullscreen]; } else if (self.state == MovieViewStateFullscreen) { [self exitFullscreen]; } } } #pragma mark - private #pragma mark -- 全屏 animation - (void)enterFullscreen { if (self.state != MovieViewStateSmall) { return; } self.state = MovieViewStateAnimating; /* * 记录进入全屏前的parentView和frame */ self.movieViewParentView = self.videoView.superview; self.movieViewFrame = self.videoView.frame; /* * movieView移到window上 */ CGRect rectInWindow = [self convertRect:self.videoView.bounds toView:[UIApplication sharedApplication].keyWindow]; [self.videoView removeFromSuperview]; self.videoView.frame = rectInWindow; [[UIApplication sharedApplication].keyWindow addSubview:self.videoView]; /* * 执行动画 */ [UIView animateWithDuration:0.5 animations:^{ self.videoView.transform = CGAffineTransformMakeRotation(M_PI_2); self.videoView.bounds = CGRectMake(0, 0, CGRectGetHeight(self.videoView.superview.bounds), CGRectGetWidth(self.videoView.superview.bounds)); self.videoView.center = CGPointMake(CGRectGetMidX(self.videoView.superview.bounds), CGRectGetMidY(self.videoView.superview.bounds)); } completion:^(BOOL finished) { self.state = MovieViewStateFullscreen; }]; [self refreshStatusBarOrientation:UIInterfaceOrientationLandscapeRight]; } #pragma mark -- 退出全屏 animation - (void)exitFullscreen { if (self.state != MovieViewStateFullscreen) { return; } self.state = MovieViewStateAnimating; CGRect frame = [self.movieViewParentView convertRect:self.movieViewFrame toView:[UIApplication sharedApplication].keyWindow]; [UIView animateWithDuration:0.5 animations:^{ self.videoView.transform = CGAffineTransformIdentity; self.videoView.frame = frame; } completion:^(BOOL finished) { /* * movieView回到小屏位置 */ [self.videoView removeFromSuperview]; self.videoView.frame = self.movieViewFrame; [self.movieViewParentView addSubview:self.videoView]; self.state = MovieViewStateSmall; }]; [self refreshStatusBarOrientation:UIInterfaceOrientationPortrait]; } #pragma mark -- 更新状态栏方向 - (void)refreshStatusBarOrientation:(UIInterfaceOrientation)interfaceOrientation { [[UIApplication sharedApplication] setStatusBarOrientation:interfaceOrientation animated:YES]; } #pragma mark - setter and getter - (HFPlayerView *)videoView { if (!_videoView) { _videoView = [[HFPlayerView alloc]initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)]; _videoView.backgroundColor = [UIColor blackColor]; } return _videoView; } @end
(3)视图控制器
// // MethodDetailViewController.h // SectionDemo // // Created by HF on 17/4/1. // Copyright © 2017年 HF-Liqun. All rights reserved. // #import#import "HFMovieView.h" @interface MethodDetailViewController : UIViewController @property (nonatomic, strong) HFMovieView *moviewView; @end // // MethodDetailViewController.m // SectionDemo // // Created by HF on 17/4/1. // Copyright © 2017年 HF-Liqun. All rights reserved. // #import "MethodDetailViewController.h" @interface MethodDetailViewController () @property (nonatomic, strong) UITableView *tableView; @property (nonatomic, strong) UIView *headView; @end @implementation MethodDetailViewController - (void)viewDidLoad { [super viewDidLoad]; [self.view addSubview:self.tableView]; [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(self.view); }]; self.tableView.tableHeaderView = self.headView; [self.headView addSubview:self.moviewView]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } #pragma mark - 旋转配置 - (BOOL)shouldAutorotate { return NO; } #pragma mark - setter/getter - (UITableView *)tableView { if (!_tableView) { _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; _tableView.backgroundColor = [UIColor clearColor]; // _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; } return _tableView; } - (UIView *)headView { if (!_headView) { _headView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 200)];; _headView.backgroundColor = [UIColor lightGrayColor]; } return _headView; } - (HFMovieView *)moviewView { if (!_moviewView) { _moviewView = [[HFMovieView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 200)]; _moviewView.backgroundColor = [UIColor yellowColor]; } return _moviewView; } @end
效果图:
(4)参考之前 写过视频播放的相关方法 优化架构分工
参考 SectionDemo
参考:
1. https://techblog.toutiao.com/2017/03/28/fullscreen/
2. iOS AVPlayer 学习
3. iOS CMTimeMake 和 CMTimeMakeWithSeconds 学习