android屏幕适配全面总结

本文致力于android屏幕适配的全面总结,尽可能的详细易懂。

2008年9月22日,谷歌正式对外发布第一款Android手机HTC T-Mobile G1,屏幕是TFT 3.2(3.17)英寸触摸屏、HVGA 480×320像素分辨率。

然而我想说的是在此之前Google手机最初的设计。Google手机最初是有物理键盘的,具备200MHz以上的处理器,64MB总存储容量,miniSD卡扩展,200万像素的摄像头+独立拍照按键,非触摸式16位色QVGA(320*240)分辨率的屏幕,物理尺寸是2.5in(2in*1.5in),dpi=160。 

这些与我们的主题有关吗?当然有,至少上面的介绍涉及到了一些知识点,是我们不能错过的。

一些基础知识

首先来看一些手机屏幕的相关知识点,从而能够比较完整的宏观的了解一下背景知识。

罗列的知识

Pixel(像素):屏幕上的点,图像显示的基本单位,px为单位,每个像素可有各自的颜色值,常见颜色格式是RGB。像素点的大小不定,跟硬件设备有关,它的大小决定屏幕的像素密度。

Screen size(屏幕尺寸):指的是手机对角线实际的物理尺寸,单位是英寸in,常见尺寸2.5in,3.7in,4.0in,5.3in,7.0in。屏幕尺寸分类small,normal,large,xlarge。

Resolution(分辨率):指手机屏幕纵、横方向像素个数,常见有240X320,320X480,460X640,480X800,480X854,540X960,640X960,1280X800,1920X1080。

Aspect Ratio(宽高比率):指的是实际的物理尺寸宽高比率,分辨率并不意味着具体的屏幕长宽比。

Video Graphics Array(显示绘图阵列VGA):是IBM在1987年随PS/2机一起推出的一种视频传输标准,具有分辨率高、显示速率快、颜色丰富等优点,在彩色显示器领域得到了广泛的应用。VGA最早指的是显示器640X480。
QVGA 320*240px,WQVGA 400*240px,HVGA 320*480px,VGA 640*480px,WVGA 800*480px,XGA 1024*480px。

Dots per inch(像素密度DPI):每英寸像素数,可以反映屏幕的清晰度。密度分类有ldpi(low)120dpi,mdpi(medium)160dpi,hdpi(high)240dpi,xhdpi(extra high)320dpi,xxhdpi(extra extra high)480dpi。

Density(密度):屏幕里像素值浓度,相对标准屏幕浓度下一个像素占的空间所容纳的像素数,可以反映出手机密度,如0.75,1,1.5,2,3。 

Density-independent pixel(设备独立像素dp):与设备密度无关的类似于像素的度量单位,dp和具体像素值的对应公式是pixel=dp*dpi/160。

Scale-independent Pixel(与缩放无关像素sp):sp和dp很类似但唯一的区别是,它不仅跟屏幕尺寸有关,还跟设置的系统字体大小有关。

举例介绍

到目前为止,对于上面的一些知识,你可能还不是很明白,没关系,其实我们才刚刚开始。我们拿Google最初设计的那个概念机来看看就清除多了。

它的高是2in,宽是1.5in,于是你可以求出它的对角线长度为2.5in(勾股定理),这就是它的屏幕尺寸。

它的宽高比就是1.5in / 2in = 3/4。

它的分辨是 QVGA 2.5 320*240px,这意味着它横向占240px,纵向占320px。

于是我们可以求出它的像素密度dpi=sqrl((240*320)/(1.5*2))=160,其实还有一个横向像素密度和纵向像素密度xdpi=240/1.5=160,ydpi=320/2=160,当一个像素的长宽尺寸相同时这三个dpi是相等的,当然绝大多数屏幕这三个值是相等的,至于那些奇葩屏幕暂时不做考虑。dpi的单位是px/in,所以求出来的160所描述的含义是单位英尺的像素数是160个,借此我们可以求出这个屏幕上一个像素的尺寸为1/dpi = 1/160 = 0.00625in。

到此我们的准备工作已经做完,我们来开始下一模块。

尺寸单位的选择

使用数字定义组件和字体大小使用dp和sp,尽量不用px。

