美文网首页
全屏方案及各自的优缺点

全屏方案及各自的优缺点

作者: 追风_少年 | 来源:发表于2017-05-22 14:49 被阅读0次

    根据一篇博客以及人家的思路重新写了这个 demo 博客地址见本文末尾。优缺点和实现思路都写在了 demo 中,demo见 GitHub

    首先需要新建播放视频所在的 View 在此我以一个 imageView 代替, 其代码如下

    
    #import <UIKit/UIKit.h>
    
    typedef NS_ENUM(NSInteger, PlayViewState)
    {
        PlayViewStateSmallScreen, // 小屏状态
        PlayViewStateAnimating,   // 动画中的状态
        PlayViewStateFullScreen   // 全屏状态
    };
    
    @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
    
    

    第一种实现方案: 播放器所在的 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];
    }
    
    /*
     * 思路:从小屏进入全屏时,将播放器所在的view放置到window上,用transform的方式做一个旋转动画,最终让view完全覆盖window。 从全屏回到小屏时,用transform的方式做旋转动画,最终让播放器所在的view回到原先的parentView
     
     * 优缺点:这种方式在实现上相对简单,因为仅仅旋转了播放器所在的view,view controller和device的方向均始终为竖直(portrait)。但最大的问题就是全屏时status bar的方向依然是竖直的,虽然之前通过全屏时隐藏statusBar来掩盖了这个问题,但这同时导致了用户无法在视频全屏时看到时间、网络情况等,体验有待改善。
     */
    
    
    - (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;
        
        
        // 计算控制器的 View 上的 palyView的 frame 相对于 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;
        // 计算播放器父View 上的 playView 的 frame 相对于窗口的 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;
        }];
    }
    
    /*
     * 屏幕旋转参考
     * http://www.2cto.com/kf/201504/393303.html
     */
    - (UIInterfaceOrientationMask)supportedInterfaceOrientations {
        return UIInterfaceOrientationMaskPortrait;
    }
    
    
    

    第二种实现方案:模态一个控制器,在模态出的控制器上全屏放置播放器的 view, 因此需要两个类退出全屏和进入全屏做动画的类,其代码如下
    1、进入全屏做转场动画的类

    #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
    {
        
        // 取到将要被模态的控制器
        UIViewController * presentVc = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        // 取到将要被模态的 view
        UIView * presentView = [transitionContext viewForKey:UITransitionContextToViewKey];
        
        // containerView是动画过程中提供的暂时容器。切换时的动画将在这个容器中进行
        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、退出全屏做转场动画的类

    #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、实现方案所需要的两个类(小屏显示、全屏显示 j即被模态的类)

    
    #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];
    }
    
    /*
     * 思路二:
     * 在一个只支持Portrait的ViewController上,present一个只支持Landscape的ViewController,通过改写ViewController之间的转场动画,既能高度自定义全屏动画,也能让StatusBar在视频全屏时横向显示。
     * 缺点一:部分控件依靠 window 尺寸布局,导致全屏动画过程中布局错乱
       原因:present 过程中,ios 对 presentingVC 的 frame 经过了两次变化
          1、由于 window 的 bounds 从竖直(height -> width)的状态转为横向(width ->)的状态由于 autoresing 的作用presentingVC的 view 也变成了横向状态
          2、系统给presentingVC的 view 增加了 transform 使其旋转了90度 让 presentingVC 的 view 看起来还是竖直方向
       结果:如果一个presentingVC的 view的子视图根据 window 布局,在第一次变化的时候宽高已经对调,这样就会导致第二次变化时这个子视图布局错乱。
     * 缺点二:屏幕渲染 bug 导致半边黑屏的问题,腾讯体育APP 就有半边黑屏的问题
     * 缺点三:UIScreen 长宽互换的 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
    
    

    第三种实现方案:在方案一的基础上,调用UIApplication的setStatusBarOrientation:animated:方法来改变statusBar的方向 同时重写当前的ViewController的shouldAutorotate方法,返回NO
    关于其中的坑点 我已在代码中有所标注

    #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];
    }
    
    /*
     * 思路:从小屏进入全屏时,将播放器所在的view放置到window上,用transform的方式做一个旋转动画,最终让view完全覆盖window。 从全屏回到小屏时,用transform的方式做旋转动画,最终让播放器所在的view回到原先的parentView
     
     * 优缺点:这种方式在实现上相对简单,因为仅仅旋转了播放器所在的view,view controller和device的方向均始终为竖直(portrait)。但最大的问题就是全屏时status bar的方向依然是竖直的,虽然之前通过全屏时隐藏statusBar来掩盖了这个问题,但这同时导致了用户无法在视频全屏时看到时间、网络情况等,体验有待改善。
     */
    
    
    - (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;
        
        
        // 计算控制器的 View 上的 palyView的 frame 相对于 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;
        // 计算播放器父View 上的 playView 的 frame 相对于窗口的 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 的话, 以下方法一定会走,但如果项目中既有 TabVC、又有 NavVC 要在父类中也实现如下方法,否则,以下方法不会走,会被拦截
     
     * tabVC: - (BOOL)shouldAutorotate
                {
                    return self.selectedViewController.shouldAutorotate;
                }
     
     * navVC: - (BOOL)shouldAutorotate
                {
                    return self.topViewController.shouldAutorotate;
                }
     */
    
    - (BOOL)shouldAutorotate
    {
        return NO;
    }
    
    

    最后:
    1、大家不喜勿喷,喜欢的可以 star
    2、参考链接

    相关文章

      网友评论

          本文标题:全屏方案及各自的优缺点

          本文链接:https://www.haomeiwen.com/subject/ucnbxxtx.html