支持各种尺寸的屏幕
- 支持多种屏幕
下载示例项目
NewsReader.zip
该课程将告诉您如何通过如下方式来支持各种尺寸的屏幕:
- 确保您的布局可以改变大小来填充整个屏幕
- 根据屏幕的配置来显示不同的布局
- 确保布局应用到对应的屏幕上
- 使用能正确缩放的图片
要确保您的布局是弹性的并且可以适应各种尺寸的屏幕,你应该使用"wrap_content" 和"match_parent" 来设置一些控件的宽高。如果使用"wrap_content" ,控件的宽度和高度将会设置为能够显示该控件内容的最小尺寸;而当你使用"match_parent" (在API level 8之前被称之为"fill_parent" )的时候,控件的高度和宽度将和父控件的大小一样。
当使用"wrap_content" 和 "match_parent" 来代替具体的大小数字的时候,您的控件要么只使用能显示其内容的最小尺寸或者填充整个能用的空间。下面是一个示例:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:id="@+id/linearLayout1" android:gravity="center" android:layout_height="50dp"> <ImageView android:id="@+id/imageView1" android:layout_height="wrap_content" android:layout_width="wrap_content" android:src="/blog_article/@drawable/logo/index.html" android:paddingRight="30dp" android:layout_gravity="left" android:layout_weight="0" /> <View android:layout_height="wrap_content" android:id="@+id/view1" android:layout_width="wrap_content" android:layout_weight="1" /> <Button android:id="@+id/categorybutton" android:background="@drawable/button_bg" android:layout_height="match_parent" android:layout_weight="0" android:layout_width="120dp" /> </LinearLayout> <fragment android:id="@+id/headlines" android:layout_height="fill_parent" android:name="com.example.android.newsreader.HeadlinesFragment" android:layout_width="match_parent" /> </LinearLayout>
注意示例中的代码是如何使用 "wrap_content" 和 "match_parent" 而不是使用具体的数值来指定控件的大小。这样布局就可以根据不同的屏幕尺寸和方向来自适应显示界面。
下图是该布局在横向或者纵向的情况下的显示情况,注意 控件的宽度和高度自动的适应屏幕的尺寸:
新闻阅读程序在横向(左)和纵向(右)的显示
通过"wrap_content" 和 "match_parent" 以及嵌套使用多个LinearLayout 您可以实现各种复杂的布局。然而,LinearLayout 不允许精确的控制子控件之间的关系;在LinearLayout 里面的控件只是一个挨着一个去布局的。如果您不想让控件只是排成一行或者一列,使用 RelativeLayout 是一个更好的选择,该布局可以指定子控件之间的相对位置。例如,你可以指定一个子控件位于屏幕的左边而另外一个位于屏幕的右边。
示例
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/label" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Type here:"/> <EditText android:id="@+id/entry" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/label"/> <Button android:id="@+id/ok" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/entry" android:layout_alignParentRight="true" android:layout_marginLeft="10dp" android:text="OK" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_toLeftOf="@id/ok" android:layout_alignTop="@id/ok" android:text="Cancel" /> </RelativeLayout>
下图显示了该布局在QVGA屏幕上的界面
在QVGA屏幕上的截图(小屏幕)
在大屏幕上的界面
在WSVGA屏幕上的截图(大屏幕)
注意:尽管控件的大小改变了, 但是他们之间的相对位置通过RelativeLayout.LayoutParams 定义为一样。
使用尺寸限定符您可以通过前面的方法实现各种各样的自适应布局和相对布局。但是这种布局只是通过把控件拉伸或者拉伸控件周围的空间,对于大小不同的屏幕并没有提供最优的用户体验。因此,您的程序应该不仅仅只是实现自适应的布局,还应该根据不同的屏幕配置分别提供更加友好的布局。通过配置限定符 可以实现这个优化,这样在运行时系统会自动的选择适合当前设备的布局和资源(例如:针对不同屏幕使用不同的布局)。
例如,很多程序在大屏幕设备上使用“两个窗口”布局模式(程序可以在一个窗口中显示一个列表而在另外一个窗口中显示列表中选中的内容)。平板设备和电视剧的屏幕足够用来同时显示两个窗口,但是手机设备就只能分别显示他们了。因此,要实现这种布局,您需要使用如下的文件:
- res/layout/main.xml , 单个窗口(默认)布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/headlines" android:layout_height="fill_parent" android:name="com.example.android.newsreader.HeadlinesFragment" android:layout_width="match_parent" /> </LinearLayout>
- res/layout-xlarge/main.xml , 两个窗口布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal"> <fragment android:id="@+id/headlines" android:layout_height="fill_parent" android:name="com.example.android.newsreader.HeadlinesFragment" android:layout_width="400dp" android:layout_marginRight="10dp"/> <fragment android:id="@+id/article" android:layout_height="fill_parent" android:name="com.example.android.newsreader.ArticleFragment" android:layout_width="fill_parent" /> </LinearLayout>
注意上面的文件夹中的xlarge 限定符,这个文件夹里面的布局只会用到屏幕尺寸为超级大(例如:10寸的平板)的设备中。其他的布局(没有限定符的)将会用于小屏幕的设备中。
使用 Smallest-width 限定符在Android 3.2之前的版本上,开发者可能有点郁闷,应为之前的“large”限定符包含的尺寸太宽泛了,例如 Dell Streak、Galaxy 平板、以及7寸的平板。但是很多开发者都想在这个范围内根据不同的具体屏幕尺寸来显示不同的布局(例如 5寸和7寸的设备)。在Android 3.2版本中引入 “Smallest-width”限定符就是为了解决这个问题的。
Smallest-width限定符可以让你指定目标设备的最少屏幕尺寸(单位是dp)。例如,普通的7寸平板的最小宽度是600dp,因此如果你希望你的程序在这种尺寸的屏幕上使用两个窗口(小于该尺寸的屏幕使用一个窗口),那么您可以使用上面的两个布局文件,只要把xlarge 限定符替换为sw600dp 即可, 可以看出在3.2+版本中,对屏幕的限定更加详细了。
- res/layout/main.xml , 一个窗口(默认)布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/headlines" android:layout_height="fill_parent" android:name="com.example.android.newsreader.HeadlinesFragment" android:layout_width="match_parent" /> </LinearLayout>
- res/layout-sw600dp/main.xml , 两个窗口布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal"> <fragment android:id="@+id/headlines" android:layout_height="fill_parent" android:name="com.example.android.newsreader.HeadlinesFragment" android:layout_width="400dp" android:layout_marginRight="10dp"/> <fragment android:id="@+id/article" android:layout_height="fill_parent" android:name="com.example.android.newsreader.ArticleFragment" android:layout_width="fill_parent" /> </LinearLayout>
上面的代码意味着,只要最小屏幕宽度大于等于600dp的设备都会使用layout-sw600dp/main.xml 这个两个窗口布局,而小于该尺寸的设备就用layout/main.xml 一个窗口的布局。
但是,这样在3.2之前的版本没法使用,因为他们不认识sw600dp 这个限定符,这样您还是要同时使用xlarge 限定符。这样您的res/layout-xlarge/main.xml 文件和res/layout-sw600dp/main.xml 文件的内容是一样的。在下一个小节中,您会看到一种技术可以让你避免这种内容一样的布局文件出现。
使用布局别名上面看到的Smallest-width限定符只适用于3.2+的设备,因此您还需要同时使用(small, normal,large and xlarge)这些限定符来让您的程序运行在3.2之前的系统中。例如:如果你想设计一个界面,在手机中显示一个窗口,而在7寸平板或者更大的设备中显示两个窗口,您需要这些布局文件:
- res/layout/main.xml: 单个窗口布局
- res/layout-xlarge: 两个窗口布局
- res/layout-sw600dp: 两个窗口布局
后面的两个布局文件是一样的,一个是用于3.2+设备的;一个用于之前的设备的。
为了避免这种布局文件的重复,并且维护起来也很麻烦,您可以使用别名文件。例如您可以定义如下的布局文件:
- res/layout/main.xml , 单个窗口布局
- res/layout/main_twopanes.xml , 两个窗口布局
布局文件内容:
-
res/values-xlarge/layout.xml
<resources> <item name="main" type="layout">@layout/main_twopanes</item> </resources>
- res/values-sw600dp/layout.xml
-
<resources> <item name="main" type="layout">@layout/main_twopanes</item> </resources>
- 这两个文件内容是一样的,但是他们实际上并没有定义布局。他们仅仅设置了main 为main_twopanes 的一个别名。既然该布局文件有xlarge 和 sw600dp 这两个限定符,这样不管系统的版本是3.2之前的还是之后的,满足尺寸要求的都会使用该布局。
有些布局在横向和纵向屏幕中都表现很好,但是有些可以稍作修改从而提升用户体验。在新闻阅读示例程序中,下面展示了在不同的屏幕尺寸和屏幕方向中使用的布局:
- 小屏幕 纵向: 带Logo的单个窗口
- 小屏幕 横向: 带Logo的单个窗口
- 7寸平板, 纵向: 有动作条的单个窗口
- 7寸平板, 横向: 有动作条的两个宽窗口
- 10寸平板, 纵向: 有动作条的两个窄窗口
- 10寸平板, 横向: 有动作条的两个宽窗口
每个布局都在res/layout/ 目录中定义了一个布局文件。通过布局别名来影射的不同的配置项中的:
res/layout/onepane.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/headlines" android:layout_height="fill_parent" android:name="com.example.android.newsreader.HeadlinesFragment" android:layout_width="match_parent" /> </LinearLayout>
res/layout/onepane_with_bar.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:id="@+id/linearLayout1" android:gravity="center" android:layout_height="50dp"> <ImageView android:id="@+id/imageView1" android:layout_height="wrap_content" android:layout_width="wrap_content" android:src="/blog_article/@drawable/logo/index.html" android:paddingRight="30dp" android:layout_gravity="left" android:layout_weight="0" /> <View android:layout_height="wrap_content" android:id="@+id/view1" android:layout_width="wrap_content" android:layout_weight="1" /> <Button android:id="@+id/categorybutton" android:background="@drawable/button_bg" android:layout_height="match_parent" android:layout_weight="0" android:layout_width="120dp" /> </LinearLayout> <fragment android:id="@+id/headlines" android:layout_height="fill_parent" android:name="com.example.android.newsreader.HeadlinesFragment" android:layout_width="match_parent" /> </LinearLayout>
res/layout/twopanes.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal"> <fragment android:id="@+id/headlines" android:layout_height="fill_parent" android:name="com.example.android.newsreader.HeadlinesFragment" android:layout_width="400dp" android:layout_marginRight="10dp"/> <fragment android:id="@+id/article" android:layout_height="fill_parent" android:name="com.example.android.newsreader.ArticleFragment" android:layout_width="fill_parent" /> </LinearLayout>
res/layout/twopanes_narrow.xml :
现在所有的布局文件都定义好了,只要使用配置限定符做好影射即可,通过布局别名很容易实现:
res/values/layouts.xm
<resources> <item name="main_layout" type="layout">@layout/onepane_with_bar</item> <bool name="has_two_panes">false</bool> </resources>
res/values-sw600dp-land/layouts.xml
<resources> <item name="main_layout" type="layout">@layout/twopanes</item> <bool name="has_two_panes">true</bool> </resources>
res/values-sw600dp-port/layouts.xml
<resources> <item name="main_layout" type="layout">@layout/onepane</item> <bool name="has_two_panes">false</bool> </resources>
res/values-xlarge-land/layouts.xml
<resources> <item name="main_layout" type="layout">@layout/twopanes</item> <bool name="has_two_panes">true</bool> </resources>
res/values-xlarge-port/layouts.xml
<resources> <item name="main_layout" type="layout">@layout/twopanes_narrow</item> <bool name="has_two_panes">true</bool> </resources>
使用Nine-patch格式图片
支持不同尺寸的屏幕也意味着在不同的屏幕上使用不同大小的图片。例如,一个按钮的背景图片在任何尺寸的按钮上都应该是差不多的。
如果您在能改变大小的控件上使用同一个图片,你将会发现这些图片将会被缩放,看起来有点毛毛糙糙。而Android系统中的nine-patch格式图片就解决了这个问题。
要让一个普通的图片转换为nine-patch格式的,如下图(为了清楚的演示,该图被放大了4倍)
button
通过Android SDK中的 draw9patch 工具(在tools/ 目录中)编辑,详情请参考这篇详细介绍 ,如下图:
button.9.png
注意 边框上的黑色像素点。左边和上边的黑点定义了图片可以被拉伸的地方,右边和下边的黑点定义了控件内容可以显示的地方。
同时也要注意图片的扩展名为.9.png 。只有这种格式的图片系统才认为是nine-patch格式的。
如果您使用该图片作为控件的背景(代码android:background="@drawable/button" ),系统会自动的拉伸响应的地方,如下图所示:
button.9.png 在各种尺寸下被拉伸的情况
一. 在wp7中,程序所使用内存不能超过90M, 否则会自动退出
二. 如果一个page没有被释放,那么它所包含的所有控件资源都不会被垃圾收集.
在wp7中操作图像资源是很费内存的,如果内存超过90M,你的程序就挂了,而我们在XAML文件中定义的Image控件,由于属于静态资源,所以你不可能在后台CS代码中对它们进行image.source = null 操作,我试过,没有用,除非你把Image控件的定义放在CS代码中,这样你执行image.source = null 操作或者将Image控件从LayoutRoot中卸掉:LayoutRoot.Children.Remove(Image), 那么这个image就会被垃圾收集。
因此,对于在XAML文件中定义的图像控件来说,要想释放这些图像所占用的内存,唯一时刻只能是离开这个Page的时刻。所以,一条重要原则就是尽量不要将一个包含大量图像控件的page作为你的MainPage,因为这个Page会等到整个程序结束(exit)的时候才会被释放,你想想,在整个程序运行过程中,这些内存一直被占用着,一旦你再开一个包括许多图像的page,或者你load了其他东西到内存里,你的内存使用很可能会超过90M。
所以建议就是:
1.如果程序有主界面的话,尽量不要在主界面上放太多图像控件。或者说不要将一个拥有大量图像的page作为main page。
2.尽可能将需要播放动画并包含大量图像的page单独做成一个page,播放完毕或者用完就离开这个page
3.Panorama 的background 如果设置成图像image的话,会消耗大量内存,一般24M左右,而且跟是否是jpg或png, 跟图像大小和复杂度也没关系, 我做过试验,唯一能降低内存消耗的方法是讲panorama的高度(height)减小,比如减小一般,那么内存消耗也会减小一般,因为它只绘制了一半在界面上嘛。
你可能会想到将panorama的背景设置成渐变,我试过,这样也会消耗17M内存。如果设置成单色(solid color brush),大约消耗2-3M左右。 唯一不会消耗内存的就是:no background.
因此,请在选用Panorama之前,好好检查一下你的程序当前已经消耗了多少内存。余下的内存还够不够你load 一个panorama大胃王。
4.听说Pivot也是个让人又爱又恨的东西,在你加了很多tab到一个pivot中,而且图像又太多时,它也可能挂。 不过具体情况我还没试,以后再说。
另外,附送一段代码,让你知道在WP7中,如何让你知道每个Page何时被释放?
#if(DEBUG)
/// <summary>
/// Add a finalizer to check for memory leaks
/// </summary>
~YourPage()
{
System.Diagnostics.Debug.WriteLine("Finalizing " + this.GetType().FullName);
}
#endif
你可以将这段代码添加到你的每一个page中,记得修改这个析构函数的名称哦。 那么当这个page被析构的时候,就会在output控制台里看到。
看来很多同学都想知道为什么会有这个限制,那么我就补充一下吧:
这是因为Windows Phone 7的应用程序认证要求规定的,5.2.5规定,任何应用程序不得使用超过90 MB的内存,除非手机的可用内存超过256 MB。
下面是英文详细说明:
5.2.5 Memory Consumption
An application must not exceed 90 MB of RAM usage, except on devices that have more than 256 MB of memory. You can use the DeviceExtendedProperties class to query the amount of memory that is available on the device and modify the application behavior at runtime to take advantage of additional memory. For more information, see the DeviceExtendedProperties class in MSDN.
The DeviceTotalMemory value returned by DeviceExtendedProperties indicates the physical RAM size in bytes. This value is less than the actual amount of device memory. For an application to pass certification, Microsoft recommends that the value returned by ApplicationPeakMemoryUsage is less than 90 MB when the DeviceTotalMemory is less than or equal to 256 MB.
所以说这个脑残的规定实际上是防止你的程序无限制的使用内存从而拖慢整个系统的运行,包括多个程序之间的切换,微软这次就是想让windows phone 7的用户体验更加流畅,由于目前推出的所有型号的wp7手机的RAM都是256MB, 因此如果以后出的手机RAM增加了,可能就不会有这个限制了。
解决办法之一是:ArrayList<Bean>中的Bean要实现序列化接口