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

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

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

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

问题描述:

  直接定位到引擎开启/弹出虚拟键盘的地方,在源码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视图的位移。

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

上一篇:charles-抓包Andriod 手机的设置


下一篇:charles-Andriod 手机手机抓包乱码