Jak określić prawidłową orientację urządzenia w trybie multi-window systemu Android N?

Z dokumentacji wielowarstwowej :

Funkcje wyłączone w trybie Multi-window

Niektóre funkcje są wyłączone lub ignorowane, gdy urządzenie jest w trybie wielu okien, ponieważ nie mają sensu dla aktywności, która może być dzielenie ekranu urządzenia z innymi aktywnościami lub aplikacjami. Do takich funkcji należą:

  • niektóre opcje dostosowywania interfejsu systemu są wyłączone; na przykład aplikacje nie mogą ukryć paska stanu, Jeśli nie są uruchomione w trybie pełnoekranowym.
  • system ignoruje zmiany atrybutu android: screen orientation.

Rozumiem, że dla większości aplikacji nie ma sensu rozróżniać między trybami pionowym i poziomym, jednak pracuję na SDK, który zawiera widok z kamery, który użytkownik może włączyć dowolną aktywność, której chce - w tym aktywność, która obsługuje tryb wielu okien. Problem polega na tym, że widok kamery zawiera SurfaceView/TextureView, który wyświetla podgląd kamery i w kolejności aby prawidłowo wyświetlać podgląd we wszystkich orientacjach aktywności, wymagana jest wiedza na temat prawidłowej orientacji aktywności, dzięki czemu podgląd kamery może być prawidłowo obracany.

Problem polega na tym, że mój kod, który oblicza prawidłową orientację aktywności, badając bieżącą orientację konfiguracji (pionową lub poziomą) i bieżący obrót ekranu. Problem polega na tym, że w trybie multi-window bieżąca orientacja konfiguracji nie odzwierciedla rzeczywistej orientacji aktywności. Wynika to z podgląd kamery jest obracany o 90 stopni, ponieważ Android zgłasza inną konfigurację niż orientacja.

Moim obecnym obejściem jest sprawdzenie orientacji na żądaną aktywność i użycie jej jako podstawy, ale są z tym dwa problemy:

    Jeśli chcesz dowiedzieć się więcej na temat tego, co jest potrzebne, skontaktuj się z nami, aby dowiedzieć się więcej na temat tego, co jest potrzebne.]}
  1. żądana orientacja aktywności może być "z tyłu", "czujnik", "użytkownik" itp. co robi nie ujawnia żadnych informacji o bieżącej orientacji aktywności.
  2. zgodnie z dokumentacja orientacja ekranu jest w rzeczywistości ignorowana w trybie wielookienkowym, więc 1. i 2. just won ' t work

Czy Jest jakiś sposób, aby solidnie obliczyć prawidłową orientację aktywności nawet w konfiguracji wielo-okienkowej?

Oto Mój kod, którego obecnie używam (Zobacz komentarze do problematycznych części):

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: zgłosiłem to również do problem z Androidem tracker .

Author: natario, 2016-12-23

2 answers

Nie wiem, czy to powinno być traktowane jako rozwiązanie, czy tylko obejście.

Jak mówisz, Twoje problemy pochodzą z Androidem N i jego trybem wielu okien. Gdy aplikacja znajduje się w wielu oknach, twój Activity nie jest powiązany z pełnymi wymiarami wyświetlacza. To na nowo definiuje pojęcie orientacji Activity. Cytowanie Ian Lake :

Okazuje się, że "portret" oznacza po prostu, że wysokość jest większa niż szerokość i "krajobraz" oznacza, że szerokość jest większa niż wysokość. Więc z pewnością ma to sens, mając na uwadze tę definicję, że Twoja aplikacja może przejść z jednego do drugiego podczas zmiany rozmiaru.

Więc nie ma już żadnego związku między zmianą orientacji a fizycznym obrotem urządzenia. (myślę, że tylko rozsądnym wykorzystaniem zmian orientacji aktywności jest teraz aktualizacja zasobów.)

Ponieważ interesują Cię wymiary urządzenia, po prostu zdobądź jego DisplayMetrics. Cytowanie dokumentów,

Na żądanie z kontekstu braku działalności metryki będą raportować Rozmiar całego wyświetlacza na podstawie bieżącego obrót i z odejmowanymi obszarami dekoracji systemu.

Więc rozwiązaniem jest:

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;

Wartości szerokości i wysokości zostaną zamienione (mniej więcej), gdy urządzenie zostanie przechylone.

Jeśli to zadziała, osobiście uruchamiałbym go za każdym razem, usuwając isInMultiWindowMode() gałąź, ponieważ

    To nie jest drogie.]}
  • nasze założenia stoją także w tryb non-multi-window
  • Prawdopodobnie będzie działać dobrze z innymi przyszłymi rodzajami trybów
  • unikasz stanu rasy isInMultiWindowMode() opisane przez CommonsWare
 5
Author: natario,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-30 22:11:11

Myślałem, że możesz użyć akcelerometru, aby wykryć, gdzie jest " dół " - a tym samym orientację telefonu. Inżynier wyjaśnia, że tak właśnie robi telefon.

Szukałam tutaj NA SO, aby to zrobić i znalazłam tę odpowiedź . Zasadniczo musisz sprawdzić, który z 3 akcelerometrów wykrywa najbardziej znaczący składnik przyciągania grawitacyjnego, o którym wiesz, że wynosi około 9,8 m / s2 w pobliżu Ziemi. Oto fragment kodu od niego:

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) {
    }
};  

Bądź ostrożny, ponieważ ten kod może nie działać w każdej sytuacji, ponieważ musisz wziąć pod uwagę scenariusze, takie jak zatrzymanie/przyspieszenie pociągu lub szybkie poruszanie telefonem w grze - oto strona rzędów wielkości na wiki, aby zacząć. Wygląda na to, że jesteś bezpieczny z wartościami, które Khalil umieścił w swoim kodzie (w odpowiedzi), ale chciałbym zachować szczególną ostrożność i zbadać, jakie wartości mogą być generowane w różnych scenariuszach.

It ' s not a nieskazitelny pomysł, ale myślę, że tak długo, jak API jest zbudowany tak, jak to jest - whithout pozwalając uzyskać ich obliczoną orientację - myślę, że jest to korzystne obejście.

 1
Author: et_l,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:05