写点什么

Android 布局优化

2014 年 1 月 24 日

categories: Android

在 Android 开发中,我们常用的布局方式主要有 LinearLayout、RelativeLayout、FrameLayout 等,通过这些布局我们可以实现各种各样的界面。与此同时,如何正确、高效的使用这些布局方式来组织 UI 控件,是我们构建优秀 Android App 的主要前提之一。本篇内容就主要围绕 Android 布局优化来讨论在日常开发中我们使用常用布局需要注意的一些方面,同时介绍一款 SDK 自带的 UI 性能检测工具 HierarchyViewer。

布局原则

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

  • 尽量多使用 RelativeLayout,不要使用绝对布局 AbsoluteLayout;
  • 将可复用的组件抽取出来并通过 < include /> 标签使用;
  • 使用 < ViewStub /> 标签来加载一些不常用的布局;
  • 使用 < merge /> 标签减少布局的嵌套层次;

由于 Android 的碎片化程度很高,市面上存在的屏幕尺寸也是各式各样,使用 RelativeLayout 能使我们构建的布局适应性更强,构建出来的 UI 布局对多屏幕的适配效果越好,通过指定 UI 控件间的相对位置,使在不同屏幕上布局的表现能基本保持一致。当然,也不是所有情况下都得使用相对布局,根据具体情况来选择和其他布局方式的搭配来实现最优布局。

1、< include /> 的使用

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

复制代码
xml common_navitationbar.xml
<RelativeLayout mlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:padding="10dip" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:text="Back"
android:textColor="@android:color/black" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Title"
android:textColor="@android:color/black" />
</RelativeLayout>

然后我们在需要引入导航栏的布局 xml 中通过 < include /> 导入这个共用布局。

复制代码
xml main.xml
<RelativeLayout mlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<include
android:layout_alignParentTop="true"
layout="@layout/common_navitationbar" />
</RelativeLayout>

通过这种方式,我们既能提高 UI 的制作和复用效率,也能保证制作的 UI 布局更加规整和易维护。布局完成后我们运行一下,可以看到如下布局效果,这就是我们刚才完成的带导航栏的界面。

接着我们进入 sdk 目录下的 tools 文件夹下,找到 HierarchyViewer 并运行(此时保持你的模拟器或真机正在运行需要进行分析的 App),双击我们正在显示的这个 App 所代表的进程。

接下来便会进入 hierarchyviewer 的界面,我们可以在这里很清晰看到正在运行的 UI 的布局层次结构以及它们之间的关系。

分析刚刚我们构建的导航栏布局,放大布局分析图可以看到,被 include 进来的 common_navitationbar.xml 根节点是一个 RelativeLayout,而包含它的主界面 main.xml 根节点也是一个 RelativeLayout,它前面还有一个 FrameLayout 等几个节点,FrameLayout 就是 Activity 布局中默认的父布局节点,再往上是一个 LinearLayout,这个 LinearLayout 就是包含 Activity 布局和状态栏的整个屏幕显示的布局父节点,这个 LinearLayout 还有一个子节点就是 ViewStub,关于这个节点我们在后面会详细介绍。

2、< merge /> 的使用

< merge /> 标签的作用是合并 UI 布局,使用该标签能降低 UI 布局的嵌套层次。该标签的主要使用场景主要包括两个,第一是当 xml 文件的根布局是 FrameLayout 时,可以用 merge 作为根节点。理由是因为 Activity 的内容布局中,默认就用了一个 FrameLayout 作为 xml 布局根节点的父节点,这一点可以从上图中看到,main.xml 的根节点是一个 RelativeLayout,其父节点就是一个 FrameLayout,如果我们在 main.xml 里面使用 FrameLayout 作为根节点的话,这时就可以使用 merge 来合并成一个 FrameLayout,这样就降低了布局嵌套层次。

我们修改一下 main.xml 的内容,将根节点修改为 merge 标签。

复制代码
xml main.xml
<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" >
<include layout="@layout/common_navitationbar" />
</merge>

