如何在Android N多窗口模式下确定正确的设备方向?

DoD*_*oDo 23 camera android multi-window android-camera android-7.0-nougat

多窗口文档:

多窗口模式下禁用的功能

当设备处于多窗口模式时,某些功能被禁用或忽略,因为它们对于可能与其他活动或应用共享设备屏幕的活动没有意义.这些功能包括:

  • 某些系统UI自定义选项已禁用; 例如,如果应用程序未以全屏模式运行,则无法隐藏状态栏.
  • 系统忽略对android:screenOrientation属性的更改.

我得到了大多数应用程序,纵向和横向模式之间的区别是没有意义的,但是我正在使用SDK,其中包含用户可以进行任何活动的摄像机视图 - 包括支持多窗口模式的活动.问题是摄像机视图包含显示摄像机预览的SurfaceView/TextureView,并且为了在所有活动方向上正确显示预览,需要有关正确活动方向的知识,以便可以正确旋转摄像机预览.

问题是我的代码通过检查当前配置方向(纵向或横向)和当前屏幕旋转来计算正确的活动方向.问题是在多窗口模式下,当前配置方向不反映真实的活动方向.这会导致相机预览旋转90度,因为Android报告的配置与方向不同.

我目前的解决方法是检查所请求的活动方向并以此为基础,但有两个问题:

  1. 请求的活动方向不必反映实际的活动方向(即可能仍未满足请求)
  2. 请求的活动方向可以是"后方","传感器","用户"等,它们不会显示有关当前活动方向的任何信息.
  3. 根据文档,在多窗口模式下实际上忽略了屏幕方向,因此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;
}
Run Code Online (Sandbox Code Playgroud)

编辑:我也向Android问题跟踪器报告过这个问题.

nat*_*rio 5

我不知道这应该被视为解决方案还是仅仅是一种解决方法.

如你所说,你的问题来自于Android N及其多窗口模式.当应用程序处于多窗口时,您Activity不依赖于完整的显示尺寸.这重新定义了Activity方向的概念.引用伊恩湖:

事实证明:"肖像"实际上只意味着高度大于宽度,"风景"意味着宽度大于高度.因此,考虑到这个定义,您的应用程序可以在调整大小时从一个过渡到另一个,这当然是有道理的.

因此,在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;
Run Code Online (Sandbox Code Playgroud)

当设备倾斜时,将交换宽度和高度值(或多或少).

如果这样做,我会亲自每次运行它,删除isInMultiWindowMode()分支,因为

  • 它不贵
  • 我们的假设也处于非多窗口模式
  • 它可能适用于任何其他未来的模式
  • 你避免了CommonsWare描述的竞争条件isInMultiWindowMode()