使用dp的原因

假设有三个分辨率不同,大小尺寸相同的屏幕A,B,C,在他们上面要显示同一长度的组件,需要分别为A,B,C设置尺寸为100px,50px,20px。明显通过设置像素来对不同设备适配是不太靠谱的。被逼无奈我们假设有一种不受像素密度限制的,类似px或者cm的单位dp,来表示组件的长度。于是ABC三屏上就可以都用dp作为长度单位,假设在A上px=dp,那么B上px=2dp,C上px=5dp,当然目前还没有5这个值的,不过它完全可以有。所以在开发的时候只需要设置一个100dp就可以适配100px,50px,20px三个值。


那么到底则个dp是个什么东西呢?我们可以设最早的一台android手机的屏幕是以后所有android手机适配的对象,那么这台手机就是参考标准,我们定义此台手机的px=dp。其实事实就是这样,Google最初设计的那个(320*240)分辨率的屏幕,就是我们参考的标准。上一模块我们已经算出它的dpi=160,现在我们规定它的px = dp = 1/160in = 0.00625in。dp表示的就是这个屏幕上一个像素的空间大小。

那么其它屏上一个px到底有多少dp呢?首先假设未知屏的像素密度dpi,那么它一个像素的大小为1/dpi英尺,它是上面那个标准屏的160/dpi倍。另外一种算法如图:


所以可以得到两个度量单位的比值为px/dp = 160 / dpi,转变成量的比值取倒数,好吧再给一个图,下图m为dp的量,n为px的量:


转化成量后可以得出结论px = dp * (dpi / 160),这也是官方给出换算方法,其中(dpi / 160)就是传说中的density

小心使用sp

google推荐使用sp作为 android:textSize 属性的单位。sp和dp很类似但唯一的区别是它不仅跟屏幕尺寸有关,还跟设置的系统字体大小有关。字体的缩放参数是可以被调整的,通过手机的系统设置—>显示—>字体大小你可以设置字体的缩放参数,然后有四种选择,小(small)、普通(normal)、大(large)、超大(huge)。默认情况是normal,此时1sp = 1dp = 0.00625in,而当文字尺寸是“大”或“超大”时,1sp > 1dp = 0.00625in。对于字体sp的缩放的额外处理设计者大概是考虑到有的用户视力或者偏好方面的因素,说的高大上一点就是为了更加友好一点。

正是因为sp跟系统缩放有关,而dp没有,所以当TextView的尺寸被用dp为单位的数值设死时,使用sp设置字体大小就会出问题。举例说明如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/TextDp"
        android:layout_width="wrap_content"
        android:layout_height="20dp"
        android:layout_marginBottom="20dp"
        android:background="#009900"
        android:gravity="center_vertical"
        android:padding="0dp"
        android:text="HelloWorld_16dp"
        android:textColor="#000099"
        android:textSize="16dp" />

    <TextView
        android:id="@+id/TextSp"
        android:layout_width="wrap_content"
        android:layout_height="20dp"
        android:background="#009900"
        android:gravity="center_vertical"
        android:padding="0dp"
        android:text="HelloWorld_16sp"
        android:textColor="#000099"
        android:textSize="16sp" />


</LinearLayout>

上面代码的效果如下图,左边的是设置字体大小为normal的效果,右边的是huge的效果,可见同一代码,不同的系统设置的展示效果不同,说明sp的缩放是跟系统设置有关系的。

    

所以sp与dp的选择取决于需求和设计,一般的选择规律是
1、如果TextView的显示区域可以弹性变化(wrap_content,当然其所有父view也需要可以弹性变化),这时可以优先考虑用sp,这时字体大小更改一般来说不会破坏界面的可用性。
2、如果TextView的显示区域已经限定在一个固定值(或者其父view被限定了),这时优先考虑用dp吧,不然遇到一直使用着超大字体的用户,字体可能横向被截了,而用户不知情,会觉得是程序问题。

获取屏幕参数