重新运行并打开 HierarchyViewer 查看此时的布局层次结构,发现之前多出来的一个 RelativeLayout 就没有了,直接将 common_navigationbar.xml 里面的内容合并到了 main.xml 里面。

使用 < merge /> 的第二种情况是当用 include 标签导入一个共用布局时,如果父布局和子布局根节点为同一类型,可以使用 merge 将子节点布局的内容合并包含到父布局中,这样就可以减少一级嵌套层次。首先我们看看不使用 merge 的情况。我们新建一个布局文件 common_navi_right.xml 用来构建一个在导航栏右边的按钮布局。

复制代码
xml common_navi_right.xml
<RelativeLayout mlns: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" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="Ok"
android:textColor="@android:color/black" />
</RelativeLayout>

然后修改 common_navitationbar.xml 的内容,添加一个 include,将右侧按钮的布局导入:

复制代码
xml common_navitationbar.xml
<RelativeLayout mlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:padding="10dip" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:text="Back"
android:textColor="@android:color/black" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Title"
android:textColor="@android:color/black" />
<include layout="@layout/common_navi_right" />
</RelativeLayout>

运行后的效果如下图,在导航栏右侧添加了一个按钮“ok”

然后再运行 HierarchyViewer 看看现在的布局结构,发现 common_navi_right.xml 作为一个布局子节点嵌套在了 common_navitationbar.xml 下面。

这时我们再将 common_navi_right.xml 的根节点类型改为 merge。

复制代码
xml common_navi_right.xml
<merge 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" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="Ok"
android:textColor="@android:color/black" />
</merge>

重新运行并打开 HierarchyViewer 查看布局结构,发现之前嵌套的一个 RelativeLayout 就没有了,这就是使用 merge 的效果,能降低布局的嵌套层次。

3、< ViewStub /> 的使用

也许有不少同学对 ViewStub 还比较陌生,首先来看看 ViewStub 在官方文档里是怎么介绍的:

A ViewStub is an invisible, zero-sized View that can be used to lazily inflate layout resources at runtime. When a ViewStub is made visible, or when inflate() is invoked, the layout resource is inflated. The ViewStub then replaces itself in its parent with the inflated View or Views. Therefore, the ViewStub exists in the view hierarchy until setVisibility(int) or inflate() is invoked. The inflated View is added to the ViewStub’s parent with the ViewStub’s layout parameters.

大致意思是:ViewStub 是一个不可见的,能在运行期间延迟加载的大小为 0 的 View,它直接继承于 View。当对一个 ViewStub 调用 inflate() 方法或设置它可见时,系统会加载在 ViewStub 标签中引入的我们自己定义的 View,然后填充在父布局当中。也就是说,在对 ViewStub 调用 inflate() 方法或设置 visible 之前,它是不占用布局空间和系统资源的。它的使用场景可以是在我们需要加载并显示一些不常用的 View 时,例如一些网络异常的提示信息等。

我们新建一个 xml 文件用来显示一个提示信息:

复制代码
xml common_msg.xml
<RelativeLayout mlns: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 的标签引入上面的布局:

复制代码
xml main.xml
<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" >
<include layout="@layout/common_navitationbar" />
<ViewStub
android:id="@+id/msg_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout="@layout/common_msg" />
</merge>

修改 MainActivity.java 的代码,我们这里设置为点击右上角按钮的时候显示自定义的 common_msg.xml 的内容。

复制代码
java MainActivity.java
public class MainActivity extends Activity {
private View msgView;
private boolean flag = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.findViewById(R.id.rightButton).
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
System.out.print("111");
if(flag){
showMsgView();
}else{
closeMsgView();
}
flag = !flag;
}
});
}
private void showMsgView(){
if(msgView != null){
msgView.setVisibility(View.VISIBLE);
return;
}
ViewStub stub = (ViewStub)findViewById(R.id.msg_layout);
msgView = stub.inflate();
}
private void closeMsgView(){
if(msgView != null){
msgView.setVisibility(View.GONE);
}
}
}

