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

多窗口文档 :

在多窗口模式下禁用function

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

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

我得到的是,大多数应用程序没有意义,以区分纵向和横向模式,但是我正在开发的SDK包含相机视图,用户可以把他们希望的任何活动,包括支持多窗口模式的活动。 问题在于摄像机视图包含了显示摄像机预览的SurfaceView / TextureView,并且为了在所有活动方向上正确显示预览,需要关于正确的活动方向的知识,以便正确地旋转摄像机预览。

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

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

  1. 所请求的活动方向不一定要反映实际的活动方向(即请求可能仍不能满足)
  2. 请求的活动方向可以是“后面”,“传感器”,“用户”等,其不显示关于当前活动方向的任何信息。
  3. 根据文档 ,在多窗口模式下屏幕方向实际上被忽略,所以1.和2.就不能工作

即使在多窗口configuration中,是否有任何方法可以稳健地计算正确的活动方向?

这是我目前使用的代码(查看有问题的部分的注释):

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

编辑:我已经报道这也是Android的问题跟踪 。

我不知道这应该被认为是一个解决scheme或只是一个解决方法。

正如你所说,你的问题与Android N及其多窗口模式。 当应用程序在多窗口中时,您的“ Activity不受限于完整的显示尺寸。 这重新定义了Activity导向的概念。 引用伊恩湖 :

结果:“肖像”的确意味着高度大于宽度,“景观”意味着宽度大于高度。 因此,从这个定义出发,您的应用程序在resize时可以从一个转换到另一个。

所以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; 

宽度和高度值将在设备倾斜时交换(或多或less)。

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

  • 这并不昂贵
  • 我们的假设也站在非多窗口模式
  • 它可能会与其他任何未来的模式很好地工作
  • 避免CommonsWare描述的isInMultiWindowMode()的竞态条件

我以为你可以利用加速度计来检测“下”的位置,从而确定手机的方向。 工程师盖解释说 ,这就是电话本身的方式。

我在这里search的方式来做到这一点,并find了答案 。 基本上你需要检查三个加速度计中哪一个检测到引力最重要的部分,你知道在地球附近大概是9.8m /s²。 这里是它的代码片段:

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

要小心,因为这个代码可能在任何情况下都不起作用,因为你需要考虑到在停止/加速列车中或者在游戏中快速移动电话这样的场景 – 这里是维基上的大小页面 ,让你开始。 Khalil的代码(在他的回答中)看起来很安全,但我会特别小心并研究在不同情况下可能产生的值。

这不是一个完美的想法,但我认为,只要API是按照它的方式构build的,而不是让你得到他们的计算方向 – 我认为这是一个有益的解决方法。