Android屏幕适配出现的缘由html
在咱们学习如何进行屏幕适配以前,咱们须要先了解下为何Android须要进行屏幕适配。前端
因为Android系统的开放性,任何用户、开发者、OEM厂商、运营商均可以对Android进行定制,修改为他们想要的样子。java
可是这种“碎片化”到底到达什么程度呢?android
在2012年,OpenSignalMaps(如下简称OSM)发布了第一份Android碎片化报告,统计数据代表,app
2012年,支持Android的设备共有3997种。框架
2013年,支持Android的设备共有11868种。iphone
2014年,支持Android的设备共有18796种。ide
下面这张图片所显示的内容足以充分说明当今Android系统碎片化问题的严重性,由于该图片中的每个矩形都表明着一种Android设备。布局
而随着支持Android系统的设备(手机、平板、电视、手表)的增多,设备碎片化、品牌碎片化、系统碎片化、传感器碎片化和屏幕碎片化的程度也在不断地加深。而咱们今天要探讨的,则是对咱们开发影响比较大的——屏幕的碎片化。学习
下面这张图是Android屏幕尺寸的示意图,在这张图里面,蓝色矩形的大小表明不一样尺寸,颜色深浅则表明所占百分比的大小。
而与之相对应的,则是下面这张图。这张图显示了IOS设备所须要进行适配的屏幕尺寸和占比。
固然,这张图片只是4,4s,5,5c,5s和平板的尺寸,如今还应该加上新推出的iphone6和plus,可是和Android的屏幕碎片化程度相比而言,仍是差的太远。
详细的统计数据请到这里查看。
如今你应该很清楚为何要对Android的屏幕进行适配了吧?屏幕尺寸这么多,为了让咱们开发的程序可以比较美观的显示在不一样尺寸、分辨率、像素密度(这些概念我会在下面详细讲解)的设备上,那就要在开发的过程当中进行处理,至于如何去进行处理,这就是咱们今天的主题了。
可是在开始进入主题以前,咱们再来探讨一件事情,那就是Android设备的屏幕尺寸,从几寸的智能手机,到10寸的平板电脑,再到几十寸的数字电视,咱们应该适配哪些设备呢?
其实这个问题不该该这么考虑,由于对于具备相同像素密度的设备来讲,像素越高,尺寸就越大,因此咱们能够换个思路,将问题从单纯的尺寸大小转换到像素大小和像素密度的角度来。
下图是2014年初,友盟统计的占比5%以上的6个主流分辨率,能够看出,占比最高的是480*800,320*480的设备居然也占据了很大比例,可是和半年前的数据相比较,中低分辨率(320*480、480*800)的比例在减小,而中高分辨率的比例则在不断地增长。虽然每一个分辨率所占的比例在变化,可是总的趋势没变,仍是这六种,只是分辨率在不断地提升。
因此说,咱们只要尽可能适配这几种分辨率,就能够在大部分的手机上正常运行了。
固然了,这只是手机的适配,对于平板设备(电视也能够看作是平板),咱们还须要一些其余的处理。
好了,到目前为止,咱们已经弄清楚了Android开发为何要进行适配,以及咱们应该适配哪些对象,接下来,终于进入咱们的正题了!
首先,咱们先要学习几个重要的概念。
重要概念
什么是屏幕尺寸、屏幕分辨率、屏幕像素密度?
什么是dp、dip、dpi、sp、px?他们之间的关系是什么?
什么是mdpi、hdpi、xdpi、xxdpi?如何计算和区分?
在下面的内容中咱们将介绍这些概念。
屏幕尺寸
屏幕尺寸指屏幕的对角线的长度,单位是英寸,1英寸=2.54厘米
好比常见的屏幕尺寸有2.四、2.八、3.五、3.七、4.二、5.0、5.五、6.0等
屏幕分辨率
屏幕分辨率是指在横纵向上的像素点数,单位是px,1px=1个像素点。通常以纵向像素*横向像素,如1960*1080。
屏幕像素密度
屏幕像素密度是指每英寸上的像素点数,单位是dpi,即“dot per inch”的缩写。屏幕像素密度与屏幕尺寸和屏幕分辨率有关,在单一变化条件下,屏幕尺寸越小、分辨率越高,像素密度越大,反之越小。
dp、dip、dpi、sp、px
px咱们应该是比较熟悉的,前面的分辨率就是用的像素为单位,大多数状况下,好比UI设计、Android原生API都会以px做为统一的计量单位,像是获取屏幕宽高等。
dip和dp是一个意思,都是Density Independent Pixels的缩写,即密度无关像素,上面咱们说过,dpi是屏幕像素密度,假如一英寸里面有160个像素,这个屏幕的像素密度就是160dpi,那么在这种状况下,dp和px如何换算呢?在Android中,规定以160dpi为基准,1dip=1px,若是密度是320dpi,则1dip=2px,以此类推。
假如一样都是画一条320px的线,在480*800分辨率手机上显示为2/3屏幕宽度,在320*480的手机上则占满了全屏,若是使用dp为单位,在这两种分辨率下,160dp都显示为屏幕一半的长度。这也是为何在Android开发中,写布局的时候要尽可能使用dp而不是px的缘由。
而sp,即scale-independent pixels,与dp相似,可是能够根据文字大小首选项进行放缩,是设置字体大小的御用单位。
mdpi、hdpi、xdpi、xxdpi
其实以前还有个ldpi,可是随着移动设备配置的不断升级,这个像素密度的设备已经很罕见了,所在如今适配时不需考虑。
mdpi、hdpi、xdpi、xxdpi用来修饰Android中的drawable文件夹及values文件夹,用来区分不一样像素密度下的图片和dimen值。
那么如何区分呢?Google官方指定按照下列标准进行区分:
在进行开发的时候,咱们须要把合适大小的图片放在合适的文件夹里面。下面以图标设计为例进行介绍。
在设计图标时,对于五种主流的像素密度(MDPI、HDPI、XHDPI、XXHDPI 和 XXXHDPI)应按照 2:3:4:6:8 的比例进行缩放。例如,一个启动图标的尺寸为48x48 dp,这表示在 MDPI 的屏幕上其实际尺寸应为 48x48 px,在 HDPI 的屏幕上其实际大小是 MDPI 的 1.5 倍 (72x72 px),在 XDPI 的屏幕上其实际大小是 MDPI 的 2 倍 (96x96 px),依此类推。
虽然 Android 也支持低像素密度 (LDPI) 的屏幕,但无需为此费神,系统会自动将 HDPI 尺寸的图标缩小到 1/2 进行匹配。
下图为图标的各个屏幕密度的对应尺寸:
解决方案
支持各类屏幕尺寸
使用wrap_content、match_parent、weight
要确保布局的灵活性并适应各类尺寸的屏幕,应使用 “wrap_content” 和 “match_parent” 控制某些视图组件的宽度和高度。
使用 “wrap_content”,系统就会将视图的宽度或高度设置成所需的最小尺寸以适应视图中的内容,而 “match_parent”(在低于 API 级别 8 的级别中称为 “fill_parent”)则会展开组件以匹配其父视图的尺寸。
若是使用 “wrap_content” 和 “match_parent” 尺寸值而不是硬编码的尺寸,视图就会相应地仅使用自身所需的空间或展开以填满可用空间。此方法可以让布局正确适应各类屏幕尺寸和屏幕方向。
下面是一段示例代码
下图是在横纵屏切换的时候的显示效果,咱们能够看到这样能够很好的适配屏幕尺寸的变化。
weight是线性布局的一个独特的属性,咱们可使用这个属性来按照比例对界面进行分配,完成一些特殊的需求。
可是,咱们对于这个属性的计算应该如何理解呢?
首先看下面的例子,咱们在布局中这样设置咱们的界面
咱们在布局里面设置为线性布局,横向排列,而后放置两个宽度为0dp的按钮,分别设置weight为1和2,在效果图中,咱们能够看到两个按钮按照1:2的宽度比例正常排列了,这也是咱们常用到的场景,这是时候很好理解,Button1的宽度就是1/(1+2) = 1/3,Button2的宽度则是2/(1+2) = 2/3,咱们能够很清楚的明白这种情景下的占好比何计算。
可是假如咱们的宽度不是0dp(wrap_content和0dp的效果相同),则是match_parent呢?
下面是设置为match_parent的效果
咱们能够看到,在这种状况下,占比和上面正好相反,这是怎么回事呢?说到这里,咱们就不得不提一下weight的计算方法了。
android:layout_weight的真实含义是:若是View设置了该属性而且有效,那么该 View的宽度等于原有宽度(android:layout_width)加上剩余空间的占比。
从这个角度咱们来解释一下上面的现象。在上面的代码中,咱们设置每一个Button的宽度都是match_parent,假设屏幕宽度为L,那么每一个Button的宽度也应该都为L,剩余宽度就等于L-(L+L)= -L。
Button1的weight=1,剩余宽度占比为1/(1+2)= 1/3,因此最终宽度为L+1/3*(-L)=2/3L,Button2的计算相似,最终宽度为L+2/3(-L)=1/3L。
这是在水平方向上的,那么在垂直方向上也是这样吗?
下面是测试代码和效果
若是是垂直方向,那么咱们应该改变的是layout_height的属性,下面是0dp的显示效果
下面是match_parent的显示效果,结论和水平是彻底同样的
虽说咱们演示了match_parent的显示效果,并说明了缘由,可是在真正用的时候,咱们都是设置某一个属性为0dp,而后按照权重计算所占百分比。
使用相对布局,禁用绝对布局
在开发中,咱们大部分时候使用的都是线性布局、相对布局和帧布局,绝对布局因为适配性极差,因此极少使用。
因为各类布局的特色不同,因此不能说哪一个布局好用,到底应该使用什么布局只能根据实际需求来肯定。咱们可使用 LinearLayout 的嵌套实例并结合 “wrap_content” 和 “match_parent”,以便构建至关复杂的布局。不过,咱们没法经过 LinearLayout 精确控制子视图的特殊关系;系统会将 LinearLayout 中的视图直接并排列出。
若是咱们须要将子视图排列出各类效果而不是一条直线,一般更合适的解决方法是使用 RelativeLayout,这样就能够根据各组件之间的特殊关系指定布局了。例如,咱们能够将某个子视图对齐到屏幕左侧,同时将另外一个视图对齐到屏幕右侧。
下面的代码以官方Demo为例说明。
在上面的代码中咱们使用了相对布局,而且使用alignXXX等属性指定了子控件的位置,下面是这种布局方式在应对屏幕变化时的表现
在小尺寸屏幕的显示
在平板的大尺寸上的显示效果
虽然控件的大小因为屏幕尺寸的增长而发生了改变,可是咱们能够看到,因为使用了相对布局,因此控件以前的位置关系并无发生什么变化,这说明咱们的适配成功了。
使用限定符
使用尺寸限定符
上面所提到的灵活布局或者是相对布局,能够为咱们带来的优点就只有这么多了。虽然这些布局能够拉伸组件内外的空间以适应各类屏幕,但它们不必定能为每种屏幕都提供最佳的用户体验。所以,咱们的应用不只仅只实施灵活布局,还应该应针对各类屏幕配置提供一些备用布局。
如何作到这一点呢?咱们能够经过使用配置限定符,在运行时根据当前的设备配置自动选择合适的资源了,例如根据各类屏幕尺寸选择不一样的布局。
不少应用会在较大的屏幕上实施“双面板”模式,即在一个面板上显示项目列表,而在另外一面板上显示对应内容。平板电脑和电视的屏幕已经大到能够同时容纳这两个面板了,但手机屏幕就须要分别显示。所以,咱们可使用如下文件以便实施这些布局:
res/layout/main.xml,单面板(默认)布局:
res/layout-large/main.xml,双面板布局:
请注意第二种布局名称目录中的 large 限定符。系统会在属于较大屏幕(例如 7 英寸或更大的平板电脑)的设备上选择此布局。系统会在较小的屏幕上选择其余布局(无限定符)。
使用最小宽度限定符
在版本低于 3.2 的 Android 设备上,开发人员遇到的问题之一是“较大”屏幕的尺寸范围,该问题会影响戴尔 Streak、早期的 Galaxy Tab 以及大部分 7 英寸平板电脑。即便这些设备的屏幕属于“较大”的尺寸,但不少应用可能会针对此类别中的各类设备(例如 5 英寸和 7 英寸的设备)显示不一样的布局。这就是 Android 3.2 版在引入其余限定符的同时引入“最小宽度”限定符的缘由。
最小宽度限定符可以让您经过指定某个最小宽度(以 dp 为单位)来定位屏幕。例如,标准 7 英寸平板电脑的最小宽度为 600 dp,所以若是您要在此类屏幕上的用户界面中使用双面板(但在较小的屏幕上只显示列表),您可使用上文中所述的单面板和双面板这两种布局,但您应使用 sw600dp 指明双面板布局仅适用于最小宽度为 600 dp 的屏幕,而不是使用 large 尺寸限定符。
res/layout/main.xml,单面板(默认)布局:
res/layout-sw600dp/main.xml,双面板布局:
也就是说,对于最小宽度大于等于 600 dp 的设备,系统会选择 layout-sw600dp/main.xml(双面板)布局,不然系统就会选择 layout/main.xml(单面板)布局。
但 Android 版本低于 3.2 的设备不支持此技术,缘由是这些设备没法将 sw600dp 识别为尺寸限定符,所以咱们仍需使用 large 限定符。这样一来,就会有一个名称为 res/layout-large/main.xml 的文件(与 res/layout-sw600dp/main.xml 同样)。可是没有太大关系,咱们将立刻学习如何避免此类布局文件出现的重复。
使用布局别名
最小宽度限定符仅适用于 Android 3.2 及更高版本。所以,若是咱们仍需使用与较低版本兼容的归纳尺寸范围(小、正常、大和特大)。例如,若是要将用户界面设计成在手机上显示单面板,但在 7 英寸平板电脑、电视和其余较大的设备上显示多面板,那么咱们就须要提供如下文件:
res/layout/main.xml: 单面板布局
res/layout-large: 多面板布局
res/layout-sw600dp: 多面板布局
后两个文件是相同的,由于其中一个用于和 Android 3.2 设备匹配,而另外一个则是为使用较低版本 Android 的平板电脑和电视准备的。
要避免平板电脑和电视的文件出现重复(以及由此带来的维护问题),您可使用别名文件。例如,您能够定义如下布局:
res/layout/main.xml,单面板布局
res/layout/main_twopanes.xml,双面板布局
而后添加这两个文件:
res/values-large/layout.xml:
res/values-sw600dp/layout.xml:
后两个文件的内容相同,但它们并未实际定义布局。它们只是将 main 设置成了 main_twopanes 的别名。因为这些文件包含 large 和 sw600dp 选择器,所以不管 Android 版本如何,系统都会将这些文件应用到平板电脑和电视上(版本低于 3.2 的平板电脑和电视会匹配 large,版本高于 3.2 的平板电脑和电视则会匹配 sw600dp)。
使用屏幕方向限定符
某些布局会同时支持横向模式和纵向模式,但咱们能够经过调整优化其中大部分布局的效果。在新闻阅读器示例应用中,每种屏幕尺寸和屏幕方向下的布局行为方式以下所示:
小屏幕,纵向:单面板,带徽标
小屏幕,横向:单面板,带徽标
7 英寸平板电脑,纵向:单面板,带操做栏
7 英寸平板电脑,横向:双面板,宽,带操做栏
10 英寸平板电脑,纵向:双面板,窄,带操做栏
10 英寸平板电脑,横向:双面板,宽,带操做栏
电视,横向:双面板,宽,带操做栏
所以,这些布局中的每一种都定义在了 res/layout/ 目录下的某个 XML 文件中。为了继续将每一个布局分配给各类屏幕配置,该应用会使用布局别名将二者相匹配:
res/layout/onepane.xml:(单面板)
res/layout/onepane_with_bar.xml:(单面板带操做栏)
res/layout/twopanes.xml:(双面板,宽布局)
res/layout/twopanes_narrow.xml:(双面板,窄布局)
既然咱们已定义了全部可能的布局,那就只需使用配置限定符将正确的布局映射到各类配置便可。
如今只需使用布局别名技术便可作到这一点:
res/values/layouts.xml:
res/values-sw600dp-land/layouts.xml:
res/values-sw600dp-port/layouts.xml:
res/values-large-land/layouts.xml:
res/values-large-port/layouts.xml:
使用自动拉伸位图
支持各类屏幕尺寸一般意味着您的图片资源还必须能适应各类尺寸。例如,不管要应用到什么形状的按钮上,按钮背景都必须能适应。
若是在能够更改尺寸的组件上使用了简单的图片,您很快就会发现显示效果多少有些不太理想,由于系统会在运行时平均地拉伸或收缩您的图片。解决方法为使用自动拉伸位图,这是一种格式特殊的 PNG 文件,其中会指明能够拉伸以及不能够拉伸的区域。
.9的制做,实际上就是在原图片上添加1px的边界,而后按照咱们的需求,把对应的位置设置成黑色线,系统就会根据咱们的实际需求进行拉伸。
下图是对.9图的四边的含义的解释,左上边表明拉伸区域,右下边表明padding box,就是间隔区域,在下面,咱们给出一个例子,方便你们理解。
先看下面两张图,咱们理解一下这四条线的含义。
上图和下图的区别,就在于右下边的黑线不同,具体的效果的区别,看右边的效果图。上图效果图中深蓝色的区域,表明内容区域,咱们能够看到是在正中央的,这是由于咱们在右下边的是两个点,这两个点距离上下左右四个方向的距离就是padding的距离,因此深蓝色内容区域在图片正中央,咱们再看下图,因为右下边的黑线是图片长度,因此就没有padding,从效果图上的表现就是深蓝色区域和图片同样大,所以,咱们能够利用右下边来控制内容与背景图边缘的padding。
若是你还不明白,那么咱们看下面的效果图,咱们分别以图一和图二做为背景图,下面是效果图。
咱们能够看到,使用wrap_content属性设置长宽,图一比图二的效果大一圈,这是为何呢?还记得我上面说的padding吗?
这就是padding的效果提现,怎么证实呢?咱们再看下面一张图,给图一添加padding=0,这样背景图设置的padding效果就没了,是否是两个同样大了?
ok,我想你应该明白右下边的黑线的含义了,下面咱们再看一下左上边的效果。
下面咱们只设置了左上边线,效果图以下
上面的线没有包住图标,下面的线正好包住了图标,从右边的效果图应该能够看出差异,黑线所在的区域就是拉伸区域,上图黑线所在的全是纯色,因此图标不变形,下面的拉伸区域包裹了图标,因此在拉伸的时候就会对图标进行拉伸,可是这样就会致使图标变形。注意到下面红线区域了嘛?这是系统提示咱们的,由于这样拉伸,不符合要求,因此会提示一下。
支持各类屏幕密度
使用非密度制约像素
因为各类屏幕的像素密度都有所不一样,所以相同数量的像素在不一样设备上的实际大小也有所差别,这样使用像素定义布局尺寸就会产生问题。所以,请务必使用 dp 或 sp 单位指定尺寸。dp 是一种非密度制约像素,其尺寸与 160 dpi 像素的实际尺寸相同。sp 也是一种基本单位,但它可根据用户的偏好文字大小进行调整(即尺度独立性像素),所以咱们应将该测量单位用于定义文字大小。
例如,请使用 dp(而非 px)指定两个视图间的间距:
请务必使用 sp 指定文字大小:
除了介绍这些最基础的知识以外,咱们下面再来讨论一下另一个问题。
通过上面的介绍,咱们都清楚,为了可以规避不一样像素密度的陷阱,Google推荐使用dp来代替px做为控件长度的度量单位,可是咱们来看下面的一个场景。
假如咱们以Nexus5做为书写代码时查看效果的测试机型,Nexus5的总宽度为360dp,咱们如今须要在水平方向上放置两个按钮,一个是150dp左对齐,另一个是200dp右对齐,中间留有10dp间隔,那么在Nexus5上面的显示效果就是下面这样
<可是若是在Nexus S或者是Nexus One运行呢?下面是运行结果
能够看到,两个按钮发生了重叠。
咱们都已经用了dp了,为何会出现这种状况呢?
你听我慢慢道来。
虽说dp能够去除不一样像素密度的问题,使得1dp在不一样像素密度上面的显示效果相同,可是仍是因为Android屏幕设备的多样性,若是使用dp来做为度量单位,并非全部的屏幕的宽度都是相同的dp长度,好比说,Nexus S和Nexus One属于hdpi,屏幕宽度是320dp,而Nexus 5属于xxhdpi,屏幕宽度是360dp,Galaxy Nexus属于xhdpi,屏幕宽度是384dp,Nexus 6 属于xxxhdpi,屏幕宽度是410dp。因此说,光Google本身一家的产品就已经有这么多的标准,并且屏幕宽度和像素密度没有任何关联关系,即便咱们使用dp,在320dp宽度的设备和410dp的设备上,仍是会有90dp的差异。固然,咱们尽可能使用match_parent和wrap_content,尽量少的用dp来指定控件的具体长宽,再结合上权重,大部分的状况咱们都是能够作到适配的。
可是除了这个方法,咱们还有没有其余的更完全的解决方案呢?
咱们换另一个思路来思考这个问题。
下面的方案来自Android Day Day Up 一群的【blue-深圳】,谢谢他的分享精神。
由于分辨率不同,因此不能用px;由于屏幕宽度不同,因此要当心的用dp,那么咱们可不能够用另一种方法来统一单位,无论分辨率是多大,屏幕宽度用一个固定的值的单位来统计呢?
答案是:固然能够。
咱们假设手机屏幕的宽度都是320某单位,那么咱们将一个屏幕宽度的总像素数平均分红320份,每一份对应具体的像素就能够了。
具体如何来实现呢?咱们看下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
import
java.io.File;
import
java.io.FileNotFoundException;
import
java.io.FileOutputStream;
import
java.io.PrintWriter;
public
class
MakeXml {
private
final
static
String rootPath =
"C:\\Users\\Administrator\\Desktop\\layoutroot\\values-{0}x{1}\\"
;
private
final
static
float
dw = 320f;
private
final
static
float
dh = 480f;
private
final
static
String WTemplate =
"[dimen name=\"x{0}\"]{1}px[/dimen]\n"
;
private
final
static
String HTemplate =
"[dimen name=\"y{0}\"]{1}px[/dimen]\n"
;
public
static
void
main(String[] args) {
makeString(
320
,
480
);
makeString(
480
,
800
);
makeString(
480
,
854
);
makeString(
540
,
960
);
makeString(
600
,
1024
);
makeString(
720
,
1184
);
makeString(
720
,
1196
);
makeString(
720
,
1280
);
makeString(
768
,
1024
);
makeString(
800
,
1280
);
makeString(
1080
,
1812
);
makeString(
1080
,
1920
);
makeString(
1440
,
2560
);
}
public
static
void
makeString(
int
w,
int
h) {
StringBuffer sb =
new
StringBuffer();
sb.append(
"[?xml version=\"1.0\" encoding=\"utf-8\"?]\n"
);
sb.append(
"[resources]"
);
float
cellw = w / dw;
for
(
int
i =
1
; i <
320
; i++) {
sb.append(WTemplate.replace(
"{0}"
, i +
""
).replace(
"{1}"
,
change(cellw * i) +
""
));
}
sb.append(WTemplate.replace(
"{0}"
,
"320"
).replace(
"{1}"
, w +
""
));
sb.append(
"[/resources]"
);
StringBuffer sb2 =
new
StringBuffer();
sb2.append(
"[?xml version=\"1.0\" encoding=\"utf-8\"?]\n"
);
sb2.append(
"[resources]"
);
float
cellh = h / dh;
for
(
int
i =
1
; i <
480
; i++) {
sb2.append(HTemplate.replace(
"{0}"
, i +
""
).replace(
"{1}"
,
change(cellh * i) +
""
));
}
sb2.append(HTemplate.replace(
"{0}"
,
"480"
).replace(
"{1}"
, h +
""
));
sb2.append(
"[/resources]"
);
String path = rootPath.replace(
"{0}"
, h +
""
).replace(
"{1}"
, w +
""
);
File rootFile =
new
File(path);
if
(!rootFile.exists()) {
rootFile.mkdirs();
}
File layxFile =
new
File(path +
"lay_x.xml"
);
File layyFile =
new
File(path +
"lay_y.xml"
);
try
{
PrintWriter pw =
new
PrintWriter(
new
FileOutputStream(layxFile));
pw.print(sb.toString());
pw.close();
pw =
new
PrintWriter(
new
FileOutputStream(layyFile));
pw.print(sb2.toString());
pw.close();
}
catch
(FileNotFoundException e) {
e.printStackTrace();
}
}
public
static
float
change(
float
a) {
int
temp = (
int
) (a *
100
);
return
temp / 100f;
}
}
|
代码应该很好懂,咱们将一个屏幕宽度分为320份,高度480份,而后按照实际像素对每个单位进行复制,放在对应values-widthxheight文件夹下面的lax.xml和lay.xml里面,这样就能够统一全部你想要的分辨率的单位了,下面是生成的一个320*480分辨率的文件,由于宽高分割以后总分数和像素数相同,因此x1就是1px,以此类推。
那么1080*1960分辨率下是什么样子呢?咱们能够看下,因为1080和320是3.37倍的关系,因此x1=3.37px
不管在什么分辨率下,x320都是表明屏幕宽度,y480都是表明屏幕高度。
那么,咱们应该如何使用呢?
首先,咱们要把生成的全部values文件夹放到res目录下,当设计师把UI高清设计图给你以后,你就能够根据设计图上的尺寸,以某一个分辨率的机型为基础,找到对应像素数的单位,而后设置给控件便可。
下图仍是两个Button,不一样的是,咱们把单位换成了咱们在values文件夹下dimen的值,这样在你指定的分辨率下,无论宽度是320dp、360dp,仍是410dp,就均可以彻底适配了。
可是,仍是有个问题,为何下面的三个没有适配呢?
这是由于因为在生成的values文件夹里,没有对应的分辨率,其实一开始是报错的,由于默认的values没有对应dimen,因此我只能在默认values里面也建立对应文件,可是里面的数据却很差处理,由于不知道分辨率,我只好默认为x1=1dp保证尽可能兼容。这也是这个解决方案的几个弊端,对于没有生成对应分辨率文件的手机,会使用默认values文件夹,若是默认文件夹没有,就会出现问题。
因此说,这个方案虽然是一劳永逸,可是因为实际上仍是使用的px做为长度的度量单位,因此多少和google的要求有所背离,很差说之后会不会出现什么不可预测的问题。其次,若是要使用这个方案,你必须尽量多的包含全部的分辨率,由于这个是使用这个方案的基础,若是有分辨率缺乏,会形成显示效果不好,甚至出错的风险,而这又势必会增长软件包的大小和维护的难度,因此你们本身斟酌,择优使用。
更多信息可参考鸿洋的新文章:Android 屏幕适配方案。
提供备用位图
因为 Android 可在具备各类屏幕密度的设备上运行,所以咱们提供的位图资源应始终能够知足各种广泛密度范围的要求:低密度、中等密度、高密度以及超高密度。这将有助于咱们的图片在全部屏幕密度上都能获得出色的质量和效果。
要生成这些图片,咱们应先提取矢量格式的原始资源,而后根据如下尺寸范围针对各密度生成相应的图片。
xhdpi:2.0
hdpi:1.5
mdpi:1.0(最低要求)
ldpi:0.75
也就是说,若是咱们为 xhdpi 设备生成了 200x200 px尺寸的图片,就应该使用同一资源为 hdpi、mdpi 和 ldpi 设备分别生成 150x150、100x100 和 75x75 尺寸的图片。
而后,将生成的图片文件放在 res/ 下的相应子目录中(mdpi、hdpi、xhdpi、xxhdpi),系统就会根据运行您应用的设备的屏幕密度自动选择合适的图片。
这样一来,只要咱们引用 @drawable/id,系统都能根据相应屏幕的 dpi 选取合适的位图。
还记得咱们上面提到的图标设计尺寸吗?和这个实际上是一个意思。
可是还有个问题须要注意下,若是是.9图或者是不须要多个分辨率的图片,就放在drawable文件夹便可,对应分辨率的图片要正确的放在合适的文件夹,不然会形成图片拉伸等问题。
实施自适应用户界面流程
前面咱们介绍过,如何根据设备特色显示恰当的布局,可是这样作,会使得用户界面流程可能会有所不一样。例如,若是应用处于双面板模式下,点击左侧面板上的项便可直接在右侧面板上显示相关内容;而若是该应用处于单面板模式下,点击相关的内容应该跳转到另一个Activity进行后续的处理。因此咱们应该按照下面的流程,一步步的完成自适应界面的实现。
肯定当前布局
因为每种布局的实施都会稍有不一样,所以咱们须要先肯定当前向用户显示的布局。例如,咱们能够先了解用户所处的是“单面板”模式仍是“双面板”模式。要作到这一点,能够经过查询指定视图是否存在以及是否已显示出来。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
ublic
class
NewsReaderActivity
extends
FragmentActivity {
boolean
mIsDualPane;
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
View articleView = findViewById(R.id.article);
mIsDualPane = articleView !=
null
&&
articleView.getVisibility() == View.VISIBLE;
}
}
|
请注意,这段代码用于查询“报道”面板是否可用,与针对具体布局的硬编码查询相比,这段代码的灵活性要大得多。
再举一个适应各类组件的存在状况的方法示例:在对这些组件执行操做前先查看它们是否可用。例如,新闻阅读器示例应用中有一个用于打开菜单的按钮,但只有在版本低于 3.0 的 Android 上运行该应用时,这个按钮才会存在,由于 API 级别 11 或更高级别中的 ActionBar 已取代了该按钮的功能。所以,您可使用如下代码为此按钮添加事件侦听器:
1
2
3
4
5
|
Button catButton = (Button) findViewById(R.id.categorybutton);
OnClickListener listener =
/* create your listener here */
;
if
(catButton !=
null
) {
catButton.setOnClickListener(listener);
}
|
根据当前布局作出响应
有些操做可能会因当前的具体布局而产生不一样的结果。例如,在新闻阅读器示例中,若是用户界面处于双面板模式下,那么点击标题列表中的标题就会在右侧面板中打开相应报道;但若是用户界面处于单面板模式下,那么上述操做就会启动一个独立活动:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@Override
public void onHeadlineSelected(int index) {
mArtIndex = index;
if
(mIsDualPane) {
/* display article on the right pane */
mArticleFragment.displayArticle(mCurrentCat.getArticle(index));
}
else
{
/* start a separate activity */
Intent intent =
new
Intent(
this
, ArticleActivity.class);
intent.putExtra(
"catIndex"
, mCatIndex);
intent.putExtra(
"artIndex"
, index);
startActivity(intent);
}
}
|
一样,若是该应用处于双面板模式下,就应设置带导航标签的操做栏;但若是该应用处于单面板模式下,就应使用下拉菜单设置导航栏。所以咱们的代码还应肯定哪一种状况比较合适:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
final String CATEGORIES[] = {
"热门报道"
,
"政治"
,
"经济"
,
"Technology"
};
public void onCreate(Bundle savedInstanceState) {
....
if
(mIsDualPane) {
/* use tabs for navigation */
actionBar.setNavigationMode(android.app.ActionBar.NAVIGATION_MODE_TABS);
int i;
for
(i = 0; i < CATEGORIES.length; i++) {
actionBar.addTab(actionBar.newTab().setText(
CATEGORIES[i]).setTabListener(handler));
}
actionBar.setSelectedNavigationItem(selTab);
}
else
{
/* use list navigation (spinner) */
actionBar.setNavigationMode(android.app.ActionBar.NAVIGATION_MODE_LIST);
SpinnerAdapter adap =
new
ArrayAdapter(
this
,
R.layout.headline_item, CATEGORIES);
actionBar.setListNavigationCallbacks(adap, handler);
}
}
|
重复使用其余活动中的片断
多屏幕设计中的重复模式是指,对于某些屏幕配置,已实施界面的一部分会用做面板;但对于其余配置,这部分就会以独立活动的形式存在。例如,在新闻阅读器示例中,对于较大的屏幕,新闻报道文本会显示在右侧面板中;但对于较小的屏幕,这些文本就会以独立活动的形式存在。
在相似状况下,一般能够在多个活动中重复使用相同的 Fragment 子类以免代码重复。例如,在双面板布局中使用了 ArticleFragment:
而后又在小屏幕的Activity布局中重复使用了它 :
1
2
|
ArticleFragment frag =
new
ArticleFragment();
getSupportFragmentManager().beginTransaction().add(android.R.id.content, frag).commit();
|
固然,这与在 XML 布局中声明片断的效果是同样的,但在这种状况下却不必使用 XML 布局,由于报道片断是此活动中的惟一组件。
请务必在设计片断时注意,不要针对具体活动建立强耦合。要作到这一点,一般能够定义一个接口,该接口归纳了相关片断与其主活动交互所需的所有方式,而后让主活动实施该界面:
例如,新闻阅读器应用的 HeadlinesFragment 会精确执行如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class HeadlinesFragment extends ListFragment {
...
OnHeadlineSelectedListener mHeadlineSelectedListener =
null
;
/* Must be implemented by host activity */
public interface OnHeadlineSelectedListener {
public void onHeadlineSelected(int index);
}
...
public void setOnHeadlineSelectedListener(OnHeadlineSelectedListener listener) {
mHeadlineSelectedListener = listener;
}
}
|
而后,若是用户选择某个标题,相关片断就会通知由主活动指定的侦听器(而不是通知某个硬编码的具体活动):
1
2
3
4
5
6
7
8
9
10
11
|
public class HeadlinesFragment extends ListFragment {
...
@Override
public void onItemClick(AdapterView parent,
View view, int position, long id) {
if
(
null
!= mHeadlineSelectedListener) {
mHeadlineSelectedListener.onHeadlineSelected(position);
}
}
...
}
|
除此以外,咱们还可使用第三方框架,好比说使用“订阅-发布”模式的EventBus来更多的优化组件之间的通讯,减小耦合。
处理屏幕配置变化
若是咱们使用独立Activity实施界面的独立部分,那么请注意,咱们可能须要对特定配置变化(例如屏幕方向的变化)作出响应,以便保持界面的一致性。
例如,在运行 Android 3.0 或更高版本的标准 7 英寸平板电脑上,若是新闻阅读器示例应用运行在纵向模式下,就会在使用独立活动显示新闻报道;但若是该应用运行在横向模式下,就会使用双面板布局。
也就是说,若是用户处于纵向模式下且屏幕上显示的是用于阅读报道的活动,那么就须要在检测到屏幕方向变化(变成横向模式)后执行相应操做,即中止上述活动并返回主活动,以便在双面板布局中显示相关内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public class ArticleActivity extends FragmentActivity {
int mCatIndex, mArtIndex;
@Override
protected void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
mCatIndex = getIntent().getExtras().getInt(
"catIndex"
, 0);
mArtIndex = getIntent().getExtras().getInt(
"artIndex"
, 0);
// If should be in two-pane mode, finish to return to main activity
if
(getResources().getBoolean(R.bool.has_two_panes)) {
finish();
return
;
}
...
}
|
经过上面几个步骤,咱们就彻底能够创建一个能够根据用户界面配置进行自适应的App了。
最佳实践
关于高清设计图尺寸
Google官方给出的高清设计图尺寸有两种方案,一种是以mdpi设计,而后对应放大获得更高分辨率的图片,另一种则是以高分辨率做为设计大小,而后按照倍数对应缩小到小分辨率的图片。
根据经验,我更推荐第二种方法,由于小分辨率在生成高分辨率图片的时候,会出现像素丢失,我不知道是否是有方法能够阻止这种状况发生。
而分辨率能够以1280*720或者是1960*1080做为主要分辨率进行设计。
ImageView的ScaleType属性
设置不一样的ScaleType会获得不一样的显示效果,通常状况下,设置为centerCrop能得到较好的适配效果。
动态设置
有一些状况下,咱们须要动态的设置控件大小或者是位置,好比说popwindow的显示位置和偏移量等,这个时候咱们能够动态的获取当前的屏幕属性,而后设置合适的数值
1
2
3
4
5
6
7
8
9
10
11
|
public class ScreenSizeUtil {
public static int getScreenWidth(Activity activity) {
return
activity.getWindowManager().getDefaultDisplay().getWidth();
}
public static int getScreenHeight(Activity activity) {
return
activity.getWindowManager().getDefaultDisplay().getHeight();
}
}
|
转自:连接