代码中我们通过 flag 来切换显示和隐藏 common_msg.xml 的内容,然后我们运行一下并点击右上角按钮来切换,效果如下:

总结

好了,到目前为止,我们就介绍了 Android 中关于布局优化的一些内容以及工具 HierarchyViewer 的使用。将前文提及的布局原则再列一下,欢迎大家补充更多的关于 Android 布局优化的实用原则。

  • 尽量多使用 RelativeLayout,不要使用绝对布局 AbsoluteLayout;
  • 将可复用的组件抽取出来并通过 < include /> 标签使用;
  • 使用 < ViewStub /> 标签来加载一些不常用的布局;
  • 使用 < merge /> 标签减少布局的嵌套层次;

作者新浪微博:唐韧_Ryan


感谢李永伦对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2014 年 1 月 24 日 03:0114031

评论

发布
暂无评论
发现更多内容

MobTech袤博与百度战略签约 携手布局数据智能产业新蓝图

Geek_116789

吴恩达推荐笔记:22张图总结深度学习全部知识

程序员生活志

学习 吴恩达

Spring5-Reactor函数式编程

小技术君

spring reactor Spring5 springboot

IDC2020 Q1通用服务器数据发布,浪潮信息成绩喜人

Geek_116789

在前端如何玩转 Word 文档

阿宝哥

html markdown word

女员工被阿里录取工资二万六,辞职时被领导挽留:给你4万留下

程序员生活志

阿里 女程序员

讲烂了的mysql,今天再给大家重温一下

爱嘤嘤嘤斯坦

Java MySQL 数据库 编程 mysql事务

​中国SaaS处在什么阶段?

ToB行业头条

话题讨论|在编程中,有哪些好习惯是应该一直坚持下去的?

InfoQ写作平台官方

写作平台 话题讨论 话题

啃碎并发(10):内存模型之内部原理

猿灯塔

为什么单元测试不是持续交付的唯一答案

陈琦

持续集成 单元测试

计算机网络基础(一)---计算机网络概览篇

书旅

php laravel 计算机网络

为什么我们需要制品管理?

Man

DevOps nexus 制品库管理 Artifactory

抢滩新基建,百度还会输给阿里和腾讯吗?

ToB行业头条

三大 OSS 缓存加速系统巅峰对决

苏锐

hadoop cache JuiceFS JindoFS Performance

如何把百万级别的订单根据金额排序

码哥字节

数据结构 排序算法

腾讯的ToB梦想

ToB行业头条

第6周-作业2-总结

seng man

laravel redis队列不执行

kaer

laravel redis Queue

自动化测试首先是一种工作文化

wangwei1237

自动化测试 测试文化

《重学 Java 设计模式》PDF 出炉了 - 小傅哥,肝了50天写出18万字271页的实战编程资料

小傅哥

Java 设计模式 小傅哥 重构 代码质量

第6周-作业1

seng man

阿里拍卖,能不能拍到点儿上?

ToB行业头条

设计模式六大原则

刘志刚

设计原则

我在项目中是这样配置Vue的

前端有的玩

Java Vue 前端 框架设计

人人都需要一份自己的「使用说明书」

非著名程序员

程序员 程序人生 提升认知 独立思考 自我思考

CAP原理简述

刘志刚

推荐系统大规模特征工程与FEDB的Spark基于LLVM优化

范式AI云

spark Sparksql 推荐系统 LLVM FEDB

2020,是中国SaaS行业的机遇之年?

ToB行业头条

Worktile完成新一轮融资,将发力研发管理赛道

Worktile

融资

微信小程序使用GoEasy实现websocket实时通讯

GoEasy消息推送

小程序 websocket 即时通讯

InfoQ 极客传媒开发者生态共创计划线上发布会

InfoQ 极客传媒开发者生态共创计划线上发布会

Android布局优化-InfoQ