如何在Android中检测布局方向的变化?

127

我刚刚实现了一个方向变化功能 - 比如当布局从竖屏变为横屏(或反之亦然)时。如何检测方向变化事件何时完成。

OrientationEventListener 无法正常工作。如何获取有关布局方向更改事件的通知?


在我的情况下,当屏幕方向改变时,onCreate() 方法被调用。 - J743799323686
11个回答

325

使用Activity的onConfigurationChanged方法,如Android文档中“处理运行时变化”指南所述:

@Override public void onConfigurationChanged(@NotNull Configuration
newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks the orientation of the screen
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
    } }

You also have to edit the appropriate element in your manifest file to include the android:configChanges:

<activity android:name=".MyActivity"
          android:configChanges="orientation|keyboardHidden"
          android:label="@string/app_name">

NOTE: with Android 3.2 (API level 13) or higher, the "screen size" also changes when the device switches between portrait and landscape orientation. Thus, if you want to prevent runtime restarts due to orientation change when developing for API level 13 or higher, you must declare android:configChanges="orientation|screenSize" for API level 13 or higher.


3
这很好,但它没有加载 "layout-land" 文件夹中的横向布局 XML 文件,而是使用了竖向 XML 布局文件。 - Programmer
29
当你在代码中包含"android:configChanges="orientation""时,你在声明你将处理屏幕方向的变化,这意味着当你切换到横屏时,你的活动不会重新创建而是调用onConfigurationChanged。如果这样做不是你想要的,你可以使用一个变量来记录你的活动方向,并每次在onPause(例如)时检查方向是否仍然相同。 - Elena
14
如果您正在开发API级别为13(3.2)或更高版本的应用程序,则应指定android:configChanges="orientation|screenSize",否则onConfigurationChanged将不会被调用。参考链接:http://developer.android.com/guide/topics/resources/runtime-changes.html#HandlingTheChange - Arne
1
嗨,我正在使用API 10(Android 2.3.3)构建我的应用程序。当我使用android:configChanges="orientation|keyboardHidden"时,它可以完美地识别方向。但是我的Nexus S运行的是Android 4.1.2,无法检测到方向变化。我该怎么办? - Karthik Andhamil
2
在您的清单文件中使用**android:configChanges="orientation|screenSize"**而不是android:configChanges="orientation|keyboardHidden",并进行检查... !!! - anon
显示剩余3条评论

16

如果在layout-land文件夹中加载布局,这意味着您有两个单独的布局,那么您需要在onConfigurationChanged方法中使用setContentView

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks the orientation of the screen
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        setContentView(R.layout.yourxmlinlayout-land);
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
        setContentView(R.layout.yourxmlinlayoutfolder);
    }
}

如果您只有一个布局,则此方法中没有必要使用setContentView。简单地:
@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
}

20
不要覆盖,只需使用layout/main.xml和layout-land/main.xml。 - Jared Burrows
@JaredBurrows:请查看http://stackoverflow.com/questions/23789605/why-is-my-orientation-change-going-unnoticed - B. Clay Shannon-B. Crow Raven
我想检测我的活动是否因屏幕旋转而被终止。我实现了onConfigurationChanged(Configuration newConfig)方法(如此答案中所述),但我没有自己处理它,而是设置了一个布尔标志,然后调用了recreate()方法,以便Android以正常方式重新创建活动。 - dazed
recreate() 方法的一个问题是它会导致屏幕短暂闪黑。因此,我采用了另一种方法:(1)在 onCreate(...) 中保存屏幕宽度,(2)在任何生命周期方法中比较保存的屏幕宽度和当前屏幕宽度,当活动被终止时(例如 onSaveInstanceState(...))。 - dazed

12

我想向您展示一种在onConfigurationChanged后保存所有Bundle的方法:

在您的类之后创建一个新的Bundle:

public class MainActivity extends Activity {
    Bundle newBundy = new Bundle();

接下来,在 "protected void onCreate" 后面添加这些内容:

接下来,在 "protected void onCreate" 方法中添加以下代码:

@Override
public void onConfigurationChanged(Configuration newConfig) {
     super.onConfigurationChanged(newConfig);
     if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            onSaveInstanceState(newBundy);
        } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
            onSaveInstanceState(newBundy);
        }
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putBundle("newBundy", newBundy);
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    savedInstanceState.getBundle("newBundy");
}
如果您将此内容添加到MainActivity中创建的所有类中,它们都将被保存。
但最好的方法是将其添加到您的AndroidManifest中:
<activity name= ".MainActivity" android:configChanges="orientation|screenSize"/>

8
这是具有误导性的。实现onConfigurationChanged()可以使您的instanceState在方向更改时不被销毁,因此您不需要保存它。您可能希望为活动生命周期中的其他事件保存它,但我不确定。 - izzy

7

使用这种方法

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT)
    {
        Toast.makeText(getActivity(),"PORTRAIT",Toast.LENGTH_LONG).show();
       //add your code what you want to do when screen on PORTRAIT MODE
    }
    else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
    {
        Toast.makeText(getActivity(),"LANDSCAPE",Toast.LENGTH_LONG).show();
        //add your code what you want to do when screen on LANDSCAPE MODE
   }
}

请不要忘记在 Androidmanifest.xml 文件中添加此内容。
android:configChanges="orientation|screenSize"

like this

<activity android:name=".MainActivity"
          android:theme="@style/Theme.AppCompat.NoActionBar"
          android:configChanges="orientation|screenSize">

    </activity>

5

如果对于某些较新的开发者有用的话,因为上面没有指定,我想说得很明确:

如果使用onConfigurationChanged,您需要两个东西:

  1. 在Activity类中编写一个onConfigurationChanged方法

  2. 在您的清单文件中指定哪些配置更改将由您的onConfigurationChanged方法处理

