Подтвердить что ты не робот

Как определить правильную ориентацию устройства в многоэкранном режиме 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.

4b9b3361

Ответ 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 будет построен так, как он есть - не позволяя получить их расчетную ориентацию - я думаю, что это выгодное решение.