Android 屏幕适配

众所周知,Android机型尺寸各种各样,于是屏幕适配就成了Android开发中很重要的一环。Android屏幕适配可能一些开发者都会遇到这样的问题,今天就来分享下屏幕适配,你会发现其实Android屏幕适配也可以很简单。

基本概念

Android屏幕适配必须要理解的一些概念,这部分可能比较枯燥,但是俗话说的好“工欲善其事,必先利器”,翻译过来就是“有什么样的枪,决定你打什么样的鸟”,一旦这些概念你理解掌握了,屏幕适配你自然而然就觉得简单多了。

  • px

是英文单词pixel的缩写,意为像素,屏幕上的点。我们通常所说的分辨率如480X800就是指的像素。

在设计领域中,像素是用来计算数码影像的最小单位。计算机中显示的图像并非连续的线条组成,而是由许多肉眼看不见的小点组成。如果把影像放大数倍,会发现这些连续色调其实是由许多色彩相近的小点所组成,这些小点就是构成影像的最小单位“像素”。由于是最小的独立显示单位,px均为整数,不会出现0.5px的情况。如:

看这个色彩鲜艳的LED灯(原图大小)

你能想象这才是他的本来面目吗?(放大之后)

  • in

表示英寸,是屏幕的物理尺寸。每英寸等于2.54厘米。例如我们经常说的手机屏幕大小有,5(英)寸、4(英)寸就是指这个单位。这些尺寸是屏幕的对角线长度。如果手机的屏幕是4英寸,表示手机的屏幕(可视区域)对角线长度是4 X 2.54 = 10.16厘米。

  • dpi

dpi是Dots Per Inch的缩写, 每英寸点数,即每英寸包含像素个数。比如320X480分辨率的手机,宽2英寸,高3英寸, 每英寸包含的像素点的数量为320/2=160dpi(横向)或480/3=160dpi(纵向),160就是这部手机的dpi,横向和纵向的这个值都是相同的,原因是大部分手机屏幕使用正方形的像素点。

  • density

屏幕密度,density和dpi的关系为 density = dpi/160

  • dp

也即dip,设备独立像素,device independent pixels的缩写,Android特有的单位,在屏幕密度dpi = 160屏幕上,1dp = 1px。

  • sp

和dp很类似,一般用来设置字体大小,和dp的区别是它可以根据用户的字体大小偏好来缩放。

Android Drawable

我们新建一个Android项目后应该可以看到很多drawable文件夹,分别对应不同的dpi

  • drawable-ldpi (dpi=120, density=0.75)

  • drawable-mdpi (dpi=160, density=1)

  • drawable-hdpi (dpi=240, density=1.5)

  • drawable-xhdpi (dpi=320, density=2)

  • drawable-xxhdpi (dpi=480, density=3)

市面上的一些Android教程大多都是教的是为每种dpi都出一套图片资源,这个固然是一种解决办法,但同时也是一种非常笨的方法,为美工或者设计增加了不少的工作量不说,同时也会让你的apk包变的很大。那么有没有什么好的方法既能保证屏幕适配,又可以最小占用设计资源,同时最好又只使用一套dpi的图片资源呢?下面就来讲解下项目中总结出来的这个方法。

首先必须清楚一个自动渲染的概念,Android SDK会自动屏幕尺寸选择对应的资源文件进行渲染,如SDK检测到你手机dpi是160的话会优先到drawable-mdpi文件夹下找对应的图片资源,注意只是优先,假设你手机dpi是160,但是你只在xhpdi文件夹下有对应的图片资源文件,程序一样可以正常运行。所以理论上来说只需要提供一种规格的图片资源就ok了,如果只提供ldpi规格的图片,对于大分辨率的手机如果把图片放大就会不清晰,所以需要提供一套你需要支持的最大dpi的图片,这样即使用户的手机分辨率很小,这样图片缩小依然很清晰。

xhdpi成为首选

