Android横竖屏解析

相信大家都或多或少的为横竖屏烦恼过吧,毕竟我们在开发中用到这方面的知识还是挺多的,比如我上一篇讲VideoView的博客,在播放视频的时候,能够自如的切换横竖屏是很有必要的。所以这篇博客就来解析Android中的横竖屏。

1、screenOrientation

当手机没有关闭横竖屏切换功能时,系统一旦触发横竖屏切换,缺省状态(即系统默认状态)下,当前活动的App的界面就会进行横竖屏切换,由于横竖屏的界面尺寸等参数不同,很多软件在设计和开发中为了避免横竖屏切换时引发不必要的麻烦,通常需要让App禁止掉横竖屏的切换,这就需要通过在AndroidManifest.xml中设置Activity中的android:screenOrientation属性值来实现。

screenOrientation属性,有以下几个参数:

  • unspecified:默认值 由系统来判断显示方向,判定的策略是和设备相关的,所以不同的设备会有不同的显示方向.
  • landscape:横屏显示
  • portrait:竖屏显示
  • user:用户当前首选的方向
  • behind:和该Activity下面的那个Activity的方向一致(在Activity堆栈中的)
  • sensor:有物理的感应器来决定。如果用户旋转设备这屏幕会横竖屏切换。
  • nosensor:忽略物理感应器,这样就不会随着用户旋转设备而更改了(unspecified设置除外)。

如果android:screenOrientation=”portrait”,则无论手机如何变动,拥有这个属性的Activity都将是竖屏显示。是landscape则为横屏。

上述修改也可以在代码中通过类似如下代码来设置:

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

2、手动切换

由上面描述可知,当android:screenOrientation为默认值”unspecified”或”sensor”等时,就会有系统根据设备的旋转情况来触发横竖屏的切换,那么有没有方法我们手动在程序中触发横竖屏的变换呢,显然上面为我们提供的setRequestedOrientation就是系统提供的一个入口。

在Activity中提供了一个方法,会在设置的参数发生变化时被调用,而我们的orientation也属于设置里的参数,所以我们可以用它去监听横竖屏变化。

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
}

newConfig就是就有现在改变的所有设置参数,我们这样就可以知道是横屏还是竖屏。

String message= newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ? "屏幕设置为:横屏" : "屏幕设置为:竖屏";

手动调用时,会无视AndroidManifest中关于screenOrientation的设置。

3、重启Activity

在切换了横竖屏后(包括用setRequestedOrientation调用)都会重新调用一轮onPause-> onStop-> onDestory-> onCreate->onStart->onResume操作,从而销毁原来的Activity对象,创建新的Activity对象,这是因为通常情况下软件在横竖屏之间切换,设置的参数发生了变化,每个参数的变化都会使Activity重启。

因为每次的重启都会导致当前数据的丢失,这对用户体验是非常差的。

要想解决这个问题有两个办法,第一个我们只要设置切换的时候不重启就可以了,改变android:configChanges属性的值即可。

  • 不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次
  • 设置Activity的android:configChanges=”orientation”时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次
  • 设置Activity android:configChanges=”orientation|keyboardHidden”时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法
  • 自从Android 3.2(API 13)后,在设置Activity的android:configChanges=”orientation|keyboardHidden”后,还是一样会重新调用各个生命周期的。因为screen size也开始跟着设备的横竖切换而改变。因此,阻止程序在运行时重新加载Activity,除了设置”orientation”,你还必须加上”ScreenSize”。

这是比较简单的方法,还有一种就是重启Activity的时候让它保留数据。就是使用onSaveInstanceState(Bundle outState)和onRestoreInstanceState(Bundle savedInstanceState)方法。