上面的答案中的清单片段,虽然毫无疑问是适用于该清单所属的特定应用程序,但却不是您需要在您的清单文件中添加以触发Activity类中的onConfigurationChanged方法的确切内容。也就是说,以下清单条目可能不适合您的应用程序。

<activity name= ".MainActivity" android:configChanges="orientation|screenSize"/>

在上述清单条目中,有各种 Android 操作用于 android:configChanges="",可以触发您的 Activity 生命周期中的 onCreate。
这非常重要 - 未在清单中指定的操作是触发 onCreate 的操作在清单中指定的操作是触发您的 Activity 类中的 onConfigurationChanged 方法的操作
因此,您需要确定哪些配置更改需要自己处理。对于像我这样的 Android 百科全书挑战者,我使用了 Android Studio 中的快速提示弹出窗口,并添加了几乎所有可能的配置选项。列出所有这些基本上意味着我将处理所有内容,并且由于配置而不会调用 onCreate。
<activity name= ".MainActivity" android:configChanges="screenLayout|touchscreen|mnc|mcc|density|uiMode|fontScale|orientation|keyboard|layoutDirection|locale|navigation|smallestScreenSize|keyboardHidden|colorMode|screenSize"/>

显然,我不想处理所有事情,所以我开始逐一排除上述选项。每次删除后重新构建和测试我的应用程序。

另一个重要的观点:如果只有一个配置选项被自动处理并触发了您的onCreate(您在上面的清单中没有列出它),那么它看起来就像 onConfigurationChanged 不起作用。您必须将所有相关选项放入清单中。

最初有3个选项会触发onCreate,然后我在S10 +上进行了测试,仍然会得到onCreate,因此我不得不再次进行排除练习,并且我还需要 | screenSize 。因此,请在多个平台上进行测试。

<activity name= ".MainActivity" android:configChanges="screenLayout|uiMode|orientation|screenSize"/>

所以,虽然我相信有人能够挑出这个问题:

  1. 在您的活动类中添加onConfigurationChanged方法,并使用TOAST或LOG进行验证,以便您可以看到它何时工作。

  2. 将所有可能的配置选项添加到您的清单中。

  3. 通过测试您的应用程序来确认onConfigurationChanged方法是否正常工作。

  4. 逐个从您的清单文件中删除每个配置选项,在每次测试后测试您的应用程序。

  5. 尽可能在各种设备上进行测试。

  6. 不要将我上面的片段复制/粘贴到您的清单文件中。 Android更新更改了列表,因此使用弹出式Android Studio提示来确保您获取它们全部。

希望这节省了某些人的时间。

以下仅供参考的onConfigurationChanged方法。在生命周期中,onConfigurationChanged在新方向可用之后被调用,但在UI被重新创建之前被调用。因此,我的第一个if检查方向是否正确,然后第二个嵌套的if查看我的UI ImageView的可见性是否正确。

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        // Checks the orientation of the screen
        if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            pokerCardLarge = findViewById(R.id.pokerCardLgImageView);

            if(pokerCardLarge.getVisibility() == pokerCardLarge.VISIBLE){
                Bitmap image = ((BitmapDrawable)pokerCardLarge.getDrawable()).getBitmap();
                pokerCardLarge.setVisibility(pokerCardLarge.VISIBLE);
                pokerCardLarge.setImageBitmap(image);
            }

        } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
            pokerCardLarge = findViewById(R.id.pokerCardLgImageView);

            if(pokerCardLarge.getVisibility() == pokerCardLarge.VISIBLE){
                Bitmap image = ((BitmapDrawable)pokerCardLarge.getDrawable()).getBitmap();
                pokerCardLarge.setVisibility(pokerCardLarge.VISIBLE);
                pokerCardLarge.setImageBitmap(image);
            }

        }
    }

2

我想指出,在AndroidManifest.xml文件中<activity>标签内没有android:configChanges="orientation"这一行,onConfigurationChanged()回调函数甚至不会被触发。 - Sreekanth Karumanaghat

1
创建一个OrientationEventListener类的实例并启用它。
OrientationEventListener mOrientationEventListener = new OrientationEventListener(YourActivity.this) {
    @Override
    public void onOrientationChanged(int orientation) {
        Log.d(TAG,"orientation = " + orientation);
    }
};

if (mOrientationEventListener.canDetectOrientation())
    mOrientationEventListener.enable();

3
请勿破坏您的答案。如果您想删除它,可以在答案底部左侧找到删除按钮。 - CalvT

1
如果其他人像我一样花费了很长时间,那么请注意 - 它不能在模拟器上工作,必须是真实设备。

同样的问题。这个只能在实际设备上运行。我无法在模拟器中使用 Pixel 3a API 30 VM 运行它。 - Paul Lefebvre

1

对于Kotlin的最简实现 - 只在屏幕从竖屏 <--> 横屏切换时触发,如果需要检测翻转(180度),则需要使用重力传感器值。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

val rotation = windowManager.defaultDisplay.rotation

    when (rotation) {            
        0 -> Log.d(TAG,"at zero degree")
        1 -> Log.d(TAG,"at 270 degree")
        2 -> Log.d(TAG,"at 180 degree")
        3 -> Log.d(TAG,"at 90 degree")


    }
}

1

我只是想提出另一种选择,这将涉及到一些人,正如上面所解释的:

android:configChanges="orientation|keyboardHidden" 

这意味着我们需要明确声明要注入的布局。

如果我们想要保留由layout-land和layout文件夹自动注入的功能,只需将其添加到onCreate中即可。

    if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
        getSupportActionBar().hide();

    } else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
        getSupportActionBar().show();
    }

在这里,我们根据手机的方向来显示或隐藏操作栏。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接