上面说了只需要提供一套大的dpi的图片就ok了,现在市面手机分辨率最大可达到1080X1920的分辨率,如Nexus5,dpi属于xxhdpi,但是毕竟还没普及,目前市面上最普遍的高端机的分辨率还多集中在720X1080范围,也就是多集中在xhdpi,所以目前来看xhpdi规格的图片成为了首选。当然随着技术规格的提高以后发展,以后可能市场上xxdpi的手机会越来越普遍,但这是后话。

设计资源紧张怎么办?

在现在的App开发中,基本都会有iOS和Android版本,有些公司为了保持App不同版本的体验交互一致,还有些公司的设计资源可能比较紧张,这些情况下iOS和Android版本基本是一个设计师主导,而大多数情况下设计师可能更会以iPhone手机为基础进行设计,包括后期的切图之类的。这个时候身为Android开发人员你是否还要求设计师单独为Android端切一套图片资源呢?这会让你们的设计师崩溃的,下面就来告诉一个项目中总结的更棒的方法。

相信设计师们一般都会用最新的iPhone5(5s和5的尺寸以及分辨率都一样)来做原型设计,而iPhone5的屏幕分辨率为640X1164, 屏幕尺寸为4英寸,根据勾股定理(a^2 + b^2 = c^2)640^2+1164^2=1764496, 然后再对其开根号可求出屏幕对角线的分辨率为:1328,除以4可得出iphone5的dpi:1328/4≈332 可以看出iPhone5的屏幕的dpi约等于320, 刚好属于xhdpi,所以你可以很自豪的像你们的设计师说不用专门为Android端切图,直接把iPhone的那一套切好的图片资源放入drawable-xhdpi文件夹里就ok了。

wrap_content VS dp

wrap_content和dp都是在Android开发中应该经常用到的,然后它们冥冥中是有关系的。

假设你看了这篇文章后都是统一有xhdpi的资源,那么你用wrap_content完全没有问题,Android会自动为其他规格的dpi屏幕适配,比如你在xhdpi放了一张120X120px大小的图片,那么在在hdpi屏幕上显示的就只有120/1.5=80px大小,但是如果你不小心同样把这张图片也放入了mdpi了,这个时候用wrap_content显示就会有问题,具体看下面的例子:

例如假设你只在drawable_xhdpi文件夹下放了test图片,xhdpi的设备会去xhdpi文件夹下找到test图片并直接显示,而mdpi的设备优先会去mdpi文件夹里查找test图片,但是没找到,最后在xhdpi文件夹下找到,然后会自动根据density计算并缩放显示出来,实际显示出来的大小是120/2=60px, 所以整体的显示比例才会看起来比较正常

  • mdpi

  • xhdpi

但是如果你在mdpi文件夹里也放入了同样的图片,那么mdpi的设备会直接去mdpi文件夹里寻找到test图片,并直接显示,而这时候显示不会缩放,实际显示大小就是120X120,在mdpi的屏幕上看起来就会比较大,如图:

通过上面整个过程,大家应该理解了Android加载资源的整个过程, wrap_content同样可以用dp来代替,就拿上面这个例子,在xhdpi文件夹内放入了一张120X120像素的test图片,宽高直接除density就得出dp的数值,即这种情况下以下代码是等同的.

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/test" />
<ImageView
    android:layout_width="60dp"
    android:layout_height="60dp"
    android:src="@drawable/test" />

总结

相信通过以上的讲解,对Android UI中的一些基本概念有个很好的理解,实际开发工作中也有一些高效的方法可以参考,应该可以应对大部分的屏幕适配工作。但是项目中仍然有一些比较特殊的适配需求满足不了,以后会针对一些特殊的需求进行示例讲解。


认识一下Sketch

Jean-Marc Denis(原文作者,前Sparrow设计师,现在在Google工作)在参加WWDC 2013时认识了Sketch的开发团队,所以在第一时间尝试了Sketch,当时由于一些功能的缺失,所以还是折回去使用Photoshop了。过了一段时间,他发现设计师社区开始疯狂着迷于这款新的设计工具,所以决定再试一试,想看看迭代了一段时间的Sketch现在能完成多少原先他需要用Photoshop来做的工作。现在,他80%的设计工作都是由Sketch来完成的,所以写了这篇文章来帮助大家一起了解Sketch。