横屏切换竖屏实际上是先把当前的横屏的Activity杀掉 然后重新创建一个竖屏的Activity,我们可以使用onSaveInstanceState()方法保存数据,它是在横屏Activity将杀死前调用,可以将须要保存的数据放入Bundle封装在系统中,切换竖屏后这个Activity又重新被创建 这样可以在onCreate(Bundle)或者onRestoreInstanceState(Bundle)方法中来回复之前保存在Bundle中的数据,这样就可以实现横竖屏界面切换数据的保存与读取,当然前提是只能保存Bundle类型的数据,也就是说大量的对象数据的话就要想其它办法来恢复。

我们拿上篇博客中的VideoVIew来做说明。像VideoView,也就是在播放视频的时候重启了Activity,那么我们需要保存的数据就应该是视频播放的进度。

@Override
protected void onSaveInstanceState(Bundle outState) {
    int progress = mVideoView.getCurrentPosition();
    outState.putInt("progress", progress);
    super.onSaveInstanceState(outState);
}

首先我们在切换屏幕之前将数据保存在Bundle中。

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    int progress = savedInstanceState.getInt("progress");
    mVideoView.seekTo(progress);
    mVideoView.start();
    super.onRestoreInstanceState(savedInstanceState);
}

然后在切换屏幕之后在Bundle中把数据取出来,那么我们就可以让VideoView跳转到取出的进度那儿,是播放还是暂停都是由我们自己决定,既然是重启,默认是暂停的。

4、横竖屏布局

我们在切换横竖屏的时候,界面的宽高会发生转换,这样在有的时候可能会发生适配不恰当的情况,就会给用户带来不好的体验,我们的app也是不完整的,所以很多情况我们都会分别给横竖屏一个布局。

具体的布局切换可以通过如下两种方法来实现:

1)在res目录下建立layout-land和layout-port目录,相应的layout文件名不变,比如activity_main.xml。layout-land是横屏的布局,layout-port是竖屏的布局,其他的不用管,横竖屏切换时程序自己会调用Activity的onCreate方法,从而根据当前横竖屏情况自动加载响应的布局。

Android横竖屏解析_第1张图片

Android横竖屏解析_第2张图片

Android横竖屏解析_第3张图片

可以看到在横屏的布局就比竖屏的多了一个TextView,因为是自动调用的,所以我们可以在这两个布局中做不同的宽高处理,去达到更好的效果。

2)假如横竖屏布局资源是不一样的,又不按照如上设置,则需要通过代码来判断当前是横屏还是竖屏然后来加载相应的xml布局文件。因为当屏幕变为横屏的时候,系统会重新呼叫当前Activity的onCreate方法,你可以把以下方法放在你的onCreate中来检查当前的方向,然后可以让你的setContentView来载入不同的layout xml。

if ( mCurrentOrientation == Configuration.ORIENTATION_PORTRAIT ) {

    setContentView(R.layout.mainP);

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

    setContentView(R.layout.mainL);
}

5、configChanges

经过上面的演示,我们可以看到具体实现涉及到了Manifest工程配置里面具体Activity的screenOrientation和configChanges两个参数,这两个参数screenOrientation的优先级要高于configChanges,即假如screenOrientation设置为固定横竖屏时,那么configChanges参数无论怎么设都没有办法引发横竖屏切换,除非在代码中手动调用setRequestedOrientation函数进行修改。

让我们来看看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的重启,甚至在Android3.2或更新的设备上)。在API级别13里加入的。
  • smallestScreenSize:物理屏幕大小的变化。不管方向的变化,仅仅在实际物理屏幕打包变化的时候,如:外接显示器。这个配置项的变化引起在smallestWidth configuration里的变化。然而,如果你的程序目标API级别是12或更低,你的Activity将自己处理这个变化(这个变化不会引起Activity的重启,甚至在Android3.2或更新的设备上)在API级别13里加入的。
  • layoutDirection:布局方向变化。例如书写方式从左向右(LTR)转换为从右向左(RTL)

从上述我们可以看到除了横竖屏,包括语言、网络、键盘和外设等变化都可以被onConfigurationChanged函数监控到。