public static HashMap<String, Object> getScreenInfos(Context context)
    {
        WindowManager mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        DisplayMetrics dm = new DisplayMetrics();
        mWindowManager.getDefaultDisplay().getMetrics(dm);
        
        HashMap<String, Object> screenInfos = new HashMap<String, Object>();
        screenInfos.put("DISPLAY", Build.DISPLAY);//string
        screenInfos.put("widthPixels", dm.widthPixels);//int
        screenInfos.put("heightPixels", dm.heightPixels);//int
        screenInfos.put("densityDpi", dm.densityDpi);//int
        screenInfos.put("density", dm.density);//float
        screenInfos.put("xdpi", dm.xdpi);//float
        screenInfos.put("ydpi", dm.ydpi);//float
        
        NumberFormat nf = NumberFormat.getNumberInstance();
        nf.setMaximumFractionDigits(1);
        double size = (Math.sqrt(dm.widthPixels * dm.widthPixels
                + dm.heightPixels * dm.heightPixels) / dm.densityDpi);
        String sizeString = nf.format(size);
        screenInfos.put("size", sizeString);//String
        
        double sizex = dm.widthPixels / dm.xdpi;
        double sizey = dm.heightPixels / dm.ydpi;
        double size2 = Math.sqrt(sizex * sizex + sizey * sizey);
        String sizeString2 = nf.format(size2);
        screenInfos.put("sizex", sizex);//double
        screenInfos.put("sizey", sizey);//double
        screenInfos.put("size2", sizeString2);//double
        
        return screenInfos;

    }

DISPLAY=A789_S_2.1.18.60
widthPixels=480
heightPixels=800
densityDpi=240
density=1.5
xdpi=240.0
ydpi=240.0

size=3.9
sizex=2.0
sizey=3.3333332538604736
size2=3.9  格式化前是3.8873011950827854

单位换算

public static int px2dip(Context context, float pxValue) {
final float density = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / density + 0.5f);
}

public static int dip2px(Context context, float dipValue) {
final float density = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * density + 0.5f);
}

public static int px2sp(Context context, float pxValue) {
final float scaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (pxValue / scaledDensity + 0.5f);
}

public static int sp2px(Context context, float spValue) {
final float scaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * scaledDensity + 0.5f);
}

代码中设置尺寸

我们可以在dimens文件中定义一下两个尺寸:

    <dimen name="text_height">20dp</dimen>
    <dimen name="text_size">20sp</dimen>

在代码中获取他们,如下:

 Resources resources = getResources();
        int textHeight = (int) resources.getDimension(R.dimen.text_height);
        int textSize = (int) resources.getDimension(R.dimen.text_size);

当系统字体缩放为normal时,在我的手机上获取的值都是30,当系统字体缩放为huge时,textHeight =30,textSize =39,这其实就是已经转化为像素后的值了。

可巧的是,在代码中设置组件尺寸时都是以像素为单位的。如下:

LinearLayout.LayoutParams params=new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, textHeight);
        params.bottomMargin=textHeight;
        textDp.setLayoutParams(params);

不巧的是,在代码中设置字体大小是以sp为单位的。如下:

textSp.setTextSize(16);

那个16是以sp为单位的,所以在设置字体大小时要想使用dp为单位的话,可以先获取dimens中以dp为单位的大小,获取后的是以px为单位的,再用上一模块的方法把px转化为sp,然后setTextSize。

其他方面注意

A、页面布局

1) 在layout文件中设置控件尺寸时应尽量采用fill_parent、wrap_content、match_parent,具体来说就是设置view的属性android:layout_width和android:layout_height的值时,使用wrap_content,match_parent。实在不行的话,采取上面所介绍的以dp和sp为单位设置具体尺寸。

2)多使用android:gravity="center"android:layout_gravity="bottom"

3) 使用LinearLayout和RelativeLayout。尽量使用LinearLayout的按比例划分屏幕的功能,还有就是用RelativeLayout的相对布局。然而凡是都不是绝对的,LinearLayout有时为了实现一些UI效果会导致页面层次过多,从而导致栈溢出或者加载相比层次少的布局稍慢的情况;RelativeLayout能够使页面层次大大减少,甚至所有组件都可以放在一个RelativeLayout中,但是RelativeLayout经常会导致页面动画和动态刷新迟钝,尤其是当RelativeLayout碰上wrap_content的时候。所以具体情况需要具体分析,到底如何设计页面,其实和UI以及功能的设计有着千丝万缕的关系。