Photoshop并不是一款合适的界面设计工具

为什么我们会期待一个新的设计工具?因为当我们更多的关注效率和关注协作,就越发现Photoshop已经不足以满足我们的期待了。下面看看具体的理由:

  • 并非为设计师打造

Photoshop是为图像处理开发的,诸如路径和矢量工具都是后期才加上去的,所以如果你关心一下Photoshop的更新路径,用户界面设计师完全不是Adobe做这条产品时关心的用户群。所以,如果抛去那些复杂的功能,一个为用户界面设计师打造的设计工具完全可以做得更简单,更高效。

  • 不适合移动时代

相信我们都会觉得为不同的设备输出不同分辨率的素材而痛苦不已吧。或许,你会使用一些第三方插件或是启用一些模板来处理这些事,但依然是很浪费时间的。

  • 引擎太过陈旧

Photoshop的引擎是上个世纪为图像处理而打造的,非常的耗费资源,即使是启动一下都要等很久,更别说一次移动多个分组了。话说回来,由于要兼顾Mac和Windows平台,能做到这样已经不容易了。

  • 越来越跟不上时代

大公司的执行效率实在太低。不知道大伙是不是和我一样,非常痛苦地等了好几个月才等到Photoshop对Retina屏幕的支持。诸如自动对齐的等功能我已经不做期待了。

为什么Sketch确实值得尝试

  • 自动保存和多版本控制

有没有梦想过拜托机械的手动保存?Sketch可以做到。Sketch在工作的时候会不断地自动保存成果,并且允许你回复到此前的任一版本。

  • 矢量作图并且像素级的完美

矢量图意味着可以任意扩展。你没有必要不断地去调整素材的尺寸,Sketch会自动就帮你维持一个像素级完美的作品。如果有的时候你必须用像素点来作图,比如说是画图标或者是插画设计,Sketch 也提供了从矢量切换到像素视图的功能。

  • 智能标尺

你喜欢xScope吗?还是正在用选框工具来进行测量?又或者是难用的网格?Sketch的智能标尺可以帮你轻松地把设计元素的对齐方式、内外间距都调整完美。就我个人而言,这个功能非常节省时间而且避免了复杂的人工计算,是我觉得最有用的功能之一。

  • 随时随地编辑元素

在Sketch里我们可以随时随地修改一个元素的圆角、高度和宽度。只需要简单地修改数值就可以轻松调整元素,对于刚从Photoshop切换过来的设计师而言非常好用。甚至会让你产生强烈的依赖。

  • 图形拼接

在Sketch 里你可以很轻松地把多个图形合并,构成一个新的图形。并且Sketch在提供了多种图形合并模式的同时,还允许你随时修改已经合并图形的子图形。这项功能使得你可以很轻松地管理和创造更复杂的图形。

  • 每个图层都可以有多种混合效果

在Photoshop里想要让一个图层有多种混合效果是很困难的。但在Sketch里,你可以很容易给一个图层加上多种混合效果,看看下面的示例就知道了。

  • 自动选择最接近的像素边界

自动选择最接近的像素边界可以让一个图形或者是图层自动修正边距到一个像素级完善的位置。比如可以把一个宽度是5.3px的图形自动修正到5px,这样就不会有模糊的像素点和粗糙的图形了。我自己给这项功能设了一个快捷键(Command+Opitons+x),来确保我做出的每一个图形都像素级完美。这比人工一个一个按照网格修改图形高效得多。

  • 样式链接

有的时候你会做一些充斥文字的设计,样式链接可以帮助你设定一个固定的样式来快速地应用在新的文字上。如果你修改了其中任何一处的样式,那么所有链接该样式的文字都会自动更新到新的样式。并且,这项功能也可以应用在图形上。

  • 导出素材

用Photoshop的时候,最痛苦的事莫过于切割元素,然后到处素材。Sketch则可以帮助你快速地导出各种分辨率和格式(pdf, eps, svg, png, jpg, tiff)的素材,通常导出只需要一个点击,并且不用借助任何第三方应用程序。

  • 分布式布局

