2016-12-23 18 views
23

От Multi-Window documentation:Как определить правильную ориентацию устройства в многоэкранном режиме Android N?

отключенные функции в режиме нескольких окон

Некоторые функции отключены или игнорируются, когда устройство находится в режиме нескольких окон, потому что они не имеют смысла для деятельности которые могут совместно использовать экран устройства с другими действиями или приложениями. Такие особенности включают в себя:

  • Некоторые параметры настройки пользовательского интерфейса системы отключены; например, приложения не могут скрыть строку состояния, если они не работают в полноэкранном режиме.
  • Система игнорирует изменения атрибута android: screenOrientation.

Я понимаю, что для большинства приложений это не имеет смысла отличие между портретным и ландшафтным режимами, однако я работаю над SDK, который содержит изображение с камеры, которые пользователь может поставить на любую деятельность, они хотят - в том числе деятельность который поддерживает многооконный режим. Проблема в том, что просмотр камеры содержит SurfaceView/TextureView, который отображает предварительный просмотр камеры и для правильного отображения предварительного просмотра во всех ориентациях деятельности, для правильного поворота камеры необходимо знать правильную ориентацию активности.

Проблема в том, что мой код, который вычисляет правильную ориентацию активности, исследует текущую конфигурационную ориентацию (портретную или альбомную) и текущее вращение экрана. Проблема в том, что в режиме многооконного режима ориентация текущей конфигурации не отражает реальную ориентацию активности. Это приводит к тому, что просмотр камеры поворачивается на 90 градусов, потому что Android сообщает о другой конфигурации, чем ориентация.

Мой текущий обходной путь состоит в проверке запрашиваемой ориентации деятельности и использовать его в качестве основы, но есть две проблемы с этим:

  1. запрашиваемый ориентационная деятельность не должна отражать фактическую ориентацию деятельности (то есть запрос может по-прежнему не выполняться)
  2. Запрошенная ориентация деятельности может быть «позади», «датчик», «пользователь» и т. д., которая не раскрывает никакой информации о текущей ориентации активности.
  3. Согласно documentation, ориентация экрана фактически игнорируется в режиме нескольких окон, так и 1. 2. просто не будет работать

Есть ли способ, чтобы решительно рассчитать правильную ориентацию деятельности даже в мульти-окна конфигурация?

Вот мой код, который я в настоящее время использую (см комментарии для проблемных частей):

protected int calculateHostScreenOrientation() { 
    int hostScreenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; 
    WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); 
    int rotation = getDisplayOrientation(wm); 

    boolean activityInPortrait; 
    if (!isInMultiWindowMode()) { 
     activityInPortrait = (mConfigurationOrientation == Configuration.ORIENTATION_PORTRAIT); 
    } else { 
     // in multi-window mode configuration orientation can be landscape even if activity is actually in portrait and vice versa 
     // Try determining from requested orientation (not entirely correct, because the requested orientation does not have to 
     // be the same as actual orientation (when they differ, this means that OS will soon rotate activity into requested orientation) 
     // Also not correct because, according to https://developer.android.com/guide/topics/ui/multi-window.html#running this orientation 
     // is actually ignored. 
     int requestedOrientation = getHostActivity().getRequestedOrientation(); 
     if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || 
       requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT || 
       requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT || 
       requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT) { 
      activityInPortrait = true; 
     } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || 
       requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE || 
       requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE || 
       requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE) { 
      activityInPortrait = false; 
     } else { 
      // what to do when requested orientation is 'behind', 'sensor', 'user', etc. ?!? 
      activityInPortrait = true; // just guess 
     } 
    } 

    if (activityInPortrait) { 
     Log.d(this, "Activity is in portrait"); 
     if (rotation == Surface.ROTATION_0) { 
      Log.d(this, "Screen orientation is 0"); 
      hostScreenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 
     } else if (rotation == Surface.ROTATION_180) { 
      Log.d(this, "Screen orientation is 180"); 
      hostScreenOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; 
     } else if (rotation == Surface.ROTATION_270) { 
      Log.d(this, "Screen orientation is 270"); 
      // natural display rotation is landscape (tablet) 
      hostScreenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 
     } else { 
      Log.d(this, "Screen orientation is 90"); 
      // natural display rotation is landscape (tablet) 
      hostScreenOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; 
     } 
    } else { 
     Log.d(this, "Activity is in landscape"); 
     if (rotation == Surface.ROTATION_90) { 
      Log.d(this, "Screen orientation is 90"); 
      hostScreenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; 
     } else if (rotation == Surface.ROTATION_270) { 
      Log.d(this, "Screen orientation is 270"); 
      hostScreenOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; 
     } else if (rotation == Surface.ROTATION_0) { 
      Log.d(this, "Screen orientation is 0"); 
      // natural display rotation is landscape (tablet) 
      hostScreenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; 
     } else { 
      Log.d(this, "Screen orientation is 180"); 
      // natural display rotation is landscape (tablet) 
      hostScreenOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; 
     } 
    } 
    return hostScreenOrientation; 
} 