B、图片自适应

1)对不同的屏幕提供合适大小的图片。

在不同的图片资源包中放不同大小的图片,系统会根据不同的手机选择合适的图片展示。比如在新建的项目中就已经分别给不同dpi的屏幕添加了不同的ic_launcher图标,他们的大小比例是:ldpi:mdpi:hdpi:xhdpi:xxhdpi=3:4:6:8:12,他们的具体尺寸为36×36,48×48,72×72,96×96,144×144。我的工程里没给ldpi的,我就把后四中图标分别拷贝出来并命名如下图,并且又放回相应的包里,这是每个包就有两个图标了,比如drawable-xhdpi包中就有ic_launcher.png和ic2xhdpi.png两个图标,然后把ic2xhdpi.png、ic3xxhdpi.png复制重命名为ic2xhnodpi.png、ic2xhnodpi.png放到drawable-nodpi包中。



现在可以开我们的测试了。我们可以使用类似如下代码引用ic0mdpi.png、ic1hdpi.png、ic2xhdpi.png、ic3xxhdpi.png、ic2xhnodpi.png、ic3xxhnodpi.png、ic_launcher.png。

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/ic2xhnodpi.png" />

 <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/ic_launcher" />

具体代码我就不黏了,显示结果如下:

android屏幕适配全面总结_第1张图片

结论:

1、android系统会选择合适的图片作为图标,如上图最后一个,由于我的手机dpi是240所以取hdpi的图片。

2、drawable-mdpi、drawable-hdpi、drawable-xhdpi、drawable-xxhdpi包中的图片在真正显示的时候会进行缩放,例如上图前四个,比较明显的是第一个,它相对其他图标被拉伸了,因而有些模糊。

3、上图前四个缩放为相同尺寸,说明正式开发的时候可以只准备一套高分辨的图片,从而适配不同的屏幕。

4、drawable-nodpi包下的图标没有缩放,按原来大小显示出来了。

2)使用9-patch PNG图片
使用图片资源时,如果出现拉伸,因为图片处理的原因,会变形,导致界面走形。9-patch PNG图片也是一种标准的PGN图片,在原生PNG图片四周空出一个像素间隔,用来标识PNG图片中哪些部分可以拉伸、哪些不可以拉伸、背景上的边框位置等。
“上、左”定义可拉伸区域
“右、下”定义显示区域,如果用到完整填充的背景图,建议不要通过android:padding来设置边距,而是通过9-patch方式来定义。
Android SDK中提供了编辑9-Patch图片的工具,在tools目录下draw9patch.bat,能够立刻看到编辑后的拉伸效果,也可以直接用其他图片编辑工具编辑,但是看不到效果。


资源引用

android的适配必然要涉及到资源引用,上面的drawable-hdpi、drawable-mdpi等图片资源目录就是最常见的。像layout、values等都可以添加限定来适配不同情况,例如:
values-en/   
    strings.xml  //对应英语的字符串
values-ar/
    strings.xml  //对应阿拉伯语的字符串
可以为单个目录添加多段限定,但是他们需要保持一定的顺序以方便阅读和理解,如下:
drawable-en-rUS-large-long-port-mdpi-finger-keysexposed-qwerty-navexposed-dpad-480x320

几种常见的限定词