作富文本设计的时候,布局会变得非常重要,这项功能可以帮助你快速地试验不同的方案。

  • 网格

其实可以把网格功能理解成按照纵横两个维度自动地结构化布局内容。这个对于排版布局而言,非常方便。

  • 出色的文字渲染

Sketch的文字渲染引擎非常出色,可以用来做很出色的文字布局和字体设计。因此,你可以放心地放弃Photoshop的抗锯齿功能。

  • CSS样式导出

如果你在做一些网页设计,Sketch可以帮助你快速地把你的成果到处成CSS样式。

  • 旋转副本

这是一项小功能,但是确实可以节约你很多时间和精力。

  • Sketch Mirror

Sketch Mirror是一个Universal App,可以方便你在iPhone或是iPad上查看自己的成果。(作者撰写这个文章的时候Sketch还没有推出官方的镜像工具,所以这一段是译者补充的)。

快速响应是杀手锏

Sketch是一个小团队,发展非常快速。简单扫一眼Sketch的升级日志,你会发现每一个更新都会带来新的功能。 我非常建议你去使用Beta版本,你会见证Sketch快速的升级。 Sketch团队非常善于聆听用户的诉求,因而软件不断地变得更好。你可以去tenderapp上向他们提出新功能的要求。

相关链接

原文来自http://jianshu.io/p/7e8905b18620


Android AdapterView setEmptyView

当我们使用ListView或GridView的时候,当列表为空的时候,我们往往需要一个Loading或者一段提示文字又或者一个特殊的View来提示用户操作,这个时候就用到了setEmptyView()方法。

setEmptyView()其实是AdapterView的方法,而我们开发中常用到的ListView, GridView, ExpandableListView等都是继承于AdapterView的,所以可以直接调用这个方法,下面来看看具体的如何使用:

以默认显示一个提示文字的TextView为例,一般有以下两种用法:

1. Empty View和ListView在同一个布局文件里

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView 
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/list_view" />

    <TextView 
        android:id="@+id/tv_empty"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Loading data..." />

</FrameLayout>
ListView listView = (ListView)findViewById(R.id.list_view);
listView.setEmptyView(findViewById(R.id.iv_empty));

2. Empty View在单独的布局文件里,这种一般适用于比较复杂的View或者打算在多个地方复用

setEmptyView()这个方法是有限制的,这个View必须在当前的View hierarchy的节点上,所以当我们写在外面单独的布局文件里时,需要把View添加到当前的View hierarchy的节点上。所以就需要下面的用法:

View emptyView = View.inflate(R.layout.empty_view, null);
((ViewGroup)list.getParent()).addView(emptyView);
ListView listView = (ListView)findViewById(R.id.list_view);
listView.setEmptyView(emptyView);

Android WebView上传文件

在这次食物上传功能的开发中,其中WebView中上传图片需要调用系统的文件系统,默认WebView是不支持文件上传的,需要自己手动配置一些东西,具体代码如下:

public class BrowserActivity extends Activity {
    private WebView mWebView;
    private ValueCallback<Uri> mUploadMessage;
    private final static int FILECHOOSER_RESULTCODE = 1;

    public void onCreate(Bundle outState) {
        super.onCreate(outState);
        setContentView(R.layout.activity_browser);
        mWebView = (WebView) findViewById(R.id.webview);
        mWebView.getSettings().setJavaScriptEnabled(true);
        mWebView.setWebChromeClient(new MyWebClient());
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        if (requestCode == FILECHOOSER_RESULTCODE) {
            if (null == mUploadMessage)
            return;
        Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData();
        mUploadMessage.onReceiveValue(result);
        mUploadMessage = null;
        }
    }

    public class MyWebClient extends WebChromeClient {
        // For Android 3.0-
        public void openFileChooser(ValueCallback<Uri> uploadMsg) {
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
            startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
        }

        // For Android 3.0+
        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("*/*");
            startActivityForResult(Intent.createChooser(i, "File Browser"), FILECHOOSER_RESULTCODE);
        }

        // For Android 4.1
        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
            mUploadMessage = uploadMsg;
            Intent i = new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");
            startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
        }
    }
}

