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

时间:2016-12-23 16:35:56

标签: android camera android-camera android-7.0-nougat multi-window

来自Multi-Window documentation

  

多窗口模式中的已禁用功能

     

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

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

对于大多数应用而言,我认为在纵向和横向模式之间区分是没有意义的,但我正在开发包含相机视图的SDK,用户可以将其放在任何他们想要的活动上 - 包括支持多个活动的活动-窗口模式。问题是摄影机视图包含显示摄像机预览的SurfaceView / TextureView,并且为了在所有活动方向上正确显示预览,需要了解正确的活动方向,以便可以正确旋转摄像机预览。

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

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

  1. 所请求的活动方向不必反映实际的活动方向(即可能仍未满足请求)
  2. 请求的活动方向可以在&#39;后面,&#39;传感器&#39;用户&#39;等等,它们不会显示有关当前活动方向的任何信息。< / LI>
  3. 根据documentation,在多窗口模式下实际上忽略了屏幕方向,因此1.和2.只是不会工作
  4. 即使在多窗口配置中,有没有办法可以稳健地计算正确的活动方向?

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

    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 issue tracker报告了这一点。

2 个答案:

答案 0 :(得分:5)

我不知道这是否应该被视为解决方案或只是解决方法。

正如您所说,您的问题来自Android N及其多窗口模式。当应用程序处于多窗口时,您的Activity与全部显示尺寸无关。这重新定义了Activity方向的概念。引用Ian Lake

  

原来:“肖像”真的只是意味着高度大于   宽度和“横向”表示宽度大于高度。所以   考虑到这个定义,你的应用程序肯定是有意义的   在调整大小时可以从一个过渡到另一个。

因此,Activity方向更改与设备物理旋转之间不再存在链接。 (我认为现在唯一合理使用活动方向更改是更新您的资源。

由于您对设备维度感兴趣,just get its 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() described by CommonsWare
  • 的竞争状况

答案 1 :(得分:1)

我认为你可以利用加速度计来检测“向下”的位置 - 从而确定手机的方向。 The Engineer Guy explains这就是手机本身的方式。

我在这里搜索了SO的方法来找到this answer。基本上你需要检查3个加速度计中哪一个检测到引力的最重要组成部分,你知道它在地球附近大约是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) {
    }
};  

请注意,因为此代码可能无法在所有情况下使用,因为您需要考虑在停止/加速火车或在游戏中快速移动手机等情况 - 这里是orders of magnitude page on wiki来帮助您开始。看起来你对Khalil在他的代码中的价值是安全的(在他的回答中),但我会特别小心并研究在不同场景中可能产生的值。

这不是一个完美的想法,但我认为只要API按照它的方式构建 - 不允许你获得他们的计算方向 - 我认为这是一个有益的解决方法。