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

摘要:
但是涉及到界面上有编辑框,会弹出输入键盘的时候,ios端的界面就会出现异常。但是ios端横竖屏切换了之后,弹出虚拟键盘之后,ui界面并没有正常的上移,而且虚拟键盘弹回之后,ui界面没有回到正常的位置,这里需要讨论的就是这个问题。而游戏横竖屏切换之后,在弹出虚拟键盘导致ui界面显示异常的问题,也是在这里出现的,我们要调整的就是在这个地方。

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

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

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

最新调整:

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

UIInterfaceOrientation getFixedOrientation2(BOOL landscape)
{
    if(landscape){
        returnUIInterfaceOrientationPortrait;
    }
    returnUIInterfaceOrientationLandscapeRight;
}

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

UIInterfaceOrientation getFixedOrientation(UIInterfaceOrientation statusBarOrientation)
{
    if (statusBarOrientation == UIInterfaceOrientationLandscapeLeft || statusBarOrientation ==UIInterfaceOrientationLandscapeRight){
        returnUIInterfaceOrientationPortrait;
    }
    returnUIInterfaceOrientationLandscapeRight;}

问题描述:

直接定位到引擎开启/弹出虚拟键盘的地方,在源码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, floatdistance)
{
    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]))
    {
        caseUIInterfaceOrientationPortrait:
            self.frame = CGRectMake(originalRect_.origin.x, originalRect_.origin.y -dis, originalRect_.size.width, originalRect_.size.height);
            break;caseUIInterfaceOrientationLandscapeRight:
            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]))
    {
...
caseUIInterfaceOrientationPortrait: begin.origin.y = viewSize.height - begin.origin.y -begin.size.height; end.origin.y = viewSize.height - end.origin.y -end.size.height; break;caseUIInterfaceOrientationLandscapeRight: 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;
    }
    returnstatusBarOrientation;
}

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

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

UIInterfaceOrientation getFixedOrientation2(BOOL landscape)
{
    if(landscape){
        returnUIInterfaceOrientationPortrait;
    }
    returnUIInterfaceOrientationLandscapeRight;
}

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

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

caseUIInterfaceOrientationLandscapeRight:
     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

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

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

caseUIInterfaceOrientationLandscapeRight:
            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];
    }
    returnUIInterfaceOrientationMaskAllButUpsideDown;
}

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

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视图的位移。

免责声明:文章转载自《cocos2dx 实现应用内屏幕旋转,ios端弹出虚拟键盘导致界面显示异常的问题》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇win10下安装FFmpeg步骤Oracle V$SESSION详解下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

data.table包使用总结

装载 作者:kicilove  来源:CSDN  原文:https://blog.csdn.net/kicilove/article/details/76060980?utm_source=copy  data.table包使用总结R中的data.table包提供了一个data.frame的高级版本,让你的程序做数据整型的运算速度大大的增加。data.t...

git subtree有效管理公共第三方lib

  如果你的项目中有很多第三方的lib,你希望使用它,并且也希望可能对该lib做修改并且贡献到原始的项目中去,或者你的项目希望模块化,分为几个repo单独维护,那么git subtree就是一个选择。git subtree管理的子项目在父项目中作为一个完整的代码copy存在,并不包含历史信息。综合考虑git subtree和git submodule的优缺...

git 拉取远程分支报错(fatal: '' is not a commit and a branch '' cannot be created from it)

问题描述从远程git上拉取某一个分支,然后报错,拉取不了这个分支。 拉取分支的命令: git checkout -b xxx-static-19 origin/xxx-static-19其中xxx-static-19是分支名。 报错 fatal: 'origin/xxx-static-19' is not a commit and a branch 'xx...

Git Your configuration specifies to merge with the ref 'release'

问题描述 昨天,以及今天(2014-11-29),使用 TortoiseGit 时碰到了一个诡异的问题. 卸载,清理注册表,重装,重启,各种折腾以后,还是不能解决. 但是23.45分一过,突然灵光一闪,解决了. 问题是这样的. 使用命令行的git push,git fetch,git pull什么的都没问题. 但是使用 TortoiseGit 执行拉取(p...

跨域大全

正常ajax请求表现 跨域类型: 跨域,指一个域下的文档或脚本试图去请求另一个域下的资源,ajax跨域只是属于浏览器"同源策略"中的一部分,其它的还有: 0)Cookie跨域iframe跨域,LocalStorage跨域 1.) 资源跳转: A链接、重定向、表单提交 2.) 资源嵌入:<link>、<script>、<i...

Android中的沉浸式状态栏效果

无意间了解到沉浸式状态栏,感觉贼拉的高大上,于是就是试着去了解一下,就有了这篇文章。下面就来了解一下啥叫沉浸式状态栏。传统的手机状态栏是呈现出黑色条状的,有的和手机主界面有很明显的区别。这一样就在一定程度上牺牲了视觉宽度,界面面积变小。Google从android kitkat(Android 4.4)开始,给我们开发者提供了一套能透明的系统ui样式给状态...