主要就是自定义了WebChromeClient,然而测试时发现在4.4以上的Android版本依然不可以,搜了下openFileChooser方法在4.4以后不是public的,所以以后不建议这种直接在WebView上传文件的做法。

stackoverflow参考链接:HTML file input in android webview (android 4.4, kitkat)


Android 布局优化

在开发过程中我们经常说性能优化,但性能优化是一个比较宽泛的概念。在Android开发中性能优化可能包括:Java代码优化, 算法优化, SQLite优化, 布局优化等。那么这篇博客就来总结并分享下Android开发中的布局优化。

布局原则

在Android UI布局过程中,通过遵守一些惯用、有效的布局原则,我们可以制作出高效且复用性高的UI,概括来说包括如下几点:

  • 尽量多使用RelativeLayout和LinearLayout, 不要使用绝对布局AbsoluteLayout,在布局层次一样的情况下, 建议使用LinearLayout代替RelativeLayout, 因为LinearLayout性能要稍高一点,但往往RelativeLayout可以简单实现LinearLayout嵌套才能实现的布局。

  • 将可复用的组件抽取出来并通过include标签使用;

  • 使用ViewStub标签来加载一些不常用的布局;

  • 使用merge标签减少布局的嵌套层次;

RelativeLayout VS LinearLayout

第一条原则说了布局层次一样的情况下LinearLayout比RelativeLayout要好, 但往往RelativeLayout可以简单实现LinearLayout嵌套才能实现的布局。假如需要实现如下布局:

用LinearLayout来实现xml代码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    android:padding="6dip">
    
    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_marginRight="6dip"
        android:src="@drawable/icon" />

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="0dip"
        android:layout_weight="1"
        android:layout_height="fill_parent">

        <TextView
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
            android:gravity="center_vertical"
            android:text="My Application" />
            
        <TextView  
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1" 
            android:singleLine="true"
            android:ellipsize="marquee"
            android:text="Simple application that shows how to use RelativeLayout" />
            
    </LinearLayout>

</LinearLayout>

而用RelativeLayout实现代码如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    android:padding="6dip">
    
    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_alignParentTop="true"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="6dip"
        android:src="@drawable/icon" />

    <TextView  
        android:id="@+id/secondLine"
        android:layout_width="fill_parent"
        android:layout_height="26dip" 
        android:layout_toRightOf="@id/icon"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:singleLine="true"
        android:ellipsize="marquee"
        android:text="Simple application that shows how to use RelativeLayout" />

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/icon"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:layout_above="@id/secondLine"
        android:layout_alignWithParentIfMissing="true"
        android:gravity="center_vertical"
        android:text="My Application" />

</RelativeLayout>

可以看到用RelativeLayout实现,布局层次明显少了,所以大多数时候优先推荐使用RelativeLayout。

查看布局层次

如何查看布局层次呢?有两种办法:一是通过手机的开发者选项,4.0及以上Android版本可通过设置->开发者选项->显示布局边界打开页面布局显示,看看是否有不必要的节点和嵌套。第二种就是利用SDK自带的UI性能检测工具HierarchyViewer。 进入sdk目录下的tools文件夹下,找到HierarchyViewer并运行(此时保持你的模拟器或真机正在运行需要进行分析的App),双击我们正在显示的这个App所代表的进程。接下来便会进入hierarchyviewer的界面,我们可以在这里很清晰看到正在运行的UI的布局层次结构以及它们之间的关系。大概的显示如下图:

通过布局图我们可以看到根节点DecorView下包含一个LinearLayout, 这个LinearLayout就是包含Activity布局和状态栏的整个屏幕显示的布局父节点,这个LinearLayout有两个子节点, 一个是FrameLayout, FrameLayout就是Activity布局中默认的父布局节点, 这个节点下面就包含了我们自己写的xml布局, 还有一个子节点就是ViewStub,关于这个节点我们在后面会详细介绍。

< include />的使用