Language and region(语言和区域
先看看两个语言缩写
zh-cn简体中文(中国)
zh-tw繁体中文(台湾地区)
zh-hk繁体中文(香港)
en-hk英语(香港)
en-us英语(美国)
en-gb英语(英国)
en-ww英语(全球)
在android中取上面语言缩写的低位作为语言限定,如zh、en;取高位大写加上前缀“r”作为区域限定,如rCN、rTW、rHK、rUS。

Layout Direction(布局方向
应用的布局方向,取值:ldrtl、ldltr
ldrtl就是“layout direction right to left”
ldltr就是“layout direction left to right”
之所以有这个玩意主要是因为有些国家的语言是从右向左的,就像中国的古书都是从右向左的,比如阿拉伯语。Added in API level 17

smallestWidth(最小宽度
可用屏幕的最小宽度,取值:sw<N>dp,具体的如sw320dp、sw600dp
它的意思是它修饰的目录必须适应最小可用尺寸为Ndp的屏幕。当屏幕的方向改变时该取值不会变,因为它是屏幕的固定属性。在确切一点说,它指的是你可用的屏幕范围的最小长度,比如,屏幕有600dp,但上面有一些UI元素占据了一些空间,那么你得到的最短宽度就会小余实际的屏幕宽度。因此,smallestWidth的意义是你的layout所期望的最小宽度,而不一定是你需要的设备的最小宽度。Added in API level 13

Screen size(屏幕尺寸
屏幕尺寸,取值:small、normal、large、xlarge
small:这种屏类似低分辨率的QVGA屏幕。对于小屏的最小布局尺寸大约是320x426dp。例如QVGA低分辨率和VGA高分辨率。
normal:这种屏类似中等分辨率的HVGA屏幕。对于普通屏幕的最小布局尺寸大约是320x470dp。如,WQVGA低分辨率屏、HVGA中等分辨率屏、WVGA高分辨率屏。
large:这种屏类似中等分辨率的VGA屏幕,对于大屏幕的最小布局尺寸大约是480x640dp。例如VGA和WVGA的中等分辨率屏。
xlarge:这种屏被认为比传统的中等分辨率的HVGA屏幕大。针对xlarge屏的最小布局尺寸大约是720x960dp。在大多数情况下,这种超大屏幕的设备因为太大而要放到背包中来携带,而且最有可能的是平板样式的设备。

使用尺寸限定符不意味着资源仅用于这个尺寸的屏幕。如果没有用限定符提供与当前设备配置相匹配的可选资源,那么系统会使用与配置最接近的资源。警告:如果所有使用尺寸限定符的资源都比当前屏幕大,那么系统将不会使用它们,并且应用程序会在运行时崩溃(例如,如果所有的布局都被标记了xlarge限定符,而设备却是一个普通尺寸的屏幕)。Added in API level 4

Screen orientation(屏幕方向
屏幕横竖屏方向,取值:port竖屏、land横屏

Screen pixel density(屏幕像素密度dpi
屏幕像素密度,取值:ldpi、mdpi、hdpi、xhdpi、xxhdpi、nodpi、tvdpi
ldpi:针对大约120dpi的低分辨率屏幕;
mdpi:针对大约160dpi的中等分辨率屏幕(在传统的HVGA上);
hdpi:针对大约240dpi的高分辨率屏幕;
xhdpi:针对大约320dpi的超高分辨率屏幕,被添加在API基本8以后的版本中;
xxhdpi:针对大约480dpi的超高分辨率屏幕,忘记在哪个版本了;
nodpi:这个限定被用于不想根据匹配的设备分辨率进行缩放的位图资源。
tvdpi:在mdpi和hdpi之间的屏幕,大约是213dpi。这种分组不是主要的分辨率,大多数是为电视来考虑的,并且大多数应用不需要它---提供mdpi和hdpi资源就可以满足大多数应用程序需要了,并且系统会适当的缩放它们。这个限定符在API级别13以后被引入。使用分辨率限定符不意味着资源仅适用与对应分辨率的屏幕。如果没有提供与当前设备配置匹配的可选资源,那么系统会使用最接近的资源。

Resolution(分辨率)  
分辨率,取值320x240, 640x480等
较大的尺寸必须首先声明。

限定词的优先级

限定词的优先级顺序由高到低一次如下

MCC and MNC mcc31、mcc310-mnc004、mcc208-mnc00
Language and region en-rUS、zh-rCN
smallestWidth sw<N>dp
Available width w<N>dp
Available height h<N>dp
Screen size small、normal、large、xlarge
Screen aspect long、notlong  
Screen orientation port、land
UI mode car、desk、television、appliance
Night mode night、notnight
Screen pixel density (dpi)  ldpi、mdpi、hdpi、xhdpi、nodpi、tvdpi
Touchscreen type notouch、finger
Primary text input method nokeys、qwerty、12key
Navigation key availability navexposed、navhidden
Primary non-touch navigation method nonav、dpad、trackball、wheel
Resolution 320x240, 640x480
Platform Version (API level)  v3、v4、v7

命名原则

这里有一些命名的通用原则:
a、您可以添加多种限定词
b、限定值之间用连接号连接
c、限定值是大小写敏感的
d、每种限定词只能有一种选择,您不能命名目录为drawable-rEN-rFR/。
e、带有限定词的目录不能被嵌套,比如,res/drawable/drawable-en是不允许的。
f、每个限定词必须在上面所列之中,限定符必须按照表上面的优先级顺序来使用。

引用

资源引用在语法中表示为未加修饰的表达式,比如 MyApp/res/drawable-port-92dp/myimage.png,在XML中表示为:@drawable/myimage,在代码中表示为:R.drawable.myimage。

android系统识别添加限定的目录

步骤一、对于已经明确了的限定和目标手机配置不一样的目录,先排除掉。
步骤二、按照上面列出的顺序,找到剩下的最高优先级的限定。
步骤三、判断是否所有目录都包含步骤二中查到的限定,如果所有目录都包含,回到步骤二,继续匹配下一限定;如果有不包含的,进入到步骤四。
步骤四、排除步骤三查找到的不包含步骤二中查到的限定的目录,如果现在剩下的目录数大于一,则回到步骤二继续,直到剩下的目录数等于一。

android屏幕适配全面总结_第2张图片


横竖屏切换

默认情况下android的activity是可以根据传感器进行横竖平切换的,切换的过程如下:
onCreate-->onStart-->onResume-->屏幕旋转-->onPause-->onStop-->onDestory-->onCreate-->onStart-->onResume
也就是说屏幕切换的时候等于重新加载了一遍activity。

禁止横竖屏切换

如果不想让软件在横竖屏之间切换,最简单的办法就是在项目的AndroidManifest.xml中找到你所指定的activity中加上android:screenOrientation属性,
它常用的参数是:
"landscape":横屏显示(宽比高要长),设置此值后activity只横屏显示。 
"portrait":竖屏显示(高比宽要长),设置此值后activity只竖屏显示。 

也可以在Java代码中通过setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)来设置,但是这种方式效果是很差的,可能会引起页面的卡顿,不过值得注意的是有个getRequestedOrientation()方法可以获取屏幕方向。

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

不重新加载activity

AndroidManifest.xmltargetSdkVersion要小于13

<uses-sdk

android:minSdkVersion="11"

 android:targetSdkVersion="12" />

Activity里增加:
android:configChanges="keyboard|orientation "

android3.2之后添加了一个新的参数screenSize,如果android:targetSdkVersion大于等于13的话上面的取值就不对了,必须是:

android:configChanges="keyboard|orientation|screenSize"

为了适配所有手机,这个targetSdkVersion要小于13

这样就可以通过实现Activity的onConfigurationChanged()方法监听横竖屏切换,如下:
    @Override
    public void onConfigurationChanged(Configuration newConfig)
    {
        super.onConfigurationChanged(newConfig);
        System.out.println("onConfigurationChanged--->>>" + strTest + num);
        if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
        {
            Log.i("info", "-->>landscape");
        }
        else if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT)
        {
            Log.i("info", "-->>portrait");
        }
    }
当你能在此方法中监听到横竖屏切换时,你就会发现activity不再走屏幕旋转-->onPause-->onStop-->onDestory-->onCreate-->onStart-->onResume这个流程了,连一个都不会走了这样就不重新加载activity了。

横竖屏适配

如果要让软件在横竖屏之间切换,由于横竖屏的高宽会发生转换,有可能会要求不同的布局。可以通过以下两种方法来切换布局:
1)在res目录下建立layout-land和layout-port目录,相应的layout文件名不变,比如main.xml。layout-land是横屏的layout,layout-port是竖屏的layout,其他的不用管,横竖屏切换时程序为调用Activity的onCreate方法,从而加载相应的布局。

2)另外一种方式是通过java代码来判断当前是横屏还是竖屏然后来加载相应的xml布局文件。因为当屏幕变为横屏的时候,系统会重新呼叫当前Activity的onCreate方法,你可以把以下方法放在你的onCreate中来检查当前的方向,然后可以让你的setContentView来载入不同的layout xml。

if(this.getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE)
{  
    // 横屏 
}elseif(this.getResources().getConfiguration().orientation==Configuration.ORIENTATION_PORTRAIT)
{  
    // 竖屏 
}

3)当然也可以在onConfigurationChanged()方法中进行布局适配,但是要想适配更低版本的手机,就成问题了,而且这用适配方式比较繁琐,所以目前来说还不推荐。

使用onConfigurationChanged()方法适配的话可以防止重新加载activity,但是横竖屏切换时重新绘制画面的过程是不可免的,而且onConfigurationChanged()要做的任务并不一定比重新加载activity少,再加上它还有其他的限制。所以我们推荐建立layout-land和layout-port目录加上保持activity数据和状态的方式来适配。在这里我需要修改一下横竖屏切换过程:
onCreate-->onStart-->onResume-->屏幕旋转-->onPause-->onsaveinstancestate-->onStop-->onretainnonconfigurationinstance->onDestory-->onCreate-->onStart-->onRestoreInstanceState-->onResume

注意新加的onsaveinstancestate()、onretainnonconfigurationinstance()、onRestoreInstanceState()。

 @Override
    protected void onSaveInstanceState(Bundle outState)
    {
        outState.putString("savestring", savestring);
        super.onSaveInstanceState(outState);
    }
  @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState)
    {
        savedInstanceState.getString("savestring");
        super.onRestoreInstanceState(savedInstanceState);
    }

