Как определить правильную ориентацию устройства в многоэкранном режиме Android N?
Из Многооконная документация:
Отключенные функции в многооконном режиме
Некоторые функции отключены или игнорируются, когда устройство находится в многооконном режиме, поскольку они не имеют смысла для активности, которая может совместно использовать экран устройства с другими действиями или приложениями. К таким функциям относятся:
- Некоторые параметры настройки пользовательского интерфейса системы отключены; например, приложения не могут скрыть строку состояния, если они не работают в полноэкранном режиме.
- Система игнорирует изменения атрибута android: screenOrientation.
Я получаю, что для большинства приложений нет смысла различать портретные и ландшафтные режимы, однако я работаю над SDK, который содержит просмотр камеры, который пользователь может наложить на любую деятельность, которую они хотят - включая активность, поддерживающую многооконный режим Режим. Проблема в том, что просмотр камеры содержит SurfaceView/TextureView, который отображает предварительный просмотр камеры и для правильного отображения предварительного просмотра во всех ориентациях деятельности, для правильного поворота изображения требуется знание правильной ориентации активности.
Проблема в том, что мой код, который вычисляет правильную ориентацию активности, исследует текущую ориентацию конфигурации (портретную или альбомную) и текущее вращение экрана. Проблема в том, что в режиме многооконного режима ориентация текущей конфигурации не отражает реальную ориентацию активности. Это приводит к тому, что просмотр камеры поворачивается на 90 градусов, потому что Android сообщает о другой конфигурации, чем ориентация.
Мое текущее обходное решение - проверить запрашиваемую ориентацию деятельности и использовать ее в качестве основы, но есть две проблемы с этим:
- запрашиваемая ориентация деятельности не должна отражать действительную ориентацию активности (т.е. запрос может все еще не выполняться).
- запрошенная ориентация активности может быть "позади", "датчик", "пользователь" и т.д., которая не раскрывает никакой информации о текущей ориентации активности.
- В соответствии с документацией ориентация экрана фактически игнорируется в многооконном режиме, поэтому 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.
Ответы
Ответ 1
Я не знаю, следует ли это рассматривать как решение или просто обходной путь.
Как вы говорите, ваши проблемы связаны с Android N и его многооконным режимом. Когда приложение находится в нескольких окнах, ваш Activity
не привязан к полным размерам дисплея. Это переопределяет концепцию ориентации Activity
. Цитирование Ian Lake:
Оказывается: "портрет" на самом деле просто означает, что высота больше, чем ширина и "пейзаж" означает, что ширина больше высоты. Так Разумеется, имеет смысл с учетом этого определения, что ваше приложение может переходить от одного к другому при изменении размера.
Итак, нет никакой связи между изменением ориентации Activity
и физическим поворотом устройства. (Я думаю, что разумное использование изменения ориентации активности только - это обновление ваших ресурсов.)
Поскольку вас интересуют размеры устройства, просто получите его 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()
описанного в CommonsWare
Ответ 2
Я думал, что вы можете использовать акселерометр, чтобы определить, где "вниз" - и, следовательно, ориентация телефона. Инженер Гай объясняет, что тот способ, которым сам телефон это делает.
Я искал здесь на SO для способа сделать это и нашел этот ответ. В основном вам нужно проверить, какой из 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) {
}
};
Будьте осторожны, так как этот код может не работать в любой ситуации, так как вам нужно учитывать сценарии, например, быть на остановке/ускоряющем поезде или быстро перемещать телефон в игре - здесь порядков на странице wiki, чтобы вы начали. Похоже, вы в безопасности со значениями, которые Халил поставил в своем коде (в его ответе), но я бы взял дополнительную осторожность и исследование того, какие значения могут быть сгенерированы в разных сценариях.
Это не безупречная идея, но я думаю, что до тех пор, пока API будет построен так, как он есть - не позволяя получить их расчетную ориентацию - я думаю, что это выгодное решение.