根據(jù)一篇博客以及人家的思路重新寫了這個(gè) demo 博客地址見本文末尾。優(yōu)缺點(diǎn)和實(shí)現(xiàn)思路都寫在了 demo 中,demo見 GitHub
首先需要新建播放視頻所在的 View 在此我以一個(gè) imageView 代替, 其代碼如下
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger, PlayViewState)
{
PlayViewStateSmallScreen, // 小屏狀態(tài)
PlayViewStateAnimating, // 動(dòng)畫中的狀態(tài)
PlayViewStateFullScreen // 全屏狀態(tài)
};
@interface PlayView : UIImageView
@property (nonatomic, assign)PlayViewState state;
@property (nonatomic, weak)UIView * playViewParent; // 記錄父視圖
@property (nonatomic, assign)CGRect playViewFrame; // 記錄在父視圖中的 frame
- (instancetype)initWithFrame:(CGRect)frame;
@end
#import "PlayView.h"
@implementation PlayView
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
self.image = [UIImage imageNamed:@"timg"];
self.contentMode = UIViewContentModeScaleAspectFit;
self.userInteractionEnabled = YES;
}
return self;
}
@end
第一種實(shí)現(xiàn)方案: 播放器所在的 View 的父視圖在 window 和控制器的 view之間相互切換
#import <UIKit/UIKit.h>
@interface FirstViewController : UIViewController
@end
#import "FirstViewController.h"
#import "Header.h"
#import "PlayView.h"
@interface FirstViewController ()
@property (nonatomic, weak)PlayView * playView;
@end
@implementation FirstViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"First";
self.view.backgroundColor = [UIColor whiteColor];
PlayView * playView = [[PlayView alloc] initWithFrame:CGRectMake(0, 64, KSCREEN_WIDTH, KSCREEN_WIDTH * 9 / 16)];
playView.backgroundColor = [UIColor blackColor];
self.playView = playView;
[self.view addSubview:playView];
UITapGestureRecognizer * tap =[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapHundle:)];
[self.playView addGestureRecognizer:tap];
}
/*
* 思路:從小屏進(jìn)入全屏?xí)r,將播放器所在的view放置到window上,用transform的方式做一個(gè)旋轉(zhuǎn)動(dòng)畫,最終讓view完全覆蓋window。 從全屏回到小屏?xí)r搅吁,用transform的方式做旋轉(zhuǎn)動(dòng)畫,最終讓播放器所在的view回到原先的parentView
* 優(yōu)缺點(diǎn):這種方式在實(shí)現(xiàn)上相對(duì)簡(jiǎn)單落午,因?yàn)閮H僅旋轉(zhuǎn)了播放器所在的view谎懦,view controller和device的方向均始終為豎直(portrait)。但最大的問題就是全屏?xí)rstatus bar的方向依然是豎直的溃斋,雖然之前通過全屏?xí)r隱藏statusBar來(lái)掩蓋了這個(gè)問題界拦,但這同時(shí)導(dǎo)致了用戶無(wú)法在視頻全屏?xí)r看到時(shí)間、網(wǎng)絡(luò)情況等梗劫,體驗(yàn)有待改善享甸。
*/
- (void)tapHundle:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded) {
if (self.playView.state == PlayViewStateFullScreen) {
[self exitFullScreen]; // 小屏
}
if (self.playView.state == PlayViewStateSmallScreen) {
[self enterFullScreen]; // 全屏
}
}
}
/*
* 全屏
*/
- (void)enterFullScreen
{
if (self.playView.state != PlayViewStateSmallScreen)
{
return;
}
self.playView.state = PlayViewStateAnimating;
self.playView.playViewParent = self.playView.superview;
self.playView.playViewFrame = self.playView.frame;
// 計(jì)算控制器的 View 上的 palyView的 frame 相對(duì)于 window的 frame
CGRect rectInWindow = [self.view convertRect:self.playView.frame toView:[UIApplication sharedApplication].keyWindow];
[self.playView removeFromSuperview];
self.playView.frame = rectInWindow;
[[UIApplication sharedApplication].keyWindow addSubview:self.playView];
__weak typeof(self) weakSelf = self;
[UIView animateWithDuration:0.5 animations:^{
weakSelf.playView.transform = CGAffineTransformMakeRotation(M_PI_2 );
weakSelf.playView.bounds = CGRectMake(0, 0, CGRectGetHeight(weakSelf.playView.playViewParent.bounds), CGRectGetWidth(weakSelf.playView.playViewParent.bounds));
weakSelf.playView.center = CGPointMake(CGRectGetMidX(weakSelf.playView.superview.bounds), CGRectGetMidY(weakSelf.playView.superview.bounds));
} completion:^(BOOL finished) {
weakSelf.playView.state = PlayViewStateFullScreen;
}];
}
/*
* 退出全屏
*/
- (void)exitFullScreen
{
if (self.playView.state != PlayViewStateFullScreen ) {
return;
}
self.playView.state = PlayViewStateAnimating;
__weak typeof(self) weakSelf = self;
// 計(jì)算播放器父View 上的 playView 的 frame 相對(duì)于窗口的 frame
CGRect frame = [self.playView.playViewParent convertRect:self.playView.playViewFrame toView:[UIApplication sharedApplication].keyWindow];
[UIView animateWithDuration:0.5 animations:^{
weakSelf.playView.transform = CGAffineTransformIdentity;
weakSelf.playView.frame = frame;
} completion:^(BOOL finished) {
[weakSelf.playView removeFromSuperview];
weakSelf.playView.frame = weakSelf.playView.playViewFrame;
[weakSelf.playView.playViewParent addSubview:weakSelf.playView];
weakSelf.playView.state = PlayViewStateSmallScreen;
}];
}
/*
* 屏幕旋轉(zhuǎn)參考
* http://www.2cto.com/kf/201504/393303.html
*/
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
第二種實(shí)現(xiàn)方案:模態(tài)一個(gè)控制器,在模態(tài)出的控制器上全屏放置播放器的 view, 因此需要兩個(gè)類退出全屏和進(jìn)入全屏做動(dòng)畫的類梳侨,其代碼如下
1蛉威、進(jìn)入全屏做轉(zhuǎn)場(chǎng)動(dòng)畫的類
#import <UIKit/UIKit.h>
@class PlayView;
@interface EnterFullScreenTransition : NSObject <UIViewControllerAnimatedTransitioning>
- (instancetype)initWithPlayView:(PlayView *)playView;
@end
#import "EnterFullScreenTransition.h"
@interface EnterFullScreenTransition ()
@property (nonatomic, strong)UIView * playView;
@end
@implementation EnterFullScreenTransition
- (instancetype)initWithPlayView:(PlayView *)playView
{
if (self = [super init]) {
self.playView = (UIView *)playView;
}
return self;
}
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
// 取到將要被模態(tài)的控制器
UIViewController * presentVc = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// 取到將要被模態(tài)的 view
UIView * presentView = [transitionContext viewForKey:UITransitionContextToViewKey];
// containerView是動(dòng)畫過程中提供的暫時(shí)容器。切換時(shí)的動(dòng)畫將在這個(gè)容器中進(jìn)行
CGRect smallPlayViewFrame = [[transitionContext containerView] convertRect:self.playView.bounds fromView:self.playView];
presentView.bounds = self.playView.bounds;
presentView.transform = CGAffineTransformMakeRotation(M_PI_2);
presentView.center = CGPointMake(CGRectGetMidX(smallPlayViewFrame), CGRectGetMidY(smallPlayViewFrame));
[[transitionContext containerView] addSubview:presentView];
self.playView.frame = presentView.bounds;
[presentView addSubview:self.playView];
CGRect presentViewFinalFrame = [transitionContext finalFrameForViewController:presentVc];
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionLayoutSubviews animations:^{
presentView.transform = CGAffineTransformIdentity;
presentView.frame = presentViewFinalFrame;
self.playView.frame = presentView.bounds;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
2走哺、退出全屏做轉(zhuǎn)場(chǎng)動(dòng)畫的類
#import <UIKit/UIKit.h>
@class PlayView;
@interface ExitFullScreenTransition : NSObject <UIViewControllerAnimatedTransitioning>
- (instancetype)initWithPlayView:(PlayView *)playView;
@end
#import "ExitFullScreenTransition.h"
#import "PlayView.h"
@interface ExitFullScreenTransition ()
@property (nonatomic, weak)PlayView * playView;
@end
@implementation ExitFullScreenTransition
- (instancetype)initWithPlayView:(PlayView *)playView
{
if (self = [super init]) {
self.playView = playView;
}
return self;
}
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIView * fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
CGRect smallPlayViewFrame = [[transitionContext containerView] convertRect:self.playView.playViewFrame fromView:self.playView.playViewParent];
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 options:UIViewAnimationOptionLayoutSubviews animations:^{
fromView.transform = CGAffineTransformIdentity;
fromView.frame = smallPlayViewFrame;
self.playView.frame = fromView.bounds;
}
completion:^(BOOL finished) {
self.playView.frame = self.playView.playViewFrame;
[self.playView.playViewParent addSubview:self.playView];
[fromView removeFromSuperview];
[transitionContext completeTransition:YES];
}];
}
@end
3蚯嫌、實(shí)現(xiàn)方案所需要的兩個(gè)類(小屏顯示、全屏顯示 j即被模態(tài)的類)
#import <UIKit/UIKit.h>
@interface SecondViewController : UIViewController
@end
#import "SecondViewController.h"
#import "PlayView.h"
#import "Header.h"
#import "FullScreenViewController.h"
#import "EnterFullScreenTransition.h"
#import "ExitFullScreenTransition.h"
@interface SecondViewController ()<UIViewControllerTransitioningDelegate>
@property (nonatomic, weak)PlayView * playView;
@property (nonatomic, weak)FullScreenViewController * fullScreenVC;
@end
@implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.title = @"Second";
PlayView * playView = [[PlayView alloc] initWithFrame:CGRectMake(0, 64, KSCREEN_WIDTH, KSCREEN_WIDTH * 9 / 16)];
playView.backgroundColor = [UIColor blackColor];
self.playView = playView;
[self.view addSubview:playView];
UITapGestureRecognizer * tap =[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapHundle:)];
[self.playView addGestureRecognizer:tap];
}
/*
* 思路二:
* 在一個(gè)只支持Portrait的ViewController上丙躏,present一個(gè)只支持Landscape的ViewController择示,通過改寫ViewController之間的轉(zhuǎn)場(chǎng)動(dòng)畫,既能高度自定義全屏動(dòng)畫晒旅,也能讓StatusBar在視頻全屏?xí)r橫向顯示栅盲。
* 缺點(diǎn)一:部分控件依靠 window 尺寸布局,導(dǎo)致全屏動(dòng)畫過程中布局錯(cuò)亂
原因:present 過程中敢朱,ios 對(duì) presentingVC 的 frame 經(jīng)過了兩次變化
1剪菱、由于 window 的 bounds 從豎直(height -> width)的狀態(tài)轉(zhuǎn)為橫向(width ->)的狀態(tài)由于 autoresing 的作用presentingVC的 view 也變成了橫向狀態(tài)
2摩瞎、系統(tǒng)給presentingVC的 view 增加了 transform 使其旋轉(zhuǎn)了90度 讓 presentingVC 的 view 看起來(lái)還是豎直方向
結(jié)果:如果一個(gè)presentingVC的 view的子視圖根據(jù) window 布局拴签,在第一次變化的時(shí)候?qū)捀咭呀?jīng)對(duì)調(diào)孝常,這樣就會(huì)導(dǎo)致第二次變化時(shí)這個(gè)子視圖布局錯(cuò)亂。
* 缺點(diǎn)二:屏幕渲染 bug 導(dǎo)致半邊黑屏的問題蚓哩,騰訊體育APP 就有半邊黑屏的問題
* 缺點(diǎn)三:UIScreen 長(zhǎng)寬互換的 bug(iOS10)
*/
- (void)tapHundle:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded) {
if (self.playView.state == PlayViewStateSmallScreen) {
[self enterFullScreen]; // 全屏
}
if (self.playView.state == PlayViewStateFullScreen) {
[self exitFullScreen]; // 退出全屏
}
}
}
/*
* 全屏
*/
- (void)enterFullScreen
{
if (self.playView.state != PlayViewStateSmallScreen) {
return;
}
self.playView.state = PlayViewStateAnimating;
// 記錄最初的 frame 和父視圖
self.playView.playViewFrame= self.playView.frame;
self.playView.playViewParent = self.view;
[self.playView removeFromSuperview];
FullScreenViewController * fullScreenVC = [[FullScreenViewController alloc] init];
fullScreenVC.transitioningDelegate = self;
__weak typeof(self) weakSelf = self;
fullScreenVC.modalPresentationStyle = UIModalPresentationOverFullScreen;
fullScreenVC.modalPresentationCapturesStatusBarAppearance = true;
[self presentViewController:fullScreenVC animated:YES completion:^{
weakSelf.playView.state = PlayViewStateFullScreen;
}];
self.fullScreenVC = fullScreenVC;
}
- (void)exitFullScreen
{
if (self.playView.state != PlayViewStateFullScreen ) {
return;
}
self.playView.state = PlayViewStateAnimating;
__weak typeof(self) weakSelf = self;
[self.fullScreenVC dismissViewControllerAnimated:YES completion:^{
weakSelf.playView.state = PlayViewStateSmallScreen;
}];
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
return [[EnterFullScreenTransition alloc] initWithPlayView:self.playView];
}
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
return [[ExitFullScreenTransition alloc] initWithPlayView:self.playView];
}
@end
#import <UIKit/UIKit.h>
@interface FullScreenViewController : UIViewController
@end
#import "FullScreenViewController.h"
#import "Header.h"
@interface FullScreenViewController ()
@end
@implementation FullScreenViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskLandscapeLeft;
}
- (BOOL)prefersStatusBarHidden {
return NO;
}
@end
第三種實(shí)現(xiàn)方案:在方案一的基礎(chǔ)上构灸,調(diào)用UIApplication的setStatusBarOrientation:animated:方法來(lái)改變statusBar的方向 同時(shí)重寫當(dāng)前的ViewController的shouldAutorotate方法,返回NO
關(guān)于其中的坑點(diǎn) 我已在代碼中有所標(biāo)注
#import <UIKit/UIKit.h>
@interface ThirdViewController : UIViewController
@end
#import "ThirdViewController.h"
#import "Header.h"
#import "PlayView.h"
@interface ThirdViewController ()
@property (nonatomic, weak)PlayView * playView;
@end
@implementation ThirdViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"Third";
self.view.backgroundColor = [UIColor whiteColor];
PlayView * playView = [[PlayView alloc] initWithFrame:CGRectMake(0, 64, KSCREEN_WIDTH, KSCREEN_WIDTH * 9 / 16)];
playView.backgroundColor = [UIColor blackColor];
self.playView = playView;
[self.view addSubview:playView];
UITapGestureRecognizer * tap =[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapHundle:)];
[self.playView addGestureRecognizer:tap];
}
/*
* 思路:從小屏進(jìn)入全屏?xí)r岸梨,將播放器所在的view放置到window上喜颁,用transform的方式做一個(gè)旋轉(zhuǎn)動(dòng)畫,最終讓view完全覆蓋window曹阔。 從全屏回到小屏?xí)r半开,用transform的方式做旋轉(zhuǎn)動(dòng)畫,最終讓播放器所在的view回到原先的parentView
* 優(yōu)缺點(diǎn):這種方式在實(shí)現(xiàn)上相對(duì)簡(jiǎn)單赃份,因?yàn)閮H僅旋轉(zhuǎn)了播放器所在的view寂拆,view controller和device的方向均始終為豎直(portrait)。但最大的問題就是全屏?xí)rstatus bar的方向依然是豎直的抓韩,雖然之前通過全屏?xí)r隱藏statusBar來(lái)掩蓋了這個(gè)問題纠永,但這同時(shí)導(dǎo)致了用戶無(wú)法在視頻全屏?xí)r看到時(shí)間、網(wǎng)絡(luò)情況等谒拴,體驗(yàn)有待改善尝江。
*/
- (void)tapHundle:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded) {
if (self.playView.state == PlayViewStateFullScreen) {
[self exitFullScreen]; // 小屏
}
if (self.playView.state == PlayViewStateSmallScreen) {
[self enterFullScreen]; // 全屏
}
}
}
/*
* 全屏
*/
- (void)enterFullScreen
{
if (self.playView.state != PlayViewStateSmallScreen)
{
return;
}
self.playView.state = PlayViewStateAnimating;
self.playView.playViewParent = self.playView.superview;
self.playView.playViewFrame = self.playView.frame;
// 計(jì)算控制器的 View 上的 palyView的 frame 相對(duì)于 window的 frame
CGRect rectInWindow = [self.view convertRect:self.playView.frame toView:[UIApplication sharedApplication].keyWindow];
[self.playView removeFromSuperview];
self.playView.frame = rectInWindow;
[[UIApplication sharedApplication].keyWindow addSubview:self.playView];
__weak typeof(self) weakSelf = self;
[UIView animateWithDuration:0.5 animations:^{
weakSelf.playView.transform = CGAffineTransformMakeRotation(M_PI_2 );
weakSelf.playView.bounds = CGRectMake(0, 0, CGRectGetHeight(weakSelf.playView.playViewParent.bounds), CGRectGetWidth(weakSelf.playView.playViewParent.bounds));
weakSelf.playView.center = CGPointMake(CGRectGetMidX(weakSelf.playView.superview.bounds), CGRectGetMidY(weakSelf.playView.superview.bounds));
} completion:^(BOOL finished) {
weakSelf.playView.state = PlayViewStateFullScreen;
}];
[self refreshStatusBarOrientation:UIInterfaceOrientationLandscapeRight];
}
/*
* 退出全屏
*/
- (void)exitFullScreen
{
if (self.playView.state != PlayViewStateFullScreen ) {
return;
}
self.playView.state = PlayViewStateAnimating;
__weak typeof(self) weakSelf = self;
// 計(jì)算播放器父View 上的 playView 的 frame 相對(duì)于窗口的 frame
CGRect frame = [self.playView.playViewParent convertRect:self.playView.playViewFrame toView:[UIApplication sharedApplication].keyWindow];
[UIView animateWithDuration:0.5 animations:^{
weakSelf.playView.transform = CGAffineTransformIdentity;
weakSelf.playView.frame = frame;
} completion:^(BOOL finished) {
[weakSelf.playView removeFromSuperview];
weakSelf.playView.frame = weakSelf.playView.playViewFrame;
[weakSelf.playView.playViewParent addSubview:weakSelf.playView];
weakSelf.playView.state = PlayViewStateSmallScreen;
}];
[self refreshStatusBarOrientation:UIInterfaceOrientationPortrait];
}
- (void)refreshStatusBarOrientation:(UIInterfaceOrientation)interfaceOrientation {
[[UIApplication sharedApplication] setStatusBarOrientation:interfaceOrientation animated:YES];
}
/* 如果只有 viewController 的話, 以下方法一定會(huì)走,但如果項(xiàng)目中既有 TabVC英上、又有 NavVC 要在父類中也實(shí)現(xiàn)如下方法,否則炭序,以下方法不會(huì)走,會(huì)被攔截
* tabVC: - (BOOL)shouldAutorotate
{
return self.selectedViewController.shouldAutorotate;
}
* navVC: - (BOOL)shouldAutorotate
{
return self.topViewController.shouldAutorotate;
}
*/
- (BOOL)shouldAutorotate
{
return NO;
}
最后:
1苍日、大家不喜勿噴少态,喜歡的可以 star
2、參考鏈接