Android手機(jī)或平板都會(huì)存在橫豎屏切換的功能,通常是由物理重力感應(yīng)觸發(fā)的亲轨,但是有時(shí)候也不盡然符欠,通常在設(shè)置里面我們可以對手機(jī)的橫豎屏切換進(jìn)行關(guān)閉。
一瓶埋、禁止APP內(nèi)橫豎屏切換
上述設(shè)置更改的是整個(gè)手機(jī)的橫豎屏切換,當(dāng)手機(jī)沒有關(guān)閉橫豎屏切換功能時(shí)诊沪,系統(tǒng)一旦觸發(fā)橫豎屏切換养筒,缺省狀態(tài)下,當(dāng)前活動(dòng)的App的界面就會(huì)進(jìn)行橫豎屏切換端姚,由于橫豎屏的界面尺寸等參數(shù)不同晕粪,很多軟件在設(shè)計(jì)和開發(fā)中為了避免橫豎屏切換時(shí)引發(fā)不必要的麻煩,通常需要讓App禁止掉橫豎屏的切換.
這就需要通過在AndroidManifest.xml中設(shè)置activity中的android:screenOrientation屬性值來實(shí)現(xiàn)渐裸。
該android:screenOrientation屬性巫湘,他有以下幾個(gè)參數(shù):
值 | 描述 |
---|---|
"unspecified" | 默認(rèn)值 由系統(tǒng)來判斷顯示方向.判定的策略是和設(shè)備相關(guān)的,所以不同的設(shè)備會(huì)有不同的顯示方向. |
"landscape" | 橫屏顯示(寬比高要長) |
"portrait" | 豎屏顯示(高比寬要長) |
"user" | 用戶當(dāng)前首選的方向 |
"behind" | 和該Activity下面的那個(gè)Activity的方向一致(在Activity堆棧中的) |
"sensor" | 有物理的感應(yīng)器來決定昏鹃。如果用戶旋轉(zhuǎn)設(shè)備這屏幕會(huì)橫豎屏切換尚氛。 |
"nosensor" | 忽略物理感應(yīng)器,這樣就不會(huì)隨著用戶旋轉(zhuǎn)設(shè)備而更改了("unspecified"設(shè)置除外)洞渤。 |
eg:
比如下列設(shè)置
android:screenOrientation="portrait"
則無論手機(jī)如何變動(dòng)阅嘶,擁有這個(gè)屬性的activity都將是豎屏顯示。
android:screenOrientation="landscape" //為橫屏顯示。
上述修改也可以在Java代碼中通過類似如下代碼來設(shè)置
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
二讯柔、APP的橫豎屏切換可以手動(dòng)觸發(fā)嗎
由上面描述可知抡蛙,當(dāng)android:screenOrientation為默認(rèn)值"unspecified"或"sensor"等時(shí),就會(huì)有系統(tǒng)根據(jù)設(shè)備的旋轉(zhuǎn)情況來觸發(fā)橫豎屏的切換魂迄,那么有沒有方法我們手動(dòng)在程序中觸發(fā)橫豎屏的變換呢粗截,顯然上面為我們提供的setRequestedOrientation就是系統(tǒng)提供的一個(gè)入口,下面我們給出一個(gè)按鍵的方式來觸發(fā)的案列:
public class MainActivity extends Activity implements OnClickListener {
private Button mBtnLandscape;
private Button mBtnPortrait;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnLandscape = (Button) findViewById(R.id.but_landscape);
mBtnPortrait = (Button) findViewById(R.id.but_portrait);
mBtnLandscape.setOnClickListener(this);
mBtnPortrait.setOnClickListener(this);
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
if(v == mBtnLandscape){
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}else{
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
String message=newConfig.orientation==Configuration.ORIENTATION_LANDSCAPE ? "屏幕設(shè)置為:橫屏" : "屏幕設(shè)置為:豎屏";
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
}
需要注意的是捣炬,手動(dòng)調(diào)用時(shí)熊昌,無視AndroidManifest中關(guān)于screenOrientation的設(shè)置;
另外上述代碼中的onConfigurationChanged要被調(diào)用到也是需要條件的遥金,在這里浴捆,只給代碼,不做討論稿械,后面再給出一個(gè)相關(guān)的補(bǔ)充說明选泻。
三、重啟Activity的橫豎屏切換
在上面的案列中美莫,缺省狀態(tài)下页眯,Activity每次橫豎屏切換(包括用setRequestedOrientation調(diào)用)都會(huì)重新調(diào)用一輪操作
onPause-> onStop-> onDestory-> onCreate->onStart->onResume
從而銷毀原來的Activity對象,創(chuàng)建新的Activity對象厢呵,這是因?yàn)橥ǔG闆r下軟件在橫豎屏之間切換窝撵,界面的高寬會(huì)發(fā)生轉(zhuǎn)換,從而可能會(huì)要求不同的布局襟铭。
具體的布局切換可以通過如下兩種方法來實(shí)現(xiàn):
- 1)在res目錄下建立layout-land和layout-port目錄,相應(yīng)的layout文件名不變碌奉,比如main.xml。layout-land是橫屏的layout,layout-port是豎屏的layout寒砖,其他的不用管赐劣,橫豎屏切換時(shí)程序自己會(huì)調(diào)用Activity的onCreate方法,從而根據(jù)當(dāng)前橫豎屏情況自動(dòng)加載響應(yīng)的布局哩都。
- 2)假如布局資源是不一樣又不按照如上設(shè)置魁兼,則需要通過java代碼來判斷當(dāng)前是橫屏還是豎屏然后來加載相應(yīng)的xml布局文件(比如mainP為豎屏mainL為橫屏)。因?yàn)楫?dāng)屏幕變?yōu)闄M屏的時(shí)候,系統(tǒng)會(huì)重新呼叫當(dāng)前Activity的onCreate方法,你可以把以下方法放在你的onCreate中來檢查當(dāng)前的方向,然后可以讓你的setContentView來載入不同的layout xml漠嵌。
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
int mCurrentOrientation = getResources().getConfiguration().orientation;
if ( mCurrentOrientation == Configuration.ORIENTATION_PORTRAIT ) {
// If current screen is portrait
Log.i("info", "portrait"); // 豎屏
setContentView(R.layout.mainP);
} else if ( mCurrentOrientation == Configuration.ORIENTATION_LANDSCAPE ) {
//If current screen is landscape
Log.i("info", "landscape"); // 橫屏
setContentView(R.layout.mainL);
}
init();//初始化咐汞,賦值等操作
findViews();//獲得控件
setListensers();//設(shè)置控件的各種監(jiān)聽方法
}
上面只是對布局切換做了描述,實(shí)際上由于重啟Activity在未加處理的情況下必然導(dǎo)致數(shù)據(jù)的丟失和重新獲取儒鹿,這樣用戶體驗(yàn)會(huì)非常差化撕。為此就要在切換前對數(shù)據(jù)進(jìn)行保存,切換重啟后對數(shù)據(jù)進(jìn)行恢復(fù)挺身,具體操作的步驟如下:
重寫Activity.onRetainNonConfigurationInstance()侯谁,用戶橫豎屏切換前保存數(shù)據(jù)
@Override
public Object onRetainNonConfigurationInstance() {
final MyDataObject data = collectMyLoadedData();
return data;
}
在onCreate()函數(shù)中調(diào)用getLastNonConfigurationInstance(),獲取onRetainNonConfigurationInstance()保存的數(shù)據(jù)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final MyDataObject data = (MyDataObject) getLastNonConfigurationInstance();
if (data == null) {
data = loadMyData();
}
...
}
四、非重啟Activity的橫豎屏切換
雖然重啟Activity為我們提供了保存數(shù)據(jù)和讀取數(shù)據(jù)的方式墙贱,但是如此一來程序會(huì)顯得有些繁瑣热芹,所以有時(shí)候程序員往往就不想讓Activity重啟,Android也為我們提供了解決方案惨撇,就是通過onConfigurationChanged攔截橫豎屏變換伊脓,從而進(jìn)行必要的重新布局和切換操作。
操作步驟如下:
- 首先魁衙,manifest中為相應(yīng)的Activity設(shè)置android:configChanges屬性报腔,從而讓Activity不延續(xù)上述的重建流程.
具體如下:
Andorid 3.2以前的SDK可以使用如下配置
android:configChanges="orientation|keyboardHidden"
而Adnroid 3.2以后的SDK必須添加一個(gè)screenSize屬性,具體如下
android:configChanges="keyboardHidden|orientation|screenSize"
或者
android:configChanges="orientation|screenSize"
關(guān)于configChanges的詳細(xì)描述剖淀,后面有個(gè)簡單補(bǔ)充章節(jié)纯蛾,這里不做過多展開。
- 其次纵隔,在Activity或View的onConfigurationChanged(Configuration newConfig)函數(shù)中獲取當(dāng)前橫豎屏參數(shù)翻诉。至于其調(diào)用順序跟touch事件的傳遞順序相似,不過他沒有消費(fèi)事件的概念捌刮,會(huì)順次調(diào)用到每一個(gè)onConfigurationChanged函數(shù)碰煌。下面是重寫Activity的例子:
//布局分別在layout-land和layout-port目錄中的同名main.xml時(shí)
@Override
public void onConfigurationChanged (Configuration newConfig){
super.onConfigurationChanged(newConfig);
setContentView(R.layout.main);
//注意,這里刪除了init()绅作,否則又初始化了芦圾,狀態(tài)就丟失
findViews();
setListensers();
}
//布局為不按照layout-land和layout-port目錄,而自定義名字時(shí)
@Override
public void onConfigurationChanged (Configuration newConfig){
super.onConfigurationChanged(newConfig);
int mCurrentOrientation = getResources().getConfiguration().orientation;
if ( mCurrentOrientation == Configuration.ORIENTATION_PORTRAIT ) {
// If current screen is portrait
setContentView(R.layout.mainP);
//注意俄认,這里刪除了init()个少,否則又初始化了,狀態(tài)就丟失
findViews();
setListensers();
} else if ( mCurrentOrientation == Configuration.ORIENTATION_LANDSCAPE ) {
//If current screen is landscape
setContentView(R.layout.mainL);
//注意眯杏,這里刪除了init()稍算,否則又初始化了,狀態(tài)就丟失
findViews();
setListensers();
}
}
當(dāng)然有時(shí)候連布局都不用更改的話役拴,就可以直接對原有控件進(jìn)行調(diào)用操作了,比如:
public class MainActivity extends Activity {
private TextView textView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Log.i("--Main--", "onCreate");
textView=(TextView)findViewById(R.id.tv_id);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.i("--Main--", "onConfigurationChanged");
if(newConfig.orientation==Configuration.ORIENTATION_LANDSCAPE){
textView.setText("當(dāng)前屏幕為橫屏");
}else{
textView.setText("當(dāng)前屏幕為豎屏");
}
}
}
需要注意的是钾埂,onConfigurationChanged函數(shù)中只能獲得橫豎屏切換后的參數(shù)河闰,在該函數(shù)中獲取不到新的Layout和控件的尺寸位置信息,如果要處理尺寸和位置信息褥紫,必須通過消息異步或者延時(shí)調(diào)用姜性,下面是一個(gè)App在橫豎屏切換時(shí)需要重新設(shè)置popupWindow位置的代碼:
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
//View中不用創(chuàng)建Handler,可直接調(diào)用post操作
//new Handler().postDelayed(new Runnable() {
// @Override
// public void run() {
// updatePopup();
// }
//}, 500);
postDelayed(new Runnable() {
@Override
public void run() {
updatePopup(); //
}
}, 500);//如果不在post中髓考,而是直接調(diào)用部念,那么彈出位置就會(huì)有問題
}
雖然上面沒有看到對布局的顯式調(diào)用進(jìn)行重新布局,照理控件的對象沒有被銷毀,但是控件在橫豎屏切換時(shí)應(yīng)該是需要進(jìn)行重新layout和measure儡炼,然后再進(jìn)行重繪的妓湘,否則不會(huì)引發(fā)彈出框位置的變化,至于如何調(diào)用重新layout乌询、measure和Draw操作榜贴,在這里就不多展開了。
五妹田、對于AndroidManifest.xml設(shè)置的補(bǔ)充
經(jīng)過上面代碼演示唬党,我們可以看到具體實(shí)現(xiàn)涉及到了Manifest工程配置里面具體Activity的screenOrientation和configChanges兩個(gè)參數(shù),這兩個(gè)參數(shù)screenOrientation的優(yōu)先級(jí)是高于configChanges鬼佣,即假如screenOrientation設(shè)置為固定橫豎屏?xí)r驶拱,那么configChanges參數(shù)無論怎么設(shè)都沒有辦法引發(fā)橫豎屏切換,除非在代碼中手動(dòng)調(diào)用setRequestedOrientation函數(shù)進(jìn)行修改晶衷。
screenOrientation屬性在前面已經(jīng)講過了蓝纲,而關(guān)于configChanges屬性設(shè)置有如下選項(xiàng):
值 | 描述 |
---|---|
mcc | IMSI移動(dòng)臺(tái)的國家代碼(MCC)發(fā)生變化——一個(gè)SIM被探測到并且更新MCC |
mnc | IMSI移動(dòng)臺(tái)的網(wǎng)絡(luò)代碼(MNC)發(fā)生變化——一個(gè)SIM被探測到并且更新MNC |
locale | 區(qū)域發(fā)生變化——用戶選擇了一個(gè)文本需要顯示的新語言 |
touchscreen | 觸摸屏發(fā)生變化。(這個(gè)通常不會(huì)發(fā)生房铭。) |
keyboard | 鍵盤類型發(fā)生變化——例如:用戶插入了外接鍵盤驻龟。 |
keyboardHidden | 鍵盤的可訪問性發(fā)生變化——例如:用戶發(fā)現(xiàn)了硬件鍵盤。 |
navigation | 導(dǎo)航類型(軌跡球或dpad)發(fā)生變化缸匪。(通常不會(huì)發(fā)生翁狐。) |
screenLayout | 屏幕布局發(fā)生變化——這個(gè)會(huì)導(dǎo)致顯示不同的Activity。 |
fontScale | 字體縮放因子發(fā)生變化——用戶選擇了新的字體大小凌蔬。 |
uiMode | 當(dāng)UI模式發(fā)生改變的時(shí)候——當(dāng)用戶放置設(shè)備到桌子或/汽車或夜間模式改變的時(shí)候可以引起UI模式變化露懒。閱讀UiModeManager。在API級(jí)別8時(shí)引入砂心。 |
orientation | 屏幕方向發(fā)生變化——用戶旋轉(zhuǎn)了屏幕懈词。注意:如果應(yīng)用程序的目標(biāo)API級(jí)別是13或更高(通過屬性minSdkVersion和屬性targetSdkVersion聲明),你也需要聲明配置項(xiàng)screenSize辩诞,因?yàn)檫@將在設(shè)備選擇肖像和屏幕方向時(shí)發(fā)生改變坎弯。 |
screenSize | 當(dāng)前可用屏幕大小發(fā)生變化。這代表一個(gè)當(dāng)前可用大小的變化译暂,和當(dāng)前的比率相關(guān)抠忘,因此當(dāng)用戶選擇不同的畫面和圖像,會(huì)發(fā)生變化外永。然而崎脉,如果你的程序目標(biāo)API級(jí)別是12或更低,你的Activity總是會(huì)自己處理這個(gè)配置變化(這個(gè)變化不會(huì)引起Activity的重啟伯顶,甚至在Android 3.2或更新的設(shè)備上)囚灼。在API級(jí)別13里加入的骆膝。 |
smallestScreenSize | 物理屏幕大小的變化。不管方向的變化灶体,僅僅在實(shí)際物理屏幕打包變化的時(shí)候阅签,如:外接顯示器。這個(gè)配置項(xiàng)的變化引起在smallestWidth configuration里的變化赃春。然而愉择,如果你的程序目標(biāo)API級(jí)別是12或更低,你的Activity將自己處理這個(gè)變化(這個(gè)變化不會(huì)引起Activity的重啟织中,甚至在Android 3.2或更新的設(shè)備上)在API級(jí)別13里加入的锥涕。 |
layoutDirection | 布局方向變化。例如書寫方式從左向右(LTR)轉(zhuǎn)換為從右向左(RTL) |
從上述這個(gè)表我們可以看到除了橫豎屏狭吼,包括語言层坠、網(wǎng)絡(luò)、鍵盤和外設(shè)等變化都可以被onConfigurationChanged函數(shù)監(jiān)控到刁笙,具體的內(nèi)容和釋義還是查看官方英文文檔吧破花,詳見如下鏈接
http://developer.android.com/guide/topics/manifest/activity-element.html
中文翻譯可以查閱
結(jié)合網(wǎng)上的整理,小結(jié)跟這幾配置相關(guān)的情景:
- 1疲吸、不設(shè)置Activity的android:configChanges時(shí)座每,切屏?xí)匦抡{(diào)用各個(gè)生命周期,切橫屏?xí)r會(huì)執(zhí)行一次摘悴,切豎屏?xí)r會(huì)執(zhí)行兩次(我在三星4.0設(shè)備上發(fā)現(xiàn)切橫屏和豎屏都是執(zhí)行一次峭梳,而并非這里說的有執(zhí)行兩次的情況,不知道是否以前版本手機(jī)會(huì)這樣蹂喻?)葱椭;
- 2、設(shè)置Activity的android:configChanges="orientation"時(shí)口四,切屏還是會(huì)重新調(diào)用各個(gè)生命周期孵运,切橫、豎屏?xí)r只會(huì)執(zhí)行一次蔓彩;
- 3治笨、設(shè)置Activity的android:configChanges="orientation|keyboardHidden"時(shí),切屏不會(huì)重新調(diào)用各個(gè)生命周期赤嚼,只會(huì)執(zhí)行onConfigurationChanged方法大磺。
注:上述描述是在Android3.2以前,如果缺少了keyboardHidden選項(xiàng)探膊,不能防止Activity的銷毀重啟,也就不能執(zhí)行onConfigurationChanged方法了待榔。
?在3.2之后逞壁,必須加上screenSize屬性才可以屏蔽調(diào)用Activity的生命周期(我在一些設(shè)備上親測可以不需要keyboardHidden流济,只要screenSize就可以了,但是保險(xiǎn)起見還是繼續(xù)保留keyboardHidden吧)腌闯。
六绳瘟、對于setRequestedOrientation函數(shù)的補(bǔ)充說明
在上述(二)對于手動(dòng)觸發(fā)橫豎屏切換的時(shí)候,我們用到了setRequestedOrientation姿骏,那時(shí)只是簡單做了下演示糖声,后來發(fā)現(xiàn)還是需要做下補(bǔ)充說明的:
首先在非重啟Activity模式下
手動(dòng)調(diào)用setRequestedOrientation之后,假如會(huì)引發(fā)橫豎屏切換(即請求的橫豎屏要求與當(dāng)前的橫豎屏情況不一致分瘦,就會(huì)引發(fā)切換)蘸泻,那么會(huì)立即調(diào)用onConfigurationChanged函數(shù);假如不會(huì)引發(fā)橫豎屏切換(請求前后一致)嘲玫,那么也就不會(huì)調(diào)用到onConfigurationChanged函數(shù)悦施。
這個(gè)手動(dòng)調(diào)用setRequestedOrientation的地方也可以在Activity中的任何地方,即也可以在onConfigurationChanged中調(diào)用去团,但是一旦指定為橫屏或豎屏完成這個(gè)變換之后抡诞,后面不論屏幕如何進(jìn)行怎么翻轉(zhuǎn)變化,都不會(huì)再觸發(fā)橫豎屏切換了土陪,也即等同于在manifest中設(shè)置了android:screenOrientation屬性為橫屏或豎屏昼汗。如果要恢復(fù)為響應(yīng)橫豎屏隨物理傳感器設(shè)備變換,那么就需要手動(dòng)調(diào)用類似如下代碼進(jìn)行恢復(fù):
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
其次在重啟Activity模式下
手動(dòng)調(diào)用setRequestedOrientation發(fā)出橫豎屏設(shè)定請求之后鬼雀,假如需要進(jìn)行橫豎屏切換(即請求前后橫豎屏狀態(tài)不一致)顷窒,則會(huì)對Activity進(jìn)行銷毀并重啟;假如不需要需要進(jìn)行橫豎屏切換取刃,則Activity維持現(xiàn)狀不變蹋肮;
手動(dòng)調(diào)用setRequestedOrientation一次,完成變換之后璧疗,也跟上面非重啟一樣坯辩,相當(dāng)于在manifest中設(shè)置了android:screenOrientation屬性為橫屏或豎屏。要想恢復(fù)也需要重新調(diào)用類似上面非重啟的調(diào)用崩侠。
在這樣一個(gè)原理下漆魔,就有了對如下一種需求的解決方案:
讓App啟動(dòng)的時(shí)候是橫屏的話就橫屏表示,縱屏的話就縱屏表示却音,然后手機(jī)切換橫豎屏就不能用了該怎么解決呢改抡?
網(wǎng)上給出了一個(gè)例子代碼,這里就不做摘抄了系瓢,有興趣可以試一下阿纤,然后對比一下人家的實(shí)現(xiàn)方式,具體見如下鏈接
http://blog.csdn.net/yimo29/article/details/6030445
另外再給出幾個(gè)我做整理時(shí)參考的帖子夷陋,覺得對我?guī)椭艽笄肥埃謩e如下
Android橫屏豎屏切換的問題(一個(gè)總結(jié)帖胰锌,還是不錯(cuò)的)
http://blog.sina.com.cn/s/blog_77c632410101790w.html
解決Android手機(jī)屏幕橫豎屏切換(一個(gè)真實(shí)測試過的小結(jié))
http://www.cnblogs.com/zhangkai281/archive/2011/07/06/2099277.html
Android 處理橫豎屏切換事件
http://ipjmc.iteye.com/blog/1265991