说明
遇到一个奇葩的问题,我在使用onConfigChanged拦截屏幕的横竖屏旋转时,发现直接进行180度的横屏/竖屏转换居然没有反应!查找原因发现仅对landscape或者portrait状态有用,而同属于landscape的reverse_landscape并不受影响。那么问题怎么破呢?刚开始想到了用Sensor的状态来监听当前屏幕状态,可是发现针对加速度传感器或者陀螺仪的参数来进行判断太麻烦,这样效率一点不高,无意Google中发现这篇帖子,作者把几个问题阐述的淋漓尽致,轮不着我说什么了,于是收藏之。
最近开发Android Camera相关的程序,被屏幕旋转搞得头大,一方面得考虑屏幕旋转后布局的变化,另一方面得搞清楚屏幕的旋转方向、角度与Camera的Preview角度的关系。本来通过重载Activity的onConfigurationChanged方法,可以检测到屏幕旋转,但发现有一个问题,它只能检测水平方向与垂直方向的切换,无法检测180度的跳转(例如:水平方向突然转180度到水平方向),所以最后不得不换成OrientationEventListener方法来解决问题。在这里分享下经验,并就此顺便总结下Android开发中屏幕旋转的处理吧。
注意
作者提倡的是在onResume()和onPause()处进行事件的监听,但是在另一篇外文博客上看到了作者的用法直接在onCreate()进行注册,并且进行了是否可用的判断,感觉这样的做法比较合理,实现如下:
public class SimpleOrientationActivity extends Activity {
OrientationEventListener mOrientationListener;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mOrientationListener = new OrientationEventListener(this,
SensorManager.SENSOR_DELAY_NORMAL) {
@Override
public void onOrientationChanged(int orientation) {
Log.v(DEBUG_TAG,
"Orientation changed to " + orientation);
}
};
if (mOrientationListener.canDetectOrientation()) {
Log.v(DEBUG_TAG, "Can detect orientation");
mOrientationListener.enable();
} else {
Log.v(DEBUG_TAG, "Cannot detect orientation");
mOrientationListener.disable();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mOrientationListener.disable();
}
}
好了不说废话,先分析一下屏幕旋转的几种情形吧:
正常模式(不做任何情况处理下)
设备横竖屏旋转的时候通常会走onCreate() —> 旋转 —>onDestroy() —> onCreate();
因为调用onCreate的缘故,导致整个生命周期进行重置了,而原有的数据在没有保存的情况下都会重新初始化(注意变量是不会被重置的)。
如果没有针对性地做任何处理的话,默认情况下,当用户手机的重力感应器打开后,旋转屏幕方向,会导致app的当前activity发生onDestroy-> onCreate,会重新构造当前activity和界面布局,很多横屏/竖屏的布局如果没有很好的设计的话,转换为竖屏/横屏后,会显示地很难看。
如果想很好地支持屏幕旋转,则建议在res中建立layout-land和layout-port两个文件夹,把横屏和竖屏的布局文件放入对应的layout文件夹中。
注意:
此处的layout-land和layout-port应该包含同样的xx_id.xml布局文件,并且如果普通的layout文件夹也有同一个xx_id.xml布局文件则它们的执行顺序是先从land/port文件夹索引,如果没有找到才去layout文件夹寻找,同一个xml文件必须同时存在于land和port中,一旦两个文件夹中只有一个文件则会运行时报错。
设置固定的屏幕方向
参见官方API,写的很明白了,还是中文的。
自行处理配置变更
如果应用在特定配置变更期间无需更新资源,并且因性能限制您需要尽量避免重启,则可声明 Activity 将自行处理配置变更,这样可以阻止系统重启 Activity。
注:自行处理配置变更可能导致备用资源的使用更为困难,因为系统不会为您自动应用这些资源。 只能在您必须避免Activity因配置变更而重启这一万般无奈的情况下,才考虑采用自行处理配置变更这种方法,而且对于大多数应用并不建议使用此方法。
要声明由 Activity 处理配置变更,请在清单文件中编辑相应的 `` 元素,以包含 android:configChanges
属性以及代表要处理的配置的值。android:configChanges
属性的文档中列出了该属性的可能值(最常用的值包括 "orientation"
和 "keyboardHidden"
,分别用于避免因屏幕方向和可用键盘改变而导致重启)。您可以在该属性中声明多个配置值,方法是用管道 |
字符分隔这些配置值。
例如,以下清单文件代码声明的 Activity 可同时处理屏幕方向变更和键盘可用性变更:
<activity android:name=".MyActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name">
现在,当其中一个配置发生变化时,MyActivity
不会重启。相反,MyActivity
会收到对 onConfigurationChanged()
的调用。向此方法传递Configuration
对象指定新设备配置。您可以通过读取 Configuration
中的字段,确定新配置,然后通过更新界面中使用的资源进行适当的更改。调用此方法时,Activity 的 Resources
对象会相应地进行更新,以根据新配置返回资源,这样,您就能够在系统不重启 Activity 的情况下轻松重置 UI 的元素。
注意:从 Android 3.2(API 级别 13)开始,当设备在纵向和横向之间切换时,“屏幕尺寸”也会发生变化。因此,在开发针对 API 级别 13 或更高版本系统的应用时,若要避免由于设备方向改变而导致运行时重启(正如 minSdkVersion
和 targetSdkVersion
属性中所声明),则除了 "orientation"
值以外,您还必须添加 "screenSize"
值。即,您必须声明 android:configChanges="orientation|screenSize"
。但是,如果您的应用是面向 API 级别 12 或更低版本的系统,则 Activity 始终会自行处理此配置变更(即便是在 Android 3.2 或更高版本的设备上运行,此配置变更也不会重启 Activity)。
例如,以下 onConfigurationChanged()
实现 检查当前设备方向:
@Override
public void onConfigurationChanged(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();
}
}
Configuration
对象代表所有当前配置,而不仅仅是已经变更的配置。大多数时候,您并不在意配置具体发生了哪些变更,而且您可以轻松地重新分配所有资源,为您正在处理的配置提供备用资源。 例如,由于 Resources
对象现已更新,因此您可以通过 setImageResource()
重置任何 ImageView
,并且使用适合于新配置的资源(如提供资源中所述)。
请注意,Configuration
字段中的值是与 Configuration
类中的特定常量匹配的整型数。有关要对每个字段使用哪些常量的文档,请参阅 Configuration
参考文档中的相应字段。
请谨记:在声明由 Activity 处理配置变更时,您有责任重置要为其提供备用资源的所有元素。 如果您声明由 Activity 处理方向变更,而且有些图像应该在横向和纵向之间切换,则必须在 onConfigurationChanged()
期间将每个资源重新分配给每个元素。
如果无需基于这些配置变更更新应用,则可不用实现 onConfigurationChanged()
。在这种情况下,仍将使用在配置变更之前用到的所有资源,只是您无需重启 Activity。 但是,应用应该始终能够在保持之前状态完好的情况下关闭和重启,因此您不得试图通过此方法来逃避在正常 Activity 生命周期期间保持您的应用状态。 这不仅仅是因为还存在其他一些无法禁止重启应用的配置变更,还因为有些事件必须由您处理,例如用户离开应用,而在用户返回应用之前该应用已被销毁。
如需了解有关您可以在 Activity 中处理哪些配置变更的详细信息,请参阅 android:configChanges
文档和 Configuration
类。
关键问题
在该函数中可以通过两种方法检测当前的屏幕状态:
第一种:
判断newConfig是否等于Configuration.ORIENTATION_LANDSCAPE,Configuration.ORIENTATION_PORTRAIT
当然,这种方法只能判断屏幕是否为横屏,或者竖屏,不能获取具体的旋转角度。
第二种:
调用this.getWindowManager().getDefaultDisplay().getRotation();
该函数的返回值,有如下四种:
Surface.ROTATION_0,Surface.ROTATION_90,Surface.ROTATION_180,Surface.ROTATION_270
其中,Surface.ROTATION_0 表示的是手机竖屏方向向上,后面几个以此为基准依次以顺时针90度递增。
(3) 这种方法的Bug
最近发现这种方法有一个Bug,它只能一次旋转90度,如果你突然一下子旋转180度,onConfigurationChanged函数不会被调用。
实时判断屏幕旋转的每一个角度
上面说的各种屏幕旋转角度的判断至多只能判断 0,90,180,270 四个角度,如果希望实时获取每一个角度的变化,则可以通过OrientationEventListener 来实现。
使用方法:
(1)创建一个类继承OrientationEventListener
(2)开启和关闭监听
可以在 activity 中创建MyOrientationDetector 类的对象,注意,监听的开启的关闭,是由该类的父类的 enable() 和 disable() 方法实现的。
因此,可以在activity的 onResume() 中调用MyOrientationDetector 对象的 enable方法,在 onPause() 中调用MyOrientationDetector 对象的 disable方法来完车功能。
(3)监测指定的屏幕旋转角度
MyOrientationDetector类的onOrientationChanged 参数orientation是一个从0~359的变量,如果只希望处理四个方向,加一个判断即可:
if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
return; //手机平放时,检测不到有效的角度
}
//只检测是否有四个角度的改变
if (orientation > 350 || orientation < 10) { //0度
orientation = 0;
} else if (orientation > 80 && orientation < 100) { //90度
orientation = 90;
} else if (orientation > 170 && orientation < 190) { //180度
orientation = 180;
} else if (orientation > 260 && orientation < 280) { //270度
orientation = 270;
} else {
return;
}
Log.i("MyOrientationDetector ", "onOrientationChanged:" + orientation);
以上就是屏幕旋转处理的方式,大部分都是出自API或者博客,其实只要写个Demo自己尝试一下就理解了。