6、setRequestedOrientation

如果我们是采用上面的configChanges不重启Activity,那么在手动调用setRequestedOrientation之后,假如会引发横竖屏切换(即请求的横竖屏要求与当前的横竖屏情况不一致,就会引发切换),那么会立即调用onConfigurationChanged函数;假如不会引发横竖屏切换(请求前后一致),那么也就不会调用到onConfigurationChanged函数。

这个手动调用setRequestedOrientation的地方可以在Activity中的任何地方,即也可以在onConfigurationChanged中调用,但是一旦指定为横屏或竖屏完成这个变换之后,后面不论屏幕如何进行怎么翻转变化,都不会再触发横竖屏切换了,也即等同于在manifest中设置了android:screenOrientation属性为横屏或竖屏。如果要恢复为响应横竖屏随物理传感器设备变换,那么就需要手动调用类似如下代码进行恢复:

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);

如果是保存数据重启Activity,那么手动调用setRequestedOrientation发出横竖屏设定请求之后,假如需要进行横竖屏切换(即请求前后横竖屏状态不一致),则会对Activity进行销毁并重启;假如不需要需要进行横竖屏切换,则Activity维持现状不变;

手动调用setRequestedOrientation一次,完成变换之后,也跟上面非重启一样,相当于在manifest中设置了android:screenOrientation属性为横屏或竖屏。要想恢复也需要重新调用类似上面非重启的调用。

那么我们可以想想,如果让App启动的时候是横屏的话就横屏显示,纵屏的话就纵屏显示,然后手机怎么让它旋转都不会触发切换横竖屏,这该怎么做呢?

首先在manifest中设置android:screenOrientation=”sensor”。

我们可以在APP启动的时候获得它的宽高,判断是处于横屏还是竖屏,然后用setRequestedOrientation()方法设置为这个值,那么原先在manifest中设置的sensor就没用了。

public void setOrientation() {
    Display display = getWindowManager().getDefaultDisplay();
    int width = display.getWidth();
    int height = display.getHeight();
    if (width > height) {
        mOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
    } else {
        mOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
    }
    this.setRequestedOrientation(mOrientation);
}

把这个方法放到onCreate中调用即可。不过如果切到别的画面的时候再回到原画面(比如按了Home键),它就仍然是横的或者是纵的。那我们想怎么让它从别的屏幕回来后,又重新横竖屏布局呢?

因为从别的画面中回来调用了onResume()方法,所以我们可以在onResume()方法中调用setRequestedOrientation()方法,将screenOrientation的值设为sensor,因为在启动APP的时候已经修改了这个值,然后如果原先的屏幕方向是竖屏,现在用户把手机置为横屏,物理感应器就会起作用,我们的Activity的方向就会改变了。

这里是否重启Activity也是一个问题,如果我们横竖屏不只有一个布局,那么如果不重启Activity,不执行onCreate()方法,那么就一直是加载那一个布局,转换了方向也是一样。所以如果我们有多个布局,就采用重启Activity的方法。

因为启动APP的时候,onResume方法执行在onCreate之后,所以屏幕方向选择好了后又被设置为sensor,因为前后屏幕状态没变,所以也不会调用onConfigurationChanged()方法。那么我们就设置一个变量来判断是不是在调用onCreate()后第一次执行onResume()。

@Override
protected void onResume() {
   super.onResume();
   if (first == 0) {
       this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
   }
   first = 0;
}

first的初始值为1,这样在要变化屏幕方向销毁Activity的时候也让first的值重置啦。

我发现百度上大家写得基本上都是一样的,我觉得虽然内容比较全面,但有不少不合理的地方,我看到他们的这些博客真是有哭笑不得的感觉,不过这些说明还是很不错的。

结束语:本文仅用来学习记录,参考查阅。

你可能感兴趣的:(Android小知识,android)