本篇文章为Android优化的布局部分,该部分应该是Android中很重要的,无论是在自定义控件中,还是在简单的书写布局时,都应该尽量遵循一些优化原则,这样布局的绘制效率才会更高,体验才能更好。
Layout结构如果太复杂,Android的绘制过程就会很复杂,measure过程就会很复杂,我分析的View绘制机制中详细介绍了整个测量、布局和绘制过程,过于复杂、嵌套的布局会造成性能问题。
嵌套的 LinearLayout 可能会使得 View 的层级结构很深。使用LinearLayout时,通常我们喜欢用嵌套的布局来动态设置一个View的Visibility ,由于LinearLayout是线性的,因此即使隐藏一个View也不会影响到其它View的排列。而在RelativeLayout中,View的位 置都是相对于其它View的,因此,隐藏之后,会导致之前的View没有参考对象了,导致的相对位置改变,这时你可以使用 alignWithParentIfMissing=”true”来处理这种情况。
此外,嵌套使用了 layout_weight 参数的 LinearLayout 的计算量会尤其大,因为每个子元素都需要被测量两次。这对需要多次重复 inflate 的 Layout 尤其需要注意,比如使用 ListView 或 GridView 时。
在使用了include后可能导致布局嵌套过多,出现不必要的layout节点,从而导致解析变慢。
merge标签可用于两种典型情况:
布局顶结点是FrameLayout且不需要设置background或padding等属性,可以用merge代替,因为Activity内容试图的parent view就是个FrameLayout,所以可以用merge消除只剩一个。
某布局作为子布局被其他布局include时,使用merge当作该布局的顶节点,这样在被引入时顶结点会自动被忽略,而将其子节点全部合并到主布局中。
比如,如果你有一个 Layout 是一个竖直方向的 LinearLayout,其中包含两个连续的 View 可以在别的 Layout 中重用,那么你会做一个 LinearLayout 来包含这两个 View ,以便重用。不过,当使用另一个 LinearLayout 来嵌套这个可重用的 LinearLayout 时,这种嵌套 LinearLayout 的方式除了减慢你的 UI 性能外没有任何意义。
为了避免这种情况,你可以用 元素来替代可重用 Layout 的根节点。例如:
1
2
3
4
5
6
7
8
9
10
|
<merge xmlns:android=
"http://schemas.android.com/apk/res/android"
>
<Button
android:layout_width=
"fill_parent"
android:layout_height=
"wrap_content"
android:text=
"@string/add"
/>
<Button
android:layout_width=
"fill_parent"
android:layout_height=
"wrap_content"
android:text=
"@string/delete"
/>
</merge>
|
现在,当你要将这个 Layout 包含到另一个 Layout 中时(并且使用了 标签),系统会直接把两个 Button 放到 Layout 中,而不会有多余的 Layout 被嵌套。
如果你的程序 UI 在不同地方重复使用某个 Layout,那本节教你如何创建高效的,可重用的 Layout 部件,并把它们“包含”到 UI Layout 中。
为了高效重用整个的 Layout,你可以使用 和 标签把其他 Layout 嵌入当前 Layout。
除了简单的把一个 Layout 包含到另一个中,你可能还想在程序开始后,仅当你的 Layout 对用户可见时才开始载入。
ViewStub 是一个轻量的视图,不需要大小信息,也不会在被加入的 Layout 中绘制任何东西。每个 ViewStub 只需要设置 android:layout 属性来指定需要被 inflate 的 Layout 类型。viewstub常用来引入那些默认不会显示,只在特殊情况下显示的布局,如进度布局、网络失败显示的刷新布局、信息出错出现的提示布局等。
以下 ViewStub 是一个半透明的进度条覆盖层。功能上讲,它应该只在新的数据项被导入到应用程序时可见。
1
2
3
4
5
6
7
|
<ViewStub
android:id=
"@+id/stub_import"
android:inflatedId=
"@+id/panel_import"
android:layout=
"@layout/progress_overlay"
android:layout_width=
"fill_parent"
android:layout_height=
"wrap_content"
android:layout_gravity=
"bottom"
/>
|
载入 ViewStub
当你要载入用 ViewStub 声明的 Layout 时,要么用 setVisibility(View.VISIBLE) 设置它的可见性,要么调用其 inflate() 方法。
下面以在一个布局main.xml中加入网络错误时的提示页面network_error.xml为例。main.mxl代码如下:
1
2
3
4
5
6
7
8
9
10
11
|
<?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"
>
……
<ViewStub
android:id=
"@+id/network_error_layout"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:layout=
"@layout/network_error"
/>
</RelativeLayout>
|
其中network_error.xml为只有在网络错误时才需要显示的布局
setVisibility(View.VISIBLE)方式
1
2
3
|
View viewStub = ((ViewStub)findViewById(R.id.stub_import));
viewStub.setVisibility(View.VISIBLE);
netErrorLayout = findViewById(R.id.net_error_layout))
|
inflate方式
1
|
View netErrorLayout = ((ViewStub) findViewById(R.id.net_error_layout)).inflate();
|
注意:
inflate() 方法会在渲染完成后返回给被 inflate 的视图,所以你不需要再调用 findViewById() 去查找这个元素。减少inflate的次数,也会对效率有一点提升。
而setVisible方式还需要再次findViewById找到ViewStub中的元素。
一旦 ViewStub 可见或是被 inflate 了,ViewStub 元素就不存在了。取而代之的是被 inflate 的 Layout,其 id 是 ViewStub 上的 android:inflatedId 属性。(ViewStub 的 android:id 属性仅在 ViewStub 可见以前可用)
注意:ViewStub 的一个缺陷是,它目前不支持使用 标签的 Layout
如果你有一个包含复杂或者每个项 (item) 包含很多数据的 ListView ,那么上下滚动的性能可能会降低。本节给你一些关于如何把滚动变得更流畅的提示。
保持程序流畅的关键,是让主线程(UI 线程)不要进行大量运算。你要确保在其他线程执行磁盘读写、网络读写或是 SQL 操作等。为了测试你的应用的状态,你可以启用 StrictMode。
你应该把主线程中的耗时间的操作,提取到一个后台线程中,使得主线程只关注 UI 绘画。
使用convertView、
你的代码可能在 ListView 滑动时经常使用 findViewById(),这样会降低性能。即使是 Adapter 返回一个用于回收的 convertView,你仍然需要查找这个元素并更新它。避免频繁调用 findViewById() 的方法之一,就是使用 View Holder(视图占位符)设计模式。
ViewHolder 存储了标签下的每个视图。这样你不用频繁查找这个元素:
1
2
3
4
5
6
7
|
static class ViewHolder {
TextView text;
TextView timestamp;
ImageView icon;
ProgressBar progress;
int position;
}
|
然后,在 Layout 的类中生成一个 ViewHolder 对象:
1
2
3
4
|
ViewHolder holder =
new
ViewHolder();
holder.icon = (ImageView) convertView.findViewById(R.id.listitem_image);
...
convertView.setTag(holder);
|
这样你就可以轻松获取每个视图,而不是用 findViewById() 来不断查找视图,节省了宝贵的运算时间。
因为每一条Item移入屏幕的时候,都会调用getView,不要在getView中做复杂的操作,不要频繁的创建对象。Item点击的处理不要提前做。特别是在快速滑动的时候,会导致频繁的调用getView。
尽量使用RelativeLayout,可以减少层级的嵌套。
慎用LinearLayout的layout_weight属性,可以使用RelativeLayout的centerHorizontal=”true”、toLeft、toRight代替
为了便于识别,你可以根据自己的业务来对当前界面的资源进行命名,比如当前是登陆界面,那么你可以这样命名:
login_edit_username
login_edit_password
login_btn_submit
login_txv_forgot_pass
ic_action_add, ic_action_location (ActionBar Icons)
ic_play, ic_save (General Icons)
ic_tab_music, ic_tab_more (Tab Icons)
对style.xml和dimens.xml的命名可以通用的尽量通用,因为一个项目的基本视图很多都是通用的,比如ActionBar、ListView等,规范通用的命名可以很方便的移植到其它项目中。
1
2
3
4
5
6
7
8
9
10
|
<color name=
"list_item_large"
>
#FCA558</color>
<color name=
"list_item_small"
>
#FBA228</color>
<dimen name=
"list_item_large"
>24dp</dimen>
<dimen name=
"list_item_small"
>18dp</dimen>
<!-- 简单ListView样式 -->
<style name=
"list_view_style_default"
>
<item name=
"android:layout_width"
>fill_parent</item>
<item name=
"android:layout_height"
>wrap_content</item>
...
</style>
|