說明
遇到一個奇葩的問題,我在使用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自己嘗試一下就理解了。
