[Android] 布局优化技巧
看了一些关于优化布局的资料,了解了很多平时不怎么注意的问题,于是把资料整理了一下,一部分内容是翻译来的,一部分是自己理解加上的。每部分内容都有demo,有些资料里的demo比较好的,我就直接拿来用了;有些没有demo或者demo写的比较难理解,我就自己去写,去验证。总之,文章里的代码都是可用的。最后,因为水平有限,难免会出错,所以发现错误请及时给我指正,谢谢!
转载请保留地址:http://www.cnblogs.com/rossoneri/p/4838072.html
1. 善用相对布局RelativeLayout
Android提供了几种方便的布局管理器,大多数时候,你只需要这些布局的一部分基本特性去实现UI。然而坚持用基本特性创建UI的代码效率并不高。常见的例子就是滥用线性布局LinearLayout
结果导致了增加View的层级。由于每往应用里增加一个View,或者更严重的说增加一个布局管理器,都会增加运行时对系统的消耗,因此这样就会导致界面初始化、布局、绘制的过程变慢。而当你的布局文件里有一些使用了weight
属性的线性布局时,这个属性会让界面在布局阶段计算两次,更增加了对资源的消耗。
设想这么一个小例子:有一个列表项,左侧是一个图标,右边上半部分是标题,下半部分是详细描述。像这样:
为了把布局关系看得更清楚,下面是用HierarchyViewr截取的线框图:
这个布局用线性布局实现非常简单。这个列表项本身是由一个ImageView
和一个纵向LinearLayout
组成的水平线性布局,然后纵向布局里面包含两个TextView
,代码如下:
<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>
这个布局可以实现效果,但如果要在一个列表ListView
中初始化每一个列表项就会比较浪费资源了。这个布局也可以使用相对布局RelativeLayout
去写,这样每个列表项就可以节省一个View,甚至节省一个View的层级。实现代码如下:
<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>
这个实现方法和之前显示的一致,但有一点不同,界面上有两行文字,标题和描述。如果描述不可用的话,代码里会直接把这个View的可见属性设为GONE
。由于在线性布局里设置TextView
的weight
为1,即其中一个TextView
隐藏后,另一个TextView
会自动把界面占满。而我们的相对布局会是这样:
这是因为在相对布局里,每个View的位置都是与其他View或者该布局本身对齐的。比如,我们设定描述相关的TextView2
与相对布局底部和右边对齐,而标题相关的TextView1
与相对布局的顶部和右部对齐,同时显示在TextView2
的上方。即TextView1
的底部要与TextView2
的顶部对齐。现在,当TextView2
隐藏时,布局文件就不知道TextView1
的底部要往哪对齐了。
要解决这个问题,就需要一个特殊的布局属性:alignWithParentIfMissing
。这个布尔型参数就是告诉RelativeLayout
,如果一个约束条件不可用的时候可以把自己的Layout作为布局的参考。在本例中就可以设置这个属性为true
,这样TextView1
就可以让自己的底部与整个Layout的底部对齐。
解决完这个bug,这个布局就完美了,不仅是布局层级得到优化,而且避免了使用weight
属性,这样,面对ListView
里的很多项,代码效率会有很大提升。
最后,打开Android Device Monitor
,选择Hierarchy View
,就可以清楚的看到布局界面层级的关系,对比下可以看到,改进后的代码不仅让View的个数减少了一个,还减少了View的层数。
2. 复用布局之include标签
在Android XML 布局文件里,每一个标签都映射到一个类的实例(一般都是View的子类)。UI工具包提供了三个特殊的没有映射到View实例的标签:<requestFocus />
,<merge />
和<include />
。
关于<requestFocus />
标签,简单提一下,就是默认获取焦点。比如我有一个TextView
,你可以在代码里调用requestFocus()
,也可以直接在布局文件里写:
<EditText
android:id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0"
android:paddingBottom="4">
<requestFocus />
</EditText>
该标签需要用在View标签内部。
关于<merge />
后面再讲。
最后<include />
标签,功能如其名,它可以包含其他的XML布局到文件中,像这样:
<com.android.launcher.Workspace
android:id="@+id/workspace"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
launcher:defaultScreen="1">
<include android:id="@+id/cell1" layout="@layout/workspace_screen" />
<include android:id="@+id/cell2" layout="@layout/workspace_screen" />
<include android:id="@+id/cell3" layout="@layout/workspace_screen" />
</com.android.launcher.Workspace>
在<include />
标签中只有layout
属性是必须写的。这个属性是一个对你所需要加入的布局的引用。例子中,相同的一个布局就被包含了三次。
这个标签也可以对包含进来的布局的一些属性进行重载。上例中重载了id
属性,同样,你可以重载其他布局属性,也就是任何android:layout_*
的属性:
<include android:layout_width="fill_parent" layout="@layout/image_holder" />
<include android:layout_width="256dip" layout="@layout/image_holder" />
这个标签在你想用其他写好的布局定制自己的界面时尤其有用,不仅方便,还节省了代码。另外,如果配合<merge />
使用它的功能会更强大。
3. 使用merge标签
<merge />
标签用来减少视图树(view tree)的层级(第一节的例子,我们就减少了一层View),要理解这个标签的用法,可以看一个简单的例子:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="center"
android:src="@drawable/golden_gate" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dip"
android:layout_gravity="center_horizontal|bottom"
android:padding="12dip"
android:background="#AA000000"
android:textColor="#ffffffff"
android:text="Golden Gate" />
</FrameLayout>
这个布局显示了一张图片,并且让文字显示在图片上方。这个布局显示效果如我们所愿,而且看起来没有问题。
当你打开HierarchyViewer
检查结果的时候你会发现这个布局越来越有趣。仔细看布局树你会发现我们在布局文件里定义的FrameLayout
是另外一个FrameLayout
的唯一子节点:
(上级的FrameLayout有的显示ContentFrameLayout,都是一个东西)
既然我们的FrameLayout
与其父View有相同的尺寸(使用了match_parent
),而且没有定义任何的背景、间距、对齐方式等,那么这个Layout完全是没有用的,反而白白增加了界面的复杂度。但我们怎样取消这个FrameLayout
呢?毕竟XML文档要求一个根标签,并且XML里面的标签大都对应一个View的实例。
这时就需要<merge />
来帮忙了。当LayoutInflater
遇到这个标签,它会把<merge />
标签里的内容加到包含<merge />
标签的父内容中去。过程有点C语言#define
的意思。下一步修改代码,只用把FrameLayout
都改为merge
即可,运行查看视图树:
原来相同的FrameLayout
变成了一个。
在这个例子中,<merge/>
标签起作用的原因是:一个Activity的content View的父布局都是FrameLayout
(或ContentFrameLayout
)。如果某个父布局的根标签是其他布局,比如说LinearLayout
,那这个技巧就没法发挥作用了。
前面说过,<include/>
标签和<merge/>
标签配合很方便。很适合用在自定义组合界面。简单说来如下:
<!--a.xml-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"
android:orientation="vertical">
...
<include layout="@layout/b" />
...
</LinearLayout>
<!--b.xml-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="10dp"
android:text="My Application Content" />
</merge>
也就是由<include/>
导入的布局文件的根节点可以用<merge/>
标签表示,这样子布局在嵌入父布局时不会出现冗余的节点。
注意:
<merge/>
只可作为xml布局的根节点- 当要填充的布局的根节点是
<merge/>
时,必须指定一个父级的ViewGroup,且设置attachToRoot
为true。(参考inflate())
4. 使用viewStub标签
ViewStub
是一个隐藏的,没有尺寸的View,它可以用来在程序运行时简单的填充布局(inflate layout)文件。当一个ViewStub
设置为可见或者调用了inflate()
方法,对应的布局文件就会被填充进来,替换掉原来父布局内包含<viewStub/>
标签的内容。所以,ViewStub
设置为可见之前,在视图层级上都会只显示<viewStub/>
标签,设置可见之后,<viewStub/>
标签就变成了其包含的布局文件。这样做的意义就是,对于那些很少显示的界面,可以将其隐藏,在需要用到的时候显示出来。那么你可能会问,一个普通的View的可见设置为GONE不也是一个效果吗?视觉上是一样,但<viewStub/>
标签默认是不占用内存的,所以在启动程序后,如果不去显示这个界面,那么这个界面永远不会占用系统资源。不过,有一点,程序填充布局的过程是比较吃系统资源的,所以它适用于很少用到的界面,比如一个阅读器的导入图书相关的界面,用户并不需要经常使用导入功能,所以它就适合这么去做。
一个demo:
初始界面是一幅图和一个按钮,按下按钮显示工具栏,并隐藏按钮:
按下工具栏上的按钮隐藏工具栏,显示初始界面的按钮:
工具栏是用<viewStub/>
标签添加的。
代码:
//MainActivity.java
public class MainActivity extends Activity implements View.OnClickListener {
Button mBtnShow;
Button mBtnDone;
Button mBtnCancel;
ViewStub viewStub;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtnShow = (Button) findViewById(R.id.btn_show);
// mBtnDone = (Button) findViewById(R.id.btn_done);
// mBtnCancel = (Button) findViewById(R.id.btn_cancel);
mBtnShow.setOnClickListener(this);
// mBtnDone.setOnClickListener(this);
// mBtnCancel.setOnClickListener(this);
viewStub = (ViewStub)findViewById(R.id.stub_import);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_show:
mBtnShow.setVisibility(View.GONE);
// viewStub.inflate();
viewStub.setVisibility(View.VISIBLE);
mBtnDone = (Button) findViewById(R.id.btn_done);
mBtnCancel = (Button) findViewById(R.id.btn_cancel);
mBtnDone.setOnClickListener(this);
mBtnCancel.setOnClickListener(this);
break;
case R.id.btn_done:
Toast.makeText(this, "Pic edit done!", Toast.LENGTH_SHORT).show();
viewStub.setVisibility(View.GONE);
mBtnShow.setVisibility(View.VISIBLE);
break;
case R.id.btn_cancel:
Toast.makeText(this, "Pic edit cancel!", Toast.LENGTH_SHORT).show();
viewStub.setVisibility(View.GONE);
mBtnShow.setVisibility(View.VISIBLE);
break;
}
}
}
<!--activity_main.xml-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="center"
android:src="@drawable/golden_gate" />
<Button
android:id="@+id/btn_show"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Show edit bar"
android:visibility="visible"/>
<ViewStub
android:id="@+id/stub_import"
android:inflatedId="@+id/panel_import"
android:layout="@layout/ok_cancel_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"/>
</merge>
<!--ok_cancel_bar.xml-->
<?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="wrap_content"
android:background="#AA000000"
android:gravity="center_horizontal"
android:orientation="horizontal">
<Button
android:id="@+id/btn_done"
android:layout_width="100dip"
android:layout_height="wrap_content"
android:text="Done" />
<Button
android:id="@+id/btn_cancel"
android:layout_width="100dip"
android:layout_height="wrap_content"
android:text="Cancel" />
</LinearLayout>
在MainActivity里,有一段初始化Button的代码被注释掉了,原因就是在程序初始化时,ok_cancel_bar.xml的布局并没有被填充(inflated),所以是找不到这个Button的,这里你也可以试一下。关于<viewStub/>
的使用方法,代码里都有。
提示:
- 其中注释掉的
viewStub.inflate();
可以用,但在这个程序里会有bug,有兴趣可以自己研究下,了解其原理。- xml文件里,ViewStub标签里有两个id,用处就是当显示
<ViewStub/>
里的内容后,它就被替换掉了,这时第一个id就不可用了,要用inflatedId。可以自己改改试试,解决前面的bug- 附加的界面显示之后就占用内存了,我想让他隐藏后再从View中剔除,不让他再占内存行不行?首先,ViewStub有inflate()方法,但没有'deinflate()'一类的方法;其次,前面说过系统添加布局过程会吃资源,剔除(如果有这个方法)也是一样,所以没必要这么做,直接设置不可见就可以了,而且不可见的view在绘制界面过程中会被直接跳过;最后,你真不想让它占内存,好吧,你需要这么做:移除掉所有填充过(inflated)的界面,然后再走一遍初始化的过程去inflate view,这样
<ViewStub/>
又回来了,你觉得这样做好吗?- demo里有一个viewStub的实例,事实上,viewStub在inflated之后就被移除了,所以不建议代码里保持对它的引用,自己可以试着优化一下。
最后看一下view tree。
程序初始化后:
显示ViewStub的内容后:
可以看到,初始界面的button不论是隐藏还是可见,它都被inflated了,而viewStub的表现就如同前面大篇幅所讲的一样。
5. 使用Hierarchy Viewer工具和lint工具
- Hierarchy Viewer 可以看到布局内各个节点的层级关系,并且可以看到每个View的详细信息,并且可以放大程序的显示界面来从像素层面检查布局的细节。
- lint 是一个静态代码检测工具,可以优化应用布局的层次结构,也可以检测其他常见代码错误。可以用它快速检测你的资源文件目录找出影响代码效率的敌方。
这里介绍有什么工具,具体用法可点这里
Reference:
Android Layout Tricks #1
Reusing layouts
Android Layout Tricks #3: Optimize by merging
Android Layout Tricks #3: Optimize with stubs
Android Layout标签之-viewStub,requestFocus,merge,include
Android抽象布局——include、merge 、ViewStub
How to “deflate” a ViewStub?
Optimizing Your UI
Written with StackEdit.
[Android] 布局优化技巧的更多相关文章
- 【转】Android布局优化之ViewStub
ViewStub是Android布局优化中一个很不错的标签/控件,直接继承自View.虽然Android开发人员基本上都听说过,但是真正用的可能不多. ViewStub可以理解成一个非常轻量级的Vie ...
- Android布局优化之include、merge、ViewStub的使用
本文针对include.merge.ViewStub三个标签如何在布局复用.有效减少布局层级以及如何可以按需加载三个方面进行介绍的. 复用布局可以帮助我们创建一些可以重复使用的复杂布局.这种方式也意味 ...
- 转:Android布局优化
categories: Android 在Android开发中,我们常用的布局方式主要有LinearLayout.RelativeLayout.FrameLayout等,通过这些布局我们可以实现各种各 ...
- [旧][Android] 布局优化
备注 原发表于2016.05.21,资料已过时,仅作备份,谨慎参考 前言 最近在编写布局时,发现这一块是有很多值得深入学习的地方的.毕竟应用开发,界面展示是十分重要的部分.另外在开发时,为自己的代码做 ...
- Android布局优化:include 、merge、ViewStub的详细总结
版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 本篇博客主要是对上篇博客的补充Android性能优化之UI渲染性能优化, 没有什么新东西,觉得应该是都掌握的玩意,写出来也只是自己做个小小的总结. ...
- [Android]Android布局优化之<include />
转载请标明:转载于http://www.cnblogs.com/Liuyt-61/p/6602891.html -------------------------------------------- ...
- Android成长日记-Android布局优化
Android常用布局 1. LinearLayout(线性布局) 2. RelativeLayout(相对布局) 3. TableLayout(表格布局) 4. AbsoluteLayou(绝对布局 ...
- Android最佳性能实践(四)——布局优化技巧
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/43376527 在前面几篇文章其中.我们学习了怎样通过合理管理内存,以及高性能编码技 ...
- android 布局优化常用技巧
android对多个模块都要是要的UI逻辑的致辞除了fragment之外,没有别的东西可以支持了, include,merge,viewstub只能支持公用的ui,但是这个通用支持不能包含逻辑(jav ...
随机推荐
- ORACLE数据库表解锁record is locked by another user
出现此问题多由于操作Oracle执行完,没有COMMIT,直接把PL/SQL关闭掉,后来导致那张表被锁住,当编辑时就会出现这个信息,record is locked by another user! ...
- Windows7下无法打开chm(mk:@MSITStore:路径[cannot open the file mk@MSITstore:路径]),chm索引就关闭的解决办法
解决方法1是: 1,右键关联chm文件的“打开方式”到\Windows\HH.exe 2,在命令行运行regsvr32 itss.dll 3,在命令行运行regsvr32 hhctrl.ocx 方法2 ...
- FactoryMethod工厂方法模式(创建型模式)
1.工厂方法模式解决的问题 现在有一个抽象的游戏设施建造系统,负责构建一个现代风格和古典风格的房屋和道路. 前提:抽象变化较慢,实现变化较快(不稳定) 整个抽象的游戏设施建造系统相对变化较慢,本例中只 ...
- Linux系统基础知识整理
一.说明 本篇文章,我将结合自己的实践以及简介,来对linux系统做一个直观清晰的介绍,使得哪些刚接触Linux的小伙伴可以快速入门,也方便自己以后进行复习查阅. 二.基本知识整理 1.Linux文件 ...
- hibernate对连接池的支持和HQL查询
hibernate对连接池的支持 连接池, 作用: 管理连接:提升连接的利用效率! 常用的连接池: C3P0连接池 Hibernate 自带的也有一个连接池,且对C3P0连接池也有支持! 只维护一个连 ...
- JavaScript对象Object
<script> var obj = new Object(); var obj2 = {}; obj2.firstName = "wang"; obj2.lastNa ...
- MySQL问答整理
1.Mysql中有哪些不同的表格? MyISAM: 基于IASM代码.可以被压缩,支持全文搜索,事务不安全,而且也不支持外键.如果事务回滚将会造成不完全回滚,从而不具备原子性.所以假如忽略事务以及访问 ...
- Java中数据类型转换大全(个人总结)
一.字符串转换为其他类型 1.将字符串转化为int型 (1)方法一 int i = Integer.parseInt(String str); (2)方法二 int i = Integer.value ...
- 【SpringBoot系列3】SpringBoot使用事务和AOP
前言: 因为SpringBoot操作两者实在太简单了,我就放一起来写了. 正文(事务): /** * springboot中运用事务 * 真的超级方便,直接加上注解就ok了,连配置都省了 * @ret ...
- 21.Module 的加载实现
Module 的加载实现 Module 的加载实现 上一章介绍了模块的语法,本章介绍如何在浏览器和 Node 之中加载 ES6 模块,以及实际开发中经常遇到的一些问题(比如循环加载). 浏览器加载 传 ...