Android开发中,大多APP可能根据实际情况直接将APP的界面方向设死了,或竖屏或横屏。但是,我们还是会遇到横竖屏切换的功能需求,不管是通过物理重力感应触发,还是用户手动触发。所以,我们有必要去弄清楚Android中横竖屏切换到底做了什么。
Follow me ......
值 | 描述 |
---|---|
unspecified | 默认值。系统自动选择屏幕方向 |
behind | 跟activity堆栈中的下面一个activity的方向一致 |
landscape | 横屏方向,显示的宽比高长 |
portrait | 竖屏方向,显示的高比宽长 |
sensor | 由设备的物理方向传感器决定,如果用户旋转设备,这屏幕就会横竖屏切换 |
nosensor | 忽略物理方向传感器,这样就不会随着用户旋转设备而横竖屏切换了("unspecified"设置除外) |
user | 用户当前首选的方向 |
reverseLandscape | API 9 以上,反向横屏 |
reversePortrait | API 9 以上,反向竖屏 |
sensorLandscape | API 9 以上,横屏,但是可以根据 物理方向传感器来切换正反向横屏 |
sensorPortrait | API 9 以上,竖屏,但是可以根据 物理方向传感器来切换正反向竖屏 |
fullSensor | API 9 以上,上下左右四个方向,由物理方向传感器决定 |
locked | API 18 以上,锁死当前屏幕的方向 |
如果你上传应用到 Google Play,需要注意以下提示:
Note: When you declare one of the landscape or portrait values, it is considered a hard requirement for the orientation in which the activity runs. As such, the value you declare enables filtering by services such as Google Play so your application is available only to devices that support the orientation required by your activities. For example, if you declare either"landscape", "reverseLandscape", or "sensorLandscape", then your application will be available only to devices that support landscape orientation. However, you should also explicitly declare that your application requires either portrait or landscape orientation with the
element. For example,. This is purely a filtering behavior provided by Google Play (and other services that support it) and the platform itself does not control whether your app can be installed when a device supports only certain orientations.
值 | 描述 |
---|---|
mcc | IMSI移动台的国家代码(MCC)发生变化——一个SIM被探测到并且更新MCC |
mnc | IMSI移动台的网络代码(MNC)发生变化——一个SIM被探测到并且更新MNC |
locale | 区域发生变化——用户选择了一个文本需要显示的新语言 |
keyboard | 键盘类型发生变化——例如:用户插入了外接键盘。 |
keyboardHidden | 键盘的可访问性发生变化——例如:用户发现了硬件键盘。 |
screenLayout | 屏幕布局发生变化——这个会导致显示不同的Activity。 |
orientation | 屏幕方向发生变化——用户旋转了屏幕。注意:如果应用程序的目标API级别是13或更高(通过属性minSdkVersion和属性targetSdkVersion声明),你也需要声明配置项screenSize,因为这将在设备选择肖像和屏幕方向时发生改变。 |
screenSize | 当前可用屏幕大小发生变化。这代表一个当前可用大小的变化,和当前的比率相关,因此当用户选择不同的画面和图像,会发生变化。然而,如果你的程序目标API级别是12或更低,你的Activity总是会自己处理这个配置变化(这个变化不会引起Activity的重启,甚至在Android 3.2或更新的设备上)。在API级别13里加入的。 |
smallestScreenSize | 物理屏幕大小的变化。不管方向的变化,仅仅在实际物理屏幕打包变化的时候,如:外接显示器。这个配置项的变化引起在smallestWidth configuration里的变化。然而,如果你的程序目标API级别是12或更低,你的Activity将自己处理这个变化(这个变化不会引起Activity的重启,甚至在Android 3.2或更新的设备上)在API级别13里加入的。 |
layoutDirection | 布局方向变化。例如书写方式从左向右(LTR)转换为从右向左(RTL) |
想了解更多就看官方文档。
通过打印Activity各个生命周期的执行情况,我们根据以下几种情况来分析:
android:configChanges="orientation"
android:configChanges="orientation|keyboardHidden"
11-02 20:17:44.898 13324-13324/com.ys.yoosir.screenconfigchange D/MainActivity: -- onPause
11-02 20:17:45.008 13324-13324/com.ys.yoosir.screenconfigchange D/MainActivity: -- onSaveInstanceState
11-02 20:17:45.008 13324-13324/com.ys.yoosir.screenconfigchange D/MainActivity: -- onStop
11-02 20:17:45.018 13324-13324/com.ys.yoosir.screenconfigchange D/MainActivity: -- onDestroy
11-02 20:17:45.038 13324-13324/com.ys.yoosir.screenconfigchange D/MainActivity: -- onCreate - orientation
11-02 20:17:45.088 13324-13324/com.ys.yoosir.screenconfigchange D/MainActivity: -- onStart
11-02 20:17:45.088 13324-13324/com.ys.yoosir.screenconfigchange D/MainActivity: -- onRestoreInstanceState
11-02 20:17:45.088 13324-13324/com.ys.yoosir.screenconfigchange D/MainActivity: -- onResume
11-02 20:19:00.268 13324-13324/com.ys.yoosir.screenconfigchange D/MainActivity: -- onPause
11-02 20:19:00.268 13324-13324/com.ys.yoosir.screenconfigchange D/MainActivity: -- onSaveInstanceState
11-02 20:19:00.268 13324-13324/com.ys.yoosir.screenconfigchange D/MainActivity: -- onStop
11-02 20:19:00.268 13324-13324/com.ys.yoosir.screenconfigchange D/MainActivity: -- onDestroy
11-02 20:19:00.318 13324-13324/com.ys.yoosir.screenconfigchange D/MainActivity: -- onCreate - orientation
11-02 20:19:00.338 13324-13324/com.ys.yoosir.screenconfigchange D/MainActivity: -- onStart
11-02 20:19:00.338 13324-13324/com.ys.yoosir.screenconfigchange D/MainActivity: -- onRestoreInstanceState
11-02 20:19:00.338 13324-13324/com.ys.yoosir.screenconfigchange D/MainActivity: -- onResume
从上面的 3种方式配置 打印的 Log (三种方式 Log是一样的,故合并显示),我们可以总结如下:
不设置Activity的
android:configChanges
时,或 设置Activity的android:configChanges="orientation"
时,或设置Activity的android:configChanges="orientation|keyboardHidden"
时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行一次方法。
而我们经常在其他地方看到的结论如下:
1、不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次
2、设置Activity的android:configChanges="orientation"时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次
3、设置Activity的android:configChanges="orientation|keyboardHidden"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法
需要说明的是:** 我的测试环境是 targetSdkVersion 24,测试模拟器是 系统API 5.0!**
查看官方文档,发现有如下提示:
注意:从 Android 3.2(API 级别 13)开始,当设备在纵向和横向之间切换时,“屏幕尺寸”也会发生变化。因此,在开发针对 API 级别 13 或更高版本系统的应用时,若要避免由于设备方向改变而导致运行时重启(正如 minSdkVersion
和 targetSdkVersion
属性中所声明),则除了"orientation"值以外,您还必须添加 "screenSize"值。即,您必须声明 android:configChanges="orientation|screenSize"。但是,如果您的应用是面向 API 级别 12 或更低版本的系统,则 Activity 始终会自行处理此配置变更(即便是在 Android 3.2 或更高版本的设备上运行,此配置变更也不会重启 Activity)。
我们现在测试第四种配置方式。
android:configChanges="orientation|keyboardHidden|screenSize"
11-02 20:44:06.568 7792-7792/com.ys.yoosir.screenconfigchange D/MainActivity: -- onConfigurationChanged
11-02 20:44:52.918 7792-7792/com.ys.yoosir.screenconfigchange D/MainActivity: -- onConfigurationChanged
结论: 从 API 13开始,配置 android:configChanges="orientation|keyboardHidden|screenSize"
,才不会销毁 activity,且只调用 onConfigurationChanged
方法。
注意: 不设置Activity的android:configChanges时,切换竖屏activity的各个生命周期执行两次,有人说是 在API 2.x下,我未测试,大家可以考证下。
android:configChanges
,或设置Activity的android:configChanges="orientation"
,或设置Activity的android:configChanges="orientation|keyboardHidden"
,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行一次。android:configChanges="orientation|keyboardHidden|screenSize"
,才不会销毁 activity,且只调用 onConfigurationChanged
方法。在代码中切换屏幕的方向主要调用 setRequestedOrientation(int requestedOrientation)
方法,此方法的作用等同于在 AndroidManifest.xml
设置Activity 的android:screenOrientation
,所以,其可传递的参数如 android:screenOrientation
表格中一样。
示例代码:
findViewById(R.id.btn_click).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d(TAG,"被点击了");
//判断当前屏幕方向
if(getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
//切换竖屏
MainActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}else{
//切换横屏
MainActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
}
});
非重启Activity模式:即设置了android:configChanges="orientation|keyboardHidden|screenSize"
,Log输出(横竖屏切换一样)
11-03 10:38:33.998 20317-20317/com.ys.yoosir.screenconfigchange D/MainActivity: 被点击了
11-03 10:38:34.108 20317-20317/com.ys.yoosir.screenconfigchange D/MainActivity: -- onConfigurationChanged
重启Activity模式:即没有设置android:configChanges="orientation|keyboardHidden|screenSize"
,Log输出(横竖屏切换一样)
11-03 10:59:23.268 7724-7724/com.ys.yoosir.screenconfigchange D/MainActivity: 被点击了
11-03 10:59:23.368 7724-7724/com.ys.yoosir.screenconfigchange D/MainActivity: -- onPause
11-03 10:59:23.368 7724-7724/com.ys.yoosir.screenconfigchange D/MainActivity: -- onSaveInstanceState
11-03 10:59:23.368 7724-7724/com.ys.yoosir.screenconfigchange D/MainActivity: -- onStop
11-03 10:59:23.368 7724-7724/com.ys.yoosir.screenconfigchange D/MainActivity: -- onDestroy
11-03 10:59:23.428 7724-7724/com.ys.yoosir.screenconfigchange D/MainActivity: -- onCreate - orientation
11-03 10:59:23.458 7724-7724/com.ys.yoosir.screenconfigchange D/MainActivity: -- onStart
11-03 10:59:23.458 7724-7724/com.ys.yoosir.screenconfigchange D/MainActivity: -- onRestoreInstanceState
11-03 10:59:23.458 7724-7724/com.ys.yoosir.screenconfigchange D/MainActivity: -- onResume
注意: 通过
setRequestedOrientation(int requestedOrientation)
修改了屏幕方向后,就类似于设置了android:screenOrientation
,效果是一样的,比如:调用setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
,无论屏幕怎么旋转,都不会切换屏幕方向。如果要恢复为响应横竖屏随物理方向传感器设备变换,那么就需要手动调用类似setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
代码进行恢复。
@Overrideprotected
void onSaveInstanceState(Bundle outState) {
Log.d(TAG," -- onSaveInstanceState");
Log.d(TAG," -- onSaveInstanceState save: name = yoosir,age = 24,handsome = ture");
outState.putString("name","yoosir");
outState.putInt("age",24);
outState.putBoolean("handsome",true);
super.onSaveInstanceState(outState);
}
....
@Overrideprotected
void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Log.d(TAG," -- onRestoreInstanceState");
if(savedInstanceState != null) {
String name = savedInstanceState.getString("name");
int age = savedInstanceState.getInt("age");
boolean isHandsome = savedInstanceState.getBoolean("handsome");
Log.d(TAG, " -- onRestoreInstanceState get: name = " + name + ",age = " + age + ",handsome = " + isHandsome);
}}
横竖屏切换Log 输出:
11-03 12:03:31.678 3663-3663/com.ys.yoosir.screenconfigchange D/MainActivity: -- onPause
11-03 12:03:31.678 3663-3663/com.ys.yoosir.screenconfigchange D/MainActivity: -- onSaveInstanceState
11-03 12:03:31.678 3663-3663/com.ys.yoosir.screenconfigchange D/MainActivity: -- onSaveInstanceState save: name = yoosir,age = 24,handsome = ture
11-03 12:03:31.688 3663-3663/com.ys.yoosir.screenconfigchange D/MainActivity: -- onStop
11-03 12:03:31.688 3663-3663/com.ys.yoosir.screenconfigchange D/MainActivity: -- onDestroy
11-03 12:03:31.758 3663-3663/com.ys.yoosir.screenconfigchange D/MainActivity: -- onCreate - orientation
11-03 12:03:31.758 3663-3663/com.ys.yoosir.screenconfigchange D/MainActivity: -- onCreate get: name = yoosir,age = 24,handsome = true
11-03 12:03:31.788 3663-3663/com.ys.yoosir.screenconfigchange D/MainActivity: -- onStart
11-03 12:03:31.788 3663-3663/com.ys.yoosir.screenconfigchange D/MainActivity: -- onRestoreInstanceState
11-03 12:03:31.788 3663-3663/com.ys.yoosir.screenconfigchange D/MainActivity: -- onRestoreInstanceState get: name = yoosir,age = 24,handsome = true
11-03 12:03:31.788 3663-3663/com.ys.yoosir.screenconfigchange D/MainActivity: -- onResume
如上,大家可以看到在 onCreate() 中也是可以拿到我们之前保存的数据的。
补充一点: 如果大家在资源目录res 中添加了 layout-land(横向布局文件夹) 和 layout-port (竖想布局文件夹),重启Activity模式的横竖屏切换,系统会自动帮我们显示正确方向的布局UI。
Paste_Image.png
@Overridepublic void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.d(TAG," -- onConfigurationChanged");
if(newConfig.orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT){
//切换到竖屏
//修改布局文件
setContentView(R.layout.activity_main);
//findViewById ....
//TODO something
Log.d(TAG," -- onConfigurationChanged 可以在竖屏方向 to do something");
}else{
//切换到横屏
//修改布局文件
setContentView(R.layout.activity_main);
//findViewById ....
//TODO something
Log.d(TAG," -- onConfigurationChanged 可以在横屏方向 to do something");
}
}
横竖屏切换Log 输出:
11-03 14:56:50.465 28612-28612/com.ys.yoosir.screenconfigchange D/MainActivity: -- onConfigurationChanged
11-03 14:56:50.495 28612-28612/com.ys.yoosir.screenconfigchange D/MainActivity: -- onConfigurationChanged 可以在横屏方向 to do something
大家可以看到,在非重启Activity模式下,横竖屏切换修改UI布局文件时,其实挺麻烦的(我是这样的看法,^_~),需要重新初始化一遍UI。不过,不需要我们手动保存数据倒是挺方便的。
打印 Activity 和 Fragment 各个生命周期,并在Activity 的 onCreate() 中添加如下代码:
//是否已经 add 了 Fragment
if(getSupportFragmentManager().findFragmentByTag("child") == null) {
Log.d(TAG, " -- onCreate has no child ");
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
blankFragment = BlankFragment.newInstance("one", "two");
fragmentTransaction.add(R.id.root_view, blankFragment, "child");
fragmentTransaction.commit();
}else{
Log.d(TAG, " -- onCreate has child ");
}
-- onCreate has no child
11-03 17:56:19.465 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: -- onCreate - orientation
11-03 17:56:19.525 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: -- onCreate has no child
11-03 17:56:19.545 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- newInstance
11-03 17:56:19.545 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onAttach
11-03 17:56:19.545 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onCreate
11-03 17:56:19.545 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onCreateView
11-03 17:56:19.585 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onActivityCreated
11-03 17:56:19.585 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onStart
11-03 17:56:19.585 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: -- onStart
11-03 17:56:19.585 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: -- onResume
11-03 17:56:19.585 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onResume
11-03 17:57:02.285 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: -- one btn click
11-03 17:57:05.705 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: -- two btn click
横竖屏切换的Log:
onCreate has child
,所以** Fragment 本身是没有被 destroy,它的views 是被destroy 的**onSaveInstanceState
保存数据,虽然 Fragment 不像 Activity 拥有 onRestoreInstanceState
方法,但是我们可以在 onActivityCreated
中获取之前保存的数据。11-03 17:57:40.095 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onPause
11-03 17:57:40.095 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: -- onPause
11-03 17:57:40.095 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: -- onSaveInstanceState
11-03 17:57:40.095 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: -- onSaveInstanceState save: name = yoosir,age = 24,handsome = ture
11-03 17:57:40.095 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onSaveInstanceState
11-03 17:57:40.095 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onSaveInstanceState save str
11-03 17:57:40.095 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onStop
11-03 17:57:40.095 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: -- onStop
11-03 17:57:40.095 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onDestroyView
11-03 17:57:40.095 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onDestroy
11-03 17:57:40.095 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onDetach
11-03 17:57:40.095 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: -- onDestroy
11-03 17:57:40.165 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onAttach
11-03 17:57:40.165 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onCreate
11-03 17:57:40.165 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: -- onCreate - orientation
11-03 17:57:40.165 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: -- onCreate get: name = yoosir,age = 24,handsome = true
11-03 17:57:40.175 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: -- onCreate has child
11-03 17:57:40.175 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onCreateView
11-03 17:57:40.185 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onActivityCreated
11-03 17:57:40.185 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onActivityCreated get 我设置了参数
11-03 17:57:40.185 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onStart
11-03 17:57:40.185 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: -- onStart
11-03 17:57:40.195 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: -- onRestoreInstanceState
11-03 17:57:40.195 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: -- onRestoreInstanceState get: name = yoosir,age = 24,handsome = true
11-03 17:57:40.195 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: -- onResume
11-03 17:57:40.195 26990-26990/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onResume
11-03 18:31:21.725 27309-27309/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onConfigurationChanged
11-03 18:31:21.725 27309-27309/com.ys.yoosir.screenconfigchange D/Main2Activity: -- onConfigurationChanged
11-03 18:31:21.725 27309-27309/com.ys.yoosir.screenconfigchange D/Main2Activity: -- onConfigurationChanged 可以在横屏方向 to do something
11-03 18:31:36.545 27309-27309/com.ys.yoosir.screenconfigchange D/Main2Activity: BlankFragment -- onConfigurationChanged
11-03 18:31:36.545 27309-27309/com.ys.yoosir.screenconfigchange D/Main2Activity: -- onConfigurationChanged
11-03 18:31:36.545 27309-27309/com.ys.yoosir.screenconfigchange D/Main2Activity: -- onConfigurationChanged 可以在竖屏方向 to do something
如果我们应用是手机和平板都可用的,且手机的只能是竖屏不可切换,平板的只能是横屏不可切换。首先,我们要区分设备是手机还是平板,然后设置屏幕方向。代码如下:
/**
* 判断是否平板设备
* @param context
* @return true:平板,false:手机
*/
private boolean isTabletDevice(Context context) {
return (context.getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE;
}
//设置屏幕方向
private void setScreenOrientation(Context context){
if(!isTabletDevice(context)){
//手机,竖屏
MainActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}else{
//平板,横屏
MainActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
}
The Real Best Practices to Save/Restore Activity's and Fragment's state 《===== 重点推荐阅读
[译] 保存/恢复 Activity 和 Fragment 状态的最佳实践 《=== 上面一篇文章的译文
如何在Android设备旋转时暂存数据以保护当前的交互状态?
Android横竖屏切换小结