在实际开发中,我们经常会遇到一些共用的UI组件,比如带返回按钮的导航栏,如果为每一个xml文件都设置这部分布局,一是重复的工作量大,二是如果有变更,那么每一个xml文件都得修改。还好,Android为我们提供了include标签,顾名思义,通过它,我们可以将这些共用的组件抽取出来单独放到一个xml文件中,然后使用include标签导入共用布局,这样,前面提到的两个问题都解决了。下面以在一个布局main.xml中用include引入另一个布局header.xml为例。

header.xml文件

<?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" >

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_40"
        android:layout_above="@+id/text"/>

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_40"
        android:layout_alignParentBottom="true"
        android:text="@string/app_name" />

</RelativeLayout>

然后我们在需要引入footer的布局xml中通过include导入这个共用布局。

main.xml文件

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="hello world" />

    <RelativeLayout 
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center" >

        <include layout="@layout/header" />

    </RelativeLayout>
</FrameLayout>

通过这种方式,我们既能提高UI的制作和复用效率,也能保证制作的UI布局更加规整和易维护。

< merge />的使用

merge标签的作用是合并UI布局,使用该标签能降低UI布局的嵌套层次。merge标签可用于两种典型情况:

  • 布局根结点是FrameLayout且不需要设置background或padding等属性,可以用merge代替,因为Activity内容布局的parent view就是个FrameLayout,所以可以用merge消除只剩一个,这一点可以从上图中看到。

  • 某布局作为子布局被其他布局include时,使用merge当作该布局的顶节点,这样在被引入时顶结点会自动被忽略,而将其子节点全部合并到主布局中。

以第一种情况为例,main.xml布局就可以优化如下:

<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout 
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="hello world" />

        <RelativeLayout 
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center" >

            <include layout="@layout/header" />

        </RelativeLayout>
    </FrameLayout>
</merge>

以第二种情况为例,header.xml布局可以优化如下:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
 
    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_40"
        android:layout_above="@+id/text"/>
 
    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_40"
        android:layout_alignParentBottom="true"
        android:text="@string/app_name" />
 
</merge>

这样就不会有多余的FrameLayout和RelativeLayout节点了。

ViewStub标签

viewstub标签同include标签一样可以用来引入一个外部布局,不同的是,viewstub引入的布局默认不会扩张,即既不会占用显示也不会占用位置,从而在解析layout时节省cpu和内存。 viewstub常用来引入那些默认不会显示,只在特殊情况下显示的布局,如进度布局、网络失败显示的刷新布局、信息出错出现的提示布局等。

我们新建一个xml文件用来显示一个网络错误时提示信息error.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" >

   <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:background="@android:color/white"
        android:padding="10dip"
        android:text="Message"
        android:textColor="@android:color/black" />

</RelativeLayout>

然后在main.xml里面加入ViewStub的标签引入上面的布局:

<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:background="@android:color/darker_gray"
    android:layout_height="match_parent" >

    ...

    <ViewStub
        android:id="@+id/error_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout="@layout/error" />

</merge>

在java中通过(ViewStub)findViewById(id)找到ViewStub,通过stub.inflate()展开ViewStub,然后得到子View,如下:

private View errorView;
 
private void showError() {
    // not repeated infalte
    if (errorView != null) {
        errorView.setVisibility(View.VISIBLE);
        return;
    }
 
    ViewStub stub = (ViewStub)findViewById(R.id.error_layout);
    errorView = stub.inflate();
}
 
private void showContent() {
    if (errorView != null) {
        errorView.setVisibility(View.GONE);
    }
}

在上面showError()中展开了ViewStub,同时我们对errorView进行了保存,这样下次不用继续inflate。

总结

这篇Blog没有详细介绍HierarchyViewer工具的使用,相信如果对布局原则比较熟练之后,对工具的依赖大大减少,开发效率也会大大的提升。除这些布局原则之外,还需要大家对Android各个组件的属性很熟悉,比如如果要做这么一个布局, 一个图片和一个文本的布局,新手们往往会用一个Layout嵌套ImageView和TextView来做, 但是当我们知道TextView有drawableLeft, drawableRight等属性时,那么实现这样的一个布局是非常快速高效的。总之,且学且实践!