注意super.onSaveInstanceState(outState);已经为我们做了一些事情,比如EditText组件,你在竖屏输入字符串后,转横屏依然可以看到你输入的字符串还在,当然前提条件是你的EditText组件有android:id。但是像ButtonsetEnabled是不能自己记录的,这时需要你手动自己重写onSaveInstanceState方法了。onSaveInstanceState一般保存一些Bundle适合保存的数据,主要是一些小的数据。如果要保存一些诸如ArrayListsocketAsyncTask等等一些数据时一般采用

 @Override
    public Object onRetainNonConfigurationInstance()
    {
        return arrayList;
    }

Object object = getLastNonConfigurationInstance();
        if (object == null)
        {
            if (arrayList == null)
            {
                arrayList  = new ArrayList<String>();
                for (int i = 0; i < 10; i++)
                {
                    arrayList .add("string->" + i);
                }
            }
        }
        else
        {
            arrayList = (ArrayList<String>) object;
        }

onRetainNonConfigurationInstance典型的案例

官方推荐使用Fragment适配横竖屏切换     

public void onCreate(Bundle bundle) {

                  super.onCreate(bundle);

                   setRetainInstance(true);//设置true,表明在存储中保留fragment对象。

}

Fragment中有一个setArguments(bundle)方法,Fragment在横竖屏切换时通过getArguments依然能够获得之前设置的数据,其原理是在FragmentActivity切换时会调用onRetainNonConfigurationInstance方法将FragmentManager中管理的所有Fragment及其状态数据(其中就包括了这个设置的Bundle)保存在一个FragmentActivity.NonConfigurationInstances对象实例中,这样在新的FragmentActivity启动时在onCreate方法中可以使用Activity.getLastNonConfigurationInstance()方法来获取这个对象,然后通过FragmentManager.restoreAllState方法还原所有Fragment及其状态。因为默认情况下横竖屏切换后整个FragmentActivity会被销毁并重建,所有Fragment中的成员变量也会丢失,但所有的Fragment状态数据如上所述会被保留并还原,这个时候所有的视图都会重新创建。


更灵活的屏幕旋转

以上所说的屏幕旋转的前提是你的手机系统设置了Auto-rotate Screen(自动旋转屏幕),屏幕旋转与否,能不能完全由应用自己决定呢?答案是肯定的。
orientationEventListener = new OrientationEventListener(this)
{
            @Override
            public void onOrientationChanged(int orientation)
            {
                System.out.println("onOrientationChanged--->>>" + orientation);
            }
};


orientationEventListener.enable();


  @Override
protected void onDestroy()
{
        orientationEventListener.disable();
        super.onDestroy();
}




你可能感兴趣的:(android)