private int getDisplayOrientation(WindowManager wm) { 
    if (DeviceManager.getSdkVersion() < 8) { 
     return wm.getDefaultDisplay().getOrientation(); 
    } 

    return wm.getDefaultDisplay().getRotation(); 
} 

private boolean isInMultiWindowMode() { 
    return Build.VERSION.SDK_INT >= 24 && getHostActivity().isInMultiWindowMode(); 
} 

protected Activity getHostActivity() { 
    Context context = getContext(); 
    while (context instanceof ContextWrapper) { 
     if (context instanceof Activity) { 
      return (Activity) context; 
     } 
     context = ((ContextWrapper) context).getBaseContext(); 
    } 
    return null; 
} 

EDIT: Я сообщил об этом также Android issue tracker.

+0

Обратите внимание, что ['isInMultiWindowMode()' имеет состояние гонки) (https://commonsware.com/blog/2016/06/07/isinmultiwindowmode-race-condition.html), добавляя к проблеме. – CommonsWare

+0

Для полноты я добавляю здесь [отчет о проблеме в нашем SDK] (https://github.com/PDF417/pdf417-android/issues/19), который также содержит скриншоты и некоторые обсуждения по теме. – DoDo

+0

Не могли бы вы просто посмотреть размеры экрана? [getMetrics()] (https://developer.android.com/reference/android/view/Display.html#getMetrics (android.util.DisplayMetrics)): «Если запрошено из показателей неактивности, будет сообщаться размер весь экран, основанный на текущем вращении и с вычитаемыми областями оформления системы ». Затем сравните ширину и высоту. – natario

ответ

5

Я не знаю, следует ли считать это решением или просто обходным путем.

Как вы говорите, ваши проблемы связаны с Android N и его многооконным режимом. Когда приложение находится в нескольких окнах, ваш Activity не привязан к полным размерам дисплея. Это переопределяет концепцию ориентации Activity. Цитирование Ian Lake:

Оказывается: «портрет» на самом деле просто означает, что высота больше, чем ширина и «пейзаж» означает, что ширина больше, чем высота. Так что , конечно, имеет смысл с учетом этого определения, что ваше приложение могло бы переходить от одного к другому при изменении размера.

Таким образом, нет никакой связи между Activity ориентация меняется и устройство физически поворачивается. (Я думаю только разумное использование ориентации деятельности меняется сейчас, чтобы обновить свои ресурсы.)

Поскольку вы заинтересованы в габаритах устройства, just get its DisplayMetrics. Цитирование документы,

Если запрошенная из контекста неактивности метрики сообщит размеру всего изображения на основе текущего вращения и субтрагированными области декорирования системы.

Таким образом, решение:

final Context app = context.getApplicationContext(); 
WindowManager manager = (WindowManager) app.getSystemService(Context.WINDOW_SERVICE); 
Display display = manager.getDefaultDisplay(); 
DisplayMetrics metrics = new DisplayMetrics(); 
display.getMetrics(metrics); 
int width = metrics.widthPixels; 
int height = metrics.heightPixels; 
boolean portrait = height >= width; 

значения ширины и высоты будут заменены (более или менее), когда устройство наклонено.

Если это работает, я бы лично запускать его каждый раз, удаляя isInMultiWindowMode() ветку, потому что

  • это не дорого
  • наши предположения стоят также в режиме без многооконный
  • его предположительно будет хорошо работать с любыми другими будущими видами режимов
  • избежать состояния гонки в isInMultiWindowMode()described by CommonsWare
+0

Спасибо! Как вы сказали, это работает каждый раз - как в многоэкранном режиме, так и в обычном режиме, поэтому больше нет необходимости в зависимости от конфигурации активности. Btw. решение, поскольку оно решает мою проблему. – DoDo

+0

Это совершенно неправильно для планшетов, которые имеют ориентацию по умолчанию по умолчанию (например, Samsung Tab SM-P600). Вы просто не можете полагаться на ** widthPixels ** и ** heightPixels **, что дает точное значение в соответствии с текущей ориентацией. –

+0

@Igor, что ширина и высота должны быть несовместимыми с устройством, то есть ориентация по умолчанию не должна иметь значения. Я не сделал этого, но я уверен, что DoDo проверил это. – natario

1

Я думал, что вы можете использовать акселерометр, чтобы определить, где находится «вниз» - и, следовательно, ориентация телефона. The Engineer Guy explains, что так делает сам телефон.

Я искал здесь на SO для способа сделать это и нашел this answer.В основном вам нужно проверить, какой из 3-х акселерометров обнаруживает наиболее значительную составляющую гравитационного тяги, которая, как вы знаете, составляет около 9,8 м/с² около земли. Вот фрагмент кода из него:

private boolean isLandscape; 

mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE); 
mSensorManager.registerListener(mSensorListener,  mSensorManager.getDefaultSensor(
             Sensor.TYPE_ACCELEROMETER),1000000); 
private final SensorEventListener mSensorListener = new SensorEventListener() { 
    @Override 
    public void onSensorChanged(SensorEvent mSensorEvent) { 
     float X_Axis = mSensorEvent.values[0]; 
     float Y_Axis = mSensorEvent.values[1]; 

     if((X_Axis <= 6 && X_Axis >= -6) && Y_Axis > 5){ 
     isLandscape = false; 
     } 
     else if(X_Axis >= 6 || X_Axis <= -6){ 
     isLandscape = true; 
     } 

    } 

    public void onAccuracyChanged(Sensor sensor, int accuracy) { 
    } 
}; 

Будьте осторожны, так как этот код может не работать в любой ситуации, когда вам необходимо принять в сценарии счета, как находясь на остановке/ускорение поезда или переместить телефон быстро в игре - вот orders of magnitude page on wiki, чтобы вы начали. Похоже, вы в безопасности со значениями, которые Халил ввел в свой код (в его ответе), но я бы взял дополнительную осторожность и исследование того, какие значения могут быть сгенерированы в разных сценариях.

Это не безупречная идея, но я думаю, что до тех пор, пока API будет построен так, как он есть - без предоставления вам рассчитанной ориентации - я думаю, что это выгодное решение.

+0

Хорошая идея, но для меня нетрудно рассчитать, что такое «вниз» (я получаю это косвенно с 'getDisplayOrientation') - проблема заключается в том, чтобы обнаружить« какое преобразование имеет активность, сделанную для моего представления, чтобы я мог противостоять ей » , Проблема в том, что когда активность вращается, она также поворачивает все ее виды, включая представление, отображающее предварительный просмотр камеры. Поэтому мне нужно знать точное вращение, которое Android сделал, чтобы противостоять его локальным вращением внутри самого окна предварительного просмотра камеры. – DoDo

+1

Чтобы лучше отобразить проблему - представьте, что информация о датчике из вашего кода правильно заключает, что устройство находится в ландшафте, потому что пользователь фактически удерживает его в ландшафте. Тем не менее, пользователь отключил поворот экрана в настройках, и действия с несколькими окнами фактически находятся в портрете. Для отображения предварительного просмотра камеры, как и в ландшафте, это неправильно, так как это приведет к некорректному вращению предварительного просмотра камеры. Это проблема, связанная с многооконной функцией. – DoDo

+0

Да, я понимаю, что вы имеете в виду. Вы правы, мое решение не поможет. –