1. 程式人生 > >cocos2dx 實現應用內屏幕旋轉,ios端彈出虛擬鍵盤導致界面顯示異常的問題

cocos2dx 實現應用內屏幕旋轉,ios端彈出虛擬鍵盤導致界面顯示異常的問題

計算 interface 輸入 ui界面 fixed nta 垂直 大於 ptr

  項目上遇到這樣的需求,總體界面要橫屏,但是部分界面需要切換到豎屏,同時橫豎屏的界面都會有編輯框。

  網上目前有很多資料涉及到這個的,安卓端實現很簡單,橫豎屏切換兩三行代碼就可以實現;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端彈出虛擬鍵盤導致界面顯示異常的問題