Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 390|回复: 0

cocos2dx 实现应用内屏幕旋转,ios端弹出虚拟键盘导致界面显示异常的问题

[复制链接]
  • TA的每日心情
    奋斗
    前天 11:25
  • 签到天数: 792 天

    [LV.10]以坛为家III

    2049

    主题

    2107

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    723136
    发表于 2021-6-17 08:23:51 | 显示全部楼层 |阅读模式

      项目上遇到这样的需求,总体界面要横屏,但是部分界面需要切换到竖屏,同时横竖屏的界面都会有编辑框。

      网上目前有很多资料涉及到这个的,安卓端实现很简单,横竖屏切换两三行代码就可以实现;ios端网上目前也有方案,比安卓稍微复杂点,但是也可以实现。但是涉及到界面上有编辑框,会弹出输入键盘的时候,ios端的界面就会出现异常。目前引擎对于编辑框的处理,在弹出键盘的时候,整体的ui界面会上移,使输入区域高于键盘,这样方便编辑的时候显示正在编辑的内容。但是ios端横竖屏切换了之后,弹出虚拟键盘之后,ui界面并没有正常的上移,而且虚拟键盘弹回之后,ui界面没有回到正常的位置,这里需要讨论的就是这个问题。

      以下涉及到源码的地方,使用的是cocos2dx 3.10 版本的引擎,我对比了一下最新版本的引擎(3.17.1)这些代码大致上是一致的,我在两个版本的引擎上都实现了这里要讨论的功能。另外,针对的是游戏默认是横屏,在某些情况下需要切换到竖屏而实现的方案。如果游戏默认是竖屏,而在某些情况下需要切换到横屏,可能思路是一致,但是具体的改动可能会有差异

    最新调整:

      解决方案中我有提到,新增了一个获取设备方向的方法,在手动旋转屏幕的时候记录当前状态栏是否是横屏,在返回设备方向,如下所示:

    UIInterfaceOrientation getFixedOrientation2(BOOL landscape)
    {
        if (landscape){
            return UIInterfaceOrientationPortrait;
        }
        return UIInterfaceOrientationLandscapeRight;
    }

      引擎默认的方法是根据状态栏的方向来返回一个固定的设备方向。一开始我在测试的时候,因为没有固定状态栏,所以我在引擎已提供的方法基础上去获取所需要的设备方向,是有问题的,即 [[UIApplication sharedApplication] statusBarOrientation] 这个方法返回的方向,是实际的状态栏方向,手持设备竖屏、横屏都会返回实际的方向。但是后来,我们在选装屏幕的时候,不固定状态栏方向,会导致输入法弹出方向有问题,即ui显示横屏的时候,设备竖屏,此时输入法是以竖屏的方式弹出来。所以,在我们已经固定了状态栏方向的时候,就可以在引擎已经提供的方法的基础上,进行修改,而不用再增加额外的方法和变量了,如下:

    UIInterfaceOrientation getFixedOrientation(UIInterfaceOrientation statusBarOrientation)
    {
        if (statusBarOrientation == UIInterfaceOrientationLandscapeLeft || statusBarOrientation == UIInterfaceOrientationLandscapeRight){
            return UIInterfaceOrientationPortrait;
        }
        return UIInterfaceOrientationLandscapeRight;
    }

     

    问题描述:

      直接定位到引擎开启/弹出虚拟键盘的地方,在源码UIEditBox.cpp里面有实现:

      以下是开启/弹出键盘的方法

    void EditBox::openKeyboard() const
    {
        _editBoxImpl->openKeyboard();
    }

      引擎执行完这个之后,ios端通过 CCEAGLView-ios.mm 里面的 onUIKeyboardNotification 方法,调用到以下 UIEditBox.cpp 的方法

    void EditBox::keyboardWillShow(IMEKeyboardNotificationInfo& info)
    {
        ...
        if (_editBoxImpl != nullptr)
        {
            _editBoxImpl->doAnimationWhenKeyboardMove(info.duration, _adjustHeight);
        }
    }

      在这里有一个方法 doAnimationWhenKeyboardMove,追踪到 UIEditBoxImpl-ios.mm 里面

    void EditBoxImplIOS::doAnimationWhenKeyboardMove(float duration, float distance)
    {
        if ([_systemControl isEditState] || distance < 0.0f) {
            [_systemControl doAnimationWhenKeyboardMoveWithDuration:duration distance:distance];
        }
    }

      再追踪到 CCUIEditBoxIOS.mm 

    - (void)doAnimationWhenKeyboardMoveWithDuration:(float)duration distance:(float)distance
    {
        ...
        [eaglview doAnimationWhenKeyboardMoveWithDuration:duration distance:distance];
    }

      再追踪到 CCEAGLView-ios.mm ,上部分代码

    -(void) doAnimationWhenKeyboardMoveWithDuration:(float)duration distance:(float)dis
    {
        ...
        switch (getFixedOrientation([[UIApplication sharedApplication] statusBarOrientation]))
        {
            case UIInterfaceOrientationPortrait:
                self.frame = CGRectMake(originalRect_.origin.x, originalRect_.origin.y - dis, originalRect_.size.width, originalRect_.size.height);
                break;case UIInterfaceOrientationLandscapeRight:
                self.frame = CGRectMake(originalRect_.origin.x + dis, originalRect_.origin.y , originalRect_.size.width, originalRect_.size.height);
                break;
                
            default:
                break;
        }
        ...
    }

      熟悉ios的应该知道,ui界面上移的动画就是在这里实现的,位移距离是 dis 变量控制,位移时间是 duration 变量控制。而游戏横竖屏切换之后,在弹出虚拟键盘导致ui界面显示异常的问题,也是在这里出现的,我们要调整的就是在这个地方。

    解决方案:

      在上面断点追踪键盘弹出的执行过程中,onUIKeyboardNotification 方法中有涉及到坐标的装换、位置的计算,doAnimationWhenKeyboardMoveWithDuration 方法则是具体的执行ui界面的位移,所以出现界面异常应该是这两个地方的计算出现了问题。

      首先断点调试 onUIKeyboardNotification 方法,从命名来看这是一个监听,在系统发出准备弹出键盘、弹出键盘等一系列消息的时候会调用这个方法,另外在垂直方向这个case里面,我们看到了对y坐标进行了调整,很像我们弹出虚拟键盘的时候界面上移的效果,这里上部分代码

    switch (getFixedOrientation([[UIApplication sharedApplication] statusBarOrientation]))
        {
         ...
    case UIInterfaceOrientationPortrait: begin.origin.y = viewSize.height - begin.origin.y - begin.size.height; end.origin.y = viewSize.height - end.origin.y - end.size.height; break;case UIInterfaceOrientationLandscapeRight: std::swap(begin.size.width, begin.size.height); std::swap(end.size.width, end.size.height); std::swap(viewSize.width, viewSize.height); tmp = begin.origin.x; begin.origin.x = begin.origin.y; begin.origin.y = tmp; tmp = end.origin.x; end.origin.x = end.origin.y; end.origin.y = tmp; break; ... }

      这个switch根据当前系统状态栏的方向,来对坐标、尺寸信息做不同的装换。这里有一个方法 getFixedOrientation 会返回方向信息

    UIInterfaceOrientation getFixedOrientation(UIInterfaceOrientation statusBarOrientation)
    {
        if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
        {
            statusBarOrientation = UIInterfaceOrientationPortrait;
        }
        return statusBarOrientation;
    }

      从这个方法的实现来看,隐约感觉有问题:如果系统版本大于8.0,则返回的是一个固定的方向,如果我们做了横竖屏切换,那么这个方向应该是不同的才对吧?

      所以我首先改了这个地方:

    UIInterfaceOrientation getFixedOrientation2(BOOL landscape)
    {
        if (landscape){
            return UIInterfaceOrientationPortrait;
        }
        return UIInterfaceOrientationLandscapeRight;
    }

      返回不是固定的反向值,而是会根据当前选择/设置的设备方向进行调整,这里起来是一个反的,比如状态栏选择了横屏,但是返回了一个竖屏方向。在这里我主要是考虑,设备横屏的时候,弹出的虚拟键盘界面位移是正常的,上文提到的修改y坐标值思路是对的。

      屏幕旋转之后,界面也应该是往上位移,所以我把 onUIKeyboardNotification 中switch的UIInterfaceOrientationLandscapeRight case里面的代码也调整了一下,跟竖屏方向的调整一样:

    case UIInterfaceOrientationLandscapeRight:
         begin.origin.y = viewSize.height - begin.origin.y - begin.size.height;
         end.origin.y = viewSize.height - end.origin.y - end.size.height;
         break;

      本来以为改好了,重新编译运行之后,发现还是有问题,所以进入到下一步修改。上文中提到 doAnimationWhenKeyboardMoveWithDuration 这个方法是最终动画执行的地方,这个方法里面也是根据设备方向做不同的操作,而且横屏状态下切换正常,竖屏异常,就直接看竖屏状态下切换的case,因为竖屏状态,getFixedOrientation返回的是横屏的方向,所以看横屏的case

    case UIInterfaceOrientationLandscapeRight:
                self.frame = CGRectMake(originalRect_.origin.x + dis, originalRect_.origin.y , originalRect_.size.width, originalRect_.size.height);
                break;

      从代码来看,位移操作是x轴移动dis距离,然后宽高是正常的原始宽高。但是我们在实际的体验上来看,竖屏的时候,看起来也应该是y轴的位移,且此时的宽高正好是跟横屏时相反的,所以这里先调整一下:

    case UIInterfaceOrientationLandscapeRight:
                self.frame = CGRectMake(originalRect_.origin.x, originalRect_.origin.y - dis, originalRect_.size.height, originalRect_.size.width);
                break;

      重新编译再执行一下,ui偏移正常了。

      但是又发现了新的问题,比如我们界面显示横屏的时候,我们把设备竖着拿,此时再点编辑框弹出键盘,发现键盘是竖着弹出来的,这个也不对;同样,界面显示竖屏的时候,我们把设备横着拿,此时弹出的键盘是横着的。网上查阅资料发现,ios键盘弹出方向,是根据状态栏方向来的。所以我们在旋转屏幕的时候,同时也要锁定状态栏的方向,这里涉及到 AppController.mm/RootViewController.mm 两个部分的代码修改

    static bool bRotate=false;
    -(NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window{
        if (bRotate){
            [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait];
        }
        else{
            [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeLeft];
        }
        return UIInterfaceOrientationMaskAllButUpsideDown;
    }

      上面的操作就是锁定了状态栏方向,

    if (bIsLeft){
                [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait];
                //[[UIDevice currentDevice] performSelector:@selector(setOrientation:) withObject:(id)UIDeviceOrientationPortrait];
                SEL selector = NSSelectorFromString(@"setOrientation:");
                NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];
                [invocation setSelector:selector];
                [invocation setTarget:[UIDevice currentDevice]];
                int val = UIDeviceOrientationPortrait;//这里可以改变旋转的方向
                [invocation setArgument:&val atIndex:2];
                [invocation invoke];
            }
            else{
                [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeLeft];
                //[[UIDevice currentDevice] performSelector:@selector(setOrientation:) withObject:(id)UIDeviceOrientationLandscapeRight];
                SEL selector = NSSelectorFromString(@"setOrientation:");
                NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];
                [invocation setSelector:selector];
                [invocation setTarget:[UIDevice currentDevice]];
                int val = UIDeviceOrientationLandscapeRight;//这里可以改变旋转的方向
                [invocation setArgument:&val atIndex:2];
                [invocation invoke];
            }

      上面的代码是旋转设备方向。网上也有另外一个方法:

    [[UIDevice currentDevice] performSelector:@selector(setOrientation:) withObject:bIsLeft ? (id)UIDeviceOrientationPortrait : (id)UIDeviceOrientationLandscapeRight];

      这个方法有个问题,如果设备选择了锁定竖屏,那么从横屏切换到竖屏,界面不会正常的旋转,需要拉一下状态栏才会旋转,我也不知道为什么。。。

    总结:

      本次修改,主要改了三个文件 AppController.mm 、RootViewController.mm、CCEAGLView-ios.mm,三个文件。要注意的地方就是,修改了屏幕旋转,如果此时还需要使用输入功能,那么还需要做进一步的改动,以适应虚拟键盘带来的ui视图的位移。

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-9-16 19:13 , Processed in 0.058509 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表