1. 程式人生 > >【Android如何禁止橫豎屏切換】

【Android如何禁止橫豎屏切換】

Android手機或平板都會存在橫豎屏切換的功能,通常是由物理重力感應觸發的,但是有時候也不盡然,通常在設定裡面我們可以對手機的橫豎屏切換進行關閉,操作介面如下


 


只需要點選下“螢幕旋轉”按鈕就可以關閉橫豎屏切換了。

一、禁止APP內橫豎屏切換
上述設定更改的是整個手機的橫豎屏切換,當手機沒有關閉橫豎屏切換功能時,系統一旦觸發橫豎屏切換,預設狀態下,當前活動的App的介面就會進行橫豎屏切換,由於橫豎屏的介面尺寸等引數不同,很多軟體在設計和開發中為了避免橫豎屏切換時引發不必要的麻煩,通常需要讓App禁止掉橫豎屏的切換,這就需要通過在AndroidManifest.xml中設定activity中的android:screenOrientation屬性值來實現。


該android:screenOrientation屬性,他有以下幾個引數:


"unspecified":預設值 由系統來判斷顯示方向.判定的策略是和裝置相關的,所以不同的裝置會有不同的顯示方向.


"landscape":橫屏顯示(寬比高要長)


"portrait":豎屏顯示(高比寬要長)


"user":使用者當前首選的方向


"behind":和該Activity下面的那個Activity的方向一致(在Activity堆疊中的)


"sensor":有物理的感應器來決定。如果使用者旋轉裝置這螢幕會橫豎屏切換。


"nosensor":忽略物理感應器,這樣就不會隨著使用者旋轉裝置而更改了("unspecified"設定除外)。


比如下列設定


android:screenOrientation="portrait"


則無論手機如何變動,擁有這個屬性的activity都將是豎屏顯示。


android:screenOrientation="landscape",為橫屏顯示。


上述修改也可以在Java程式碼中通過類似如下程式碼來設定

二、APP的橫豎屏切換可以手動觸發嗎
由上面描述可知,當android:screenOrientation為預設值"unspecified"或"sensor"等時,就會有系統根據裝置的旋轉情況來觸發橫豎屏的切換,那麼有沒有方法我們手動在程式中觸發橫豎屏的變換呢,顯然上面為我們提供的setRequestedOrientation就是系統提供的一個入口,下面我們給出一個按鍵的方式來觸發的案列:


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 ? "螢幕設定為:橫屏" : "螢幕設定為:豎屏";


         Toast.makeText(this, message, Toast.LENGTH_LONG).show();


     }


}


需要注意的是,手動呼叫時,無視AndroidManifest中關於screenOrientation的設定;另外上述程式碼中的onConfigurationChanged要被呼叫到也是需要條件的,在這裡,只給程式碼,不做討論,後面再給出一個相關的補充說明。


三、重啟Activity的橫豎屏切換
在上面的案列中,預設狀態下,Activity每次橫豎屏切換(包括用setRequestedOrientation呼叫)都會重新呼叫一輪onPause-> onStop-> onDestory-> onCreate->onStart->onResume操作,從而銷燬原來的Activity物件,建立新的Activity物件,這是因為通常情況下軟體在橫豎屏之間切換,介面的高寬會發生轉換,從而可能會要求不同的佈局。具體的佈局切換可以通過如下兩種方法來實現:


1)在res目錄下建立layout-land和layout-port目錄,相應的layout檔名不變,比如main.xml。layout-land是橫屏的layout,layout-port是豎屏的layout,其他的不用管,橫豎屏切換時程式自己會呼叫Activity的onCreate方法,從而根據當前橫豎屏情況自動載入響應的佈局。


2)假如佈局資源是不一樣又不按照如上設定,則需要通過java程式碼來判斷當前是橫屏還是豎屏然後來載入相應的xml佈局檔案(比如mainP為豎屏mainL為橫屏)。因為當螢幕變為橫屏的時候,系統會重新呼叫當前Activity的onCreate方法,你可以把以下方法放在你的onCreate中來檢查當前的方向,然後可以讓你的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();//設定控制元件的各種監聽方法


}


上面只是對佈局切換做了描述,實際上由於重啟Activity在未加處理的情況下必然導致資料的丟失和重新獲取,這樣使用者體驗會非常差。為此就要在切換前對資料進行儲存,切換重啟後對資料進行恢復,具體操作的步驟如下:


重寫Activity.onRetainNonConfigurationInstance(),使用者橫豎屏切換前儲存資料


@Override 


public Object onRetainNonConfigurationInstance() { 


    final MyDataObject data = collectMyLoadedData(); 


    return data; 


}


在onCreate()函式中呼叫getLastNonConfigurationInstance(),獲取onRetainNonConfigurationInstance()儲存的資料


@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為我們提供了儲存資料和讀取資料的方式,但是如此一來程式會顯得有些繁瑣,所以有時候程式設計師往往就不想讓Activity重啟,Android也為我們提供瞭解決方案,就是通過onConfigurationChanged攔截橫豎屏變換,從而進行必要的重新佈局和切換操作。操作步驟如下:


首先,manifest中為相應的Activity設定android:configChanges屬性,從而讓Activity不延續上述的重建流程,具體如下:


Andorid 3.2以前的SDK可以使用如下配置


android:configChanges="orientation|keyboardHidden"


而Adnroid 3.2以後的SDK必須新增一個screenSize屬性,具體如下


android:configChanges="keyboardHidden|orientation|screenSize"


或者


android:configChanges="orientation|screenSize"


關於configChanges的詳細描述,後面有個簡單補充章節,這裡不做過多展開。


其次,在Activity或View的onConfigurationChanged(Configuration newConfig)函式中獲取當前橫豎屏引數。至於其呼叫順序跟touch事件的傳遞順序相似,不過他沒有消費事件的概念,會順次呼叫到每一個onConfigurationChanged函式。下面是重寫Activity的例子:


//佈局分別在layout-land和layout-port目錄中的同名main.xml時


@Override


public void onConfigurationChanged (Configuration newConfig){


    super.onConfigurationChanged(newConfig);


    setContentView(R.layout.main);


    //注意,這裡刪除了init(),否則又初始化了,狀態就丟失


    findViews();


    setListensers();


}


//佈局為不按照layout-land和layout-port目錄,而自定義名字時


@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(),否則又初始化了,狀態就丟失


        findViews();


        setListensers();


    } else if ( mCurrentOrientation == Configuration.ORIENTATION_LANDSCAPE ) {


        //If current screen is landscape


        setContentView(R.layout.mainL);


        //注意,這裡刪除了init(),否則又初始化了,狀態就丟失


        findViews();


        setListensers();


    }


}


當然有時候連佈局都不用更改的話,就可以直接對原有控制元件進行呼叫操作了,比如:


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("當前螢幕為橫屏");


        }else{


            textView.setText("當前螢幕為豎屏");


        }


    }   


}


需要注意的是,onConfigurationChanged函式中只能獲得橫豎屏切換後的引數,在該函式中獲取不到新的Layout和控制元件的尺寸位置資訊,如果要處理尺寸和位置資訊,必須通過訊息非同步或者延時呼叫,下面是一個App在橫豎屏切換時需要重新設定popupWindow位置的程式碼:


@Override


protected void onConfigurationChanged(Configuration newConfig) {


    super.onConfigurationChanged(newConfig);


    //View中不用建立Handler,可直接呼叫post操作


    //new Handler().postDelayed(new Runnable() {


    //    @Override


    //    public void run() {


    //        updatePopup();    


    //    }


    //}, 500);


 


    postDelayed(new Runnable() {


        @Override


        public void run() {


            updatePopup();      //


        }


    }, 500);//如果不在post中,而是直接呼叫,那麼彈出位置就會有問題


}


雖然上面沒有看到對佈局的顯式呼叫進行重新佈局,照理控制元件的物件沒有被銷燬,但是控制元件在橫豎屏切換時應該是需要進行重新layout和measure,然後再進行重繪的,否則不會引發彈出框位置的變化,至於如何呼叫重新layout、measure和Draw操作,在這裡就不多展開了。


五、對於AndroidManifest.xml設定的補充
經過上面程式碼演示,我們可以看到具體實現涉及到了Manifest工程配置裡面具體Activity的screenOrientation和configChanges兩個引數,這兩個引數screenOrientation的優先順序是高於configChanges,即假如screenOrientation設定為固定橫豎屏時,那麼configChanges引數無論怎麼設都沒有辦法引發橫豎屏切換,除非在程式碼中手動呼叫setRequestedOrientation函式進行修改。


screenOrientation屬性在前面已經講過了,而關於configChanges屬性設定有如下選項:





描述


mcc


IMSI移動臺的國家程式碼(MCC)發生變化——一個SIM被探測到並且更新MCC


mnc


IMSI移動臺的網路程式碼(MNC)發生變化——一個SIM被探測到並且更新MNC


locale


區域發生變化——使用者選擇了一個文字需要顯示的新語言


touchscreen


觸控式螢幕發生變化。(這個通常不會發生。)


keyboard


鍵盤型別發生變化——例如:使用者插入了外接鍵盤。


keyboardHidden


鍵盤的可訪問性發生變化——例如:使用者發現了硬體鍵盤。


navigation


導航型別(軌跡球或dpad)發生變化。(通常不會發生。)


screenLayout


屏幕布局發生變化——這個會導致顯示不同的Activity。


fontScale


字型縮放因子發生變化——使用者選擇了新的字型大小。


uiMode


當UI模式發生改變的時候——當用戶放置裝置到桌子或/汽車或夜間模式改變的時候可以引起UI模式變化。閱讀UiModeManager。在API級別8時引入。


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)


 


從上述這個表我們可以看到除了橫豎屏,包括語言、網路、鍵盤和外設等變化都可以被onConfigurationChanged函式監控到,具體的內容和釋義還是檢視官方英文文件吧,詳見如下連結


http://developer.android.com/guide/topics/manifest/activity-element.html


中文翻譯可以查閱 http://wiki.eoe.cn/page/Activity.html


結合網上的整理,小結跟這幾配置相關的情景:


1、不設定Activity的android:configChanges時,切屏會重新呼叫各個生命週期,切橫屏時會執行一次,切豎屏時會執行兩次(我在三星4.0裝置上發現切橫屏和豎屏都是執行一次,而並非這裡說的有執行兩次的情況,不知道是否以前版本手機會這樣?);


2、設定Activity的android:configChanges="orientation"時,切屏還是會重新呼叫各個生命週期,切橫、豎屏時只會執行一次;


3、設定Activity的android:configChanges="orientation|keyboardHidden"時,切屏不會重新呼叫各個生命週期,只會執行onConfigurationChanged方法。


注:上述描述是在Android3.2以前,如果缺少了keyboardHidden選項,不能防止Activity的銷燬重啟,也就不能執行onConfigurationChanged方法了。在3.2之後,必須加上screenSize屬性才可以遮蔽呼叫Activity的生命週期(我在一些裝置上親測可以不需要keyboardHidden,只要screenSize就可以了,但是保險起見還是繼續保留keyboardHidden吧)。


六、對於setRequestedOrientation函式的補充說明
在上述(二)對於手動觸發橫豎屏切換的時候,我們用到了setRequestedOrientation,那時只是簡單做了下演示,後來發現還是需要做下補充說明的:


首先在非重啟Activity模式下


手動呼叫setRequestedOrientation之後,假如會引發橫豎屏切換(即請求的橫豎屏要求與當前的橫豎屏情況不一致,就會引發切換),那麼會立即呼叫onConfigurationChanged函式;假如不會引發橫豎屏切換(請求前後一致),那麼也就不會呼叫到onConfigurationChanged函式。


這個手動呼叫setRequestedOrientation的地方也可以在Activity中的任何地方,即也可以在onConfigurationChanged中呼叫,但是一旦指定為橫屏或豎屏完成這個變換之後,後面不論螢幕如何進行怎麼翻轉變化,都不會再觸發橫豎屏切換了,也即等同於在manifest中設定了android:screenOrientation屬性為橫屏或豎屏。如果要恢復為響應橫豎屏隨物理感測器裝置變換,那麼就需要手動呼叫類似如下程式碼進行恢復:


setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);


其次在重啟Activity模式下


手動呼叫setRequestedOrientation發出橫豎屏設定請求之後,假如需要進行橫豎屏切換(即請求前後橫豎屏狀態不一致),則會對Activity進行銷燬並重啟;假如不需要需要進行橫豎屏切換,則Activity維持現狀不變;


手動呼叫setRequestedOrientation一次,完成變換之後,也跟上面非重啟一樣,相當於在manifest中設定了android:screenOrientation屬性為橫屏或豎屏。要想恢復也需要重新呼叫類似上面非重啟的呼叫。

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)