Android 卡顿优化 4 布局优化实际技巧
今天分享一些layout布局书写中的一些技巧,希望看过之后你也一样可以写出性价比高的布局。我个人的目标是用最少的View写出一样效果的布局。因为我相信View的数量减少伴随着的就是层级的减少。从而达到结构清晰,渲染速度快的效果。顺着这个逻辑,我将优化分为重用、合并、按需载入。
重用
< include/>
< include>标签可以在一个布局中引入另外一个布局,这个的好处显而易见。类似于我们经常用到的工具类,随用随调。便于统一修改使用。
举例说明:首先写一个公共的布局title_bar.xml,app中常用的标题栏。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:background="@color/background"
android:layout_height="48dp">
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:src="@drawable/icon_back"/>
<TextView
tools:text="标题"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="18sp"
android:textColor="@color/white" />
<TextView
tools:text="确定"
android:layout_width="wrap_content"
android:gravity="center"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:textSize="16sp"
android:textColor="@color/white" />
</RelativeLayout>
预览:
下来activity_main.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">
<include
layout="@layout/title_bar"/>
</RelativeLayout>
运行后效果和预览一样一样的。当然我们也可以在< include>标签当中重新设置宽高等layout属性。
合并
减少嵌套
首先我们心中要有一个大原则:尽量保持布局层级的扁平化。在这个大原则下我们要知道:
在不影响层级深度的情况下,使用LinearLayout而不是RelativeLayout。因为RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,才会让子View调用2次onMeasure。Measure的耗时越长那么绘制效率就低。
如果非要是嵌套,那么尽量避免RelativeLayout嵌套RelativeLayout。这简直就是恶性循环,丧心病狂。
实现方法就不细说了,大家都是明白人。
< merge/>
< merge/>主要用来去除不必要的FrameLayout。它的使用最理想的情况就是你的根布局是FrameLayout,同时没有使用background等属性。这时可以直接替换。因为我们布局外层就是FrameLayout,直接“合并”。
举例说明:比如上面用到的activity_main.xml文件,我们通过View Hierarchy工具看一下,如图:
可以看到,最外层是FrameLayout,下来我们修改一下。
<?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">
<include
layout="@layout/title_bar"/>
</merge>
再次查看:
很明显少了一层RelativeLayout,当然运行效果是一样的。当然如果我们不需要title_bar.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">
<ImageView
android:layout_width="wrap_content"
android:layout_height="48dp"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:src="@drawable/icon_back_1"/>
<TextView
android:layout_gravity="center_horizontal"
android:text="标题"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_centerInParent="true"
android:textSize="18sp"
android:textColor="@color/black" />
<TextView
android:text="确定"
android:layout_gravity="right"
android:layout_width="wrap_content"
android:gravity="center"
android:layout_height="48dp"
android:layout_alignParentRight="true"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:textSize="16sp"
android:textColor="@color/black" />
</merge>
运行效果:
运行查看层级,如下图:
结果很明显。
用TextView同时显示图片和文字
这个我就不细说了,举一个我们项目中的一个例子,代码一看便知。
首先要完成的效果是如下图:
这种效果很常见,一般实现方法是这样。(貌似没人这样写吧,哈哈)
<?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="match_parent">
<LinearLayout
android:orientation="horizontal"
android:background="@color/white"
android:layout_width="match_parent"
android:layout_height="50dp">
<ImageView
android:layout_marginLeft="10dp"
android:layout_width="wrap_content"
android:src="@drawable/icon_1"
android:layout_height="match_parent" />
<TextView
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textSize="16sp"
android:text="我的卡券"
android:gravity="center_vertical"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent" />
<ImageView
android:layout_marginRight="10dp"
android:src="@drawable/icon_4"
android:layout_width="wrap_content"
android:layout_height="match_parent"/>
</LinearLayout>
</LinearLayout>
效果图:
那么我们优化一下:
<?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="match_parent">
<TextView
android:drawableLeft="@drawable/icon_1"
android:drawableRight="@drawable/icon_4"
android:drawablePadding="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textSize="16sp"
android:text="我的卡券"
android:background="@color/white"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="50dp" />
</LinearLayout>
你没有看错,少了两个ImageView和去除嵌套LinearLayout。效果不用说一样一样的。当然EditView等也一样的,还有属性drawableBottom和drawableTop供你使用。同时利用代码setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom)
可以让我们动态去设置图片。
使用TextView的行间距
先上我们需要实现的效果图:
效果很简单,实现代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="100dp"
android:background="@color/white"
android:layout_width="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<ImageView
android:padding="25dp"
android:src="@drawable/kd_1"
android:layout_width="100dp"
android:layout_height="100dp"/>
<LinearLayout
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="100dp">
<TextView
tools:text="揽件方式:上门取件"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="25dp"/>
<TextView
tools:text="快递公司:顺丰快递"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="25dp"/>
<TextView
tools:text="预约时间:9月6日 立即取件"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="25dp"/>
<TextView
tools:text="快递费用:等待称重确定价格"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="25dp"/>
</LinearLayout>
</LinearLayout>
这里我偷懒了多嵌套了一层LinearLayout,但。。。这不重要,我先直接修改。
优化后代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="100dp"
android:background="@color/white"
android:layout_width="match_parent">
<ImageView
android:padding="25dp"
android:src="@drawable/kd_1"
android:layout_width="100dp"
android:layout_height="match_parent"/>
<TextView
android:textSize="14dp"
android:lineSpacingExtra="8dp"
android:gravity="center_vertical"
android:text="揽件方式:上门取件\n快递公司:顺丰快递\n预约时间:9月6日 立即取件\n快递费用:等待称重确定价格"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
老规矩,效果一样一样的。可以看到我们仅仅利用android:lineSpacingExtra="8dp"
这一行代码就省去了3个TextView,如果行数更多呢?是不是方便多了。
其中:lineSpacingExtra
属性代表的是行间距,他默认是0,是一个绝对高度值。同时还有lineSpacingMultiplier
属性,它代表行间距倍数,默认为1.0f,是一个相对高度值。我们来使用一下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="100dp"
android:background="@color/white"
android:layout_width="match_parent">
<ImageView
android:padding="25dp"
android:src="@drawable/kd_1"
android:layout_width="100dp"
android:layout_height="100dp"/>
<TextView
android:textSize="14dp"
android:lineSpacingMultiplier="1.3"
android:gravity="center_vertical"
android:text="揽件方式:上门取件\n快递公司:顺丰快递\n预约时间:9月6日 立即取件\n快递费用:等待称重确定价格"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
当然了这两条属性可以同时使用,查看源码可以知道,他们的高度计算规则为mTextPaint.getFontMetricsInt(null) * 行间距倍数 + 行间距。
使用Spannable或Html.fromHtml
这里也是举例说明,比如下图效果:
如果实现上图红框中的效果,笨办法就是写三个TextView,“¥”,“价格”,“门市价”分别实现,其实用一个TextVIew就可以实现,类似如下代码:
String text = String.format("¥%1$s 门市价:¥%2$s", 18.6, 22);
int z = text.lastIndexOf("门");
SpannableStringBuilder style = new SpannableStringBuilder(text);
style.setSpan(new AbsoluteSizeSpan(DisplayUtil.dip2px(mContext,14)), 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); //字号
style.setSpan(new ForegroundColorSpan(Color.parseColor("#afafaf")), z, text.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE); //颜色
style.setSpan(new AbsoluteSizeSpan(DisplayUtil.dip2px(mContext,14)), z, text.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE); //字号
tv.setText(style);
同样Html.fromHtml也可以实现。这样不就减少了两个TextView了。
按需载入
ViewStub
在开发中经常会遇到这样的情况,会在程序运行时动态根据条件来决定显示哪个View或某个布局。那么通常做法就是把用到的View都写在布局中,然后在代码中动态的更改它的可见性。但是它的这样仍然会创建View,会影响性能。
这时就可以用到ViewStub了,ViewStub是一个轻量级的View,不占布局位置,占用资源非常小。
例子:比如我们请求网络加载列表,如果网络异常或者加载失败我们可以显示一个提示View,上面可以点击重新加载。当然一直没有错误时,我们就不显示。
<?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" >
……
<ViewStub
android:layout_gravity="center"
android:id="@+id/hint_view"
android:layout_width="match_parent"
android:inflatedId="@+id/hint_view"
android:layout_height="wrap_content"
android:layout="@layout/hint_view"/>
</merge>
hint_view.xml就是这个提示View,可以根据情况自己写。
用法:
private View hintView;
if (网络异常。。。) {
if (hintView == null) {
ViewStub viewStub = (ViewStub)this.findViewById(R.id.hint_view);
hintView = viewStub.inflate();
TextView textView = (TextView) hintView.findViewById(R.id.tv);
textView.setText("网络异常!");
}
hintView.setVisibility(View.VISIBLE);
}else{
if (hintView != null) {
hintView.setVisibility(View.GONE);
}
}
用法很简单,记得一旦ViewStub可见或是被inflate了,ViewStub就不存在了,取而代之的是被inflate的Layout。所以它也被称做惰性控件。
其他小技巧
用LinearLayout自带的分割线
还记得上文用TextView同时显示图片和文字中的例子吗?我们可以看到每个条目之间都是有一根分隔线的,那么怎么实现呢?别人我不知道,反正我原来是用一个View设置高度实现的。相信一定有人和我一样。
那么老办法我就不演示了,直接上代码:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:divider="@drawable/divider"
android:showDividers="middle">
<TextView
android:drawableLeft="@drawable/icon_1"
android:drawableRight="@drawable/icon_4"
android:drawablePadding="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textSize="16sp"
android:text="我的卡券"
android:background="@color/white"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="50dp" />
<TextView
android:drawableLeft="@drawable/icon_2"
android:drawableRight="@drawable/icon_4"
android:drawablePadding="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textSize="16sp"
android:text="地址管理"
android:background="@color/white"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="50dp" />
<TextView
android:drawableLeft="@drawable/icon_3"
android:drawableRight="@drawable/icon_4"
android:drawablePadding="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textSize="16sp"
android:text="检查更新"
android:background="@color/white"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="50dp" />
</LinearLayout>
效果图:
实现的核心部分其实是LinearLayout的这两行。
android:divider="@drawable/divider"
android:showDividers="middle"
其中divider.xml是分隔线样式。
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:width="1dp"
android:height="1dp"/>
<solid android:color="#e1e1e1"/>
</shape>
showDividers 是分隔线的显示位置,beginning、middle、end分别代表显示在开始位置,中间,末尾。
还有dividerPadding属性这里没有用到,意思很明确给divider添加padding。感兴趣可以试试。
Space控件
还是接着上面的例子,如果要给条目中间添加间距,怎么实现呢?当然也很简单,比如添加一个高10dp的View,或者使用android:layout_marginTop="10dp"
等方法。但是增加View违背了我们的初衷,并且影响性能。使用过多的margin其实会影响代码的可读性。
这时你就可以使用Space,他是一个轻量级的。我们可以看下源码:
/**
* Space is a lightweight View subclass that may be used to create gaps between components
* in general purpose layouts.
*/
public final class Space extends View {
/**
* {@inheritDoc}
*/
public Space(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
if (getVisibility() == VISIBLE) {
setVisibility(INVISIBLE);
}
}
/**
* {@inheritDoc}
*/
public Space(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
/**
* {@inheritDoc}
*/
public Space(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* {@inheritDoc}
*/
public Space(Context context) {
//noinspection NullableProblems
this(context, null);
}
/**
* Draw nothing.
*
* @param canvas an unused parameter.
*/
@Override
public void draw(Canvas canvas) {
}
/**
* Compare to: {@link View#getDefaultSize(int, int)}
* If mode is AT_MOST, return the child size instead of the parent size
* (unless it is too big).
*/
private static int getDefaultSize2(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
result = Math.min(size, specSize);
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
getDefaultSize2(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize2(getSuggestedMinimumHeight(), heightMeasureSpec));
}
}
可以看到在draw方法没有绘制任何东西,那么性能也就几乎没有影响。
实现代码与效果:
<?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="match_parent"
android:orientation="vertical"
android:divider="@drawable/divider"
android:showDividers="middle|beginning|end">
<TextView
android:drawableLeft="@drawable/icon_1"
android:drawableRight="@drawable/icon_4"
android:drawablePadding="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textSize="16sp"
android:text="我的卡券"
android:background="@color/white"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="50dp" />
<TextView
android:drawableLeft="@drawable/icon_2"
android:drawableRight="@drawable/icon_4"
android:drawablePadding="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textSize="16sp"
android:text="地址管理"
android:background="@color/white"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="50dp" />
<Space
android:layout_width="match_parent"
android:layout_height="15dp"/>
<TextView
android:drawableLeft="@drawable/icon_3"
android:drawableRight="@drawable/icon_4"
android:drawablePadding="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:textSize="16sp"
android:text="检查更新"
android:background="@color/white"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="50dp" />
</LinearLayout>
防止过度绘制
这个完全可以看鸿洋大神这篇 《Android UI性能优化实战 识别绘制中的性能问题》:http://blog.csdn.net/lmj623565791/article/details/45556391
参考
总结
最后想想如果没有用这些技巧,我们要写多少代码,多少View?效果是不是杠杠的!其实上面说了这么多,具体的情景使用还是要看项目的具体情况,在性能面前有些还是要取舍的,但千万不能为了优化而优化。优化也是不断积累的过程,不要指望立竿见影。愿大家都能写出一手漂亮的布局。最后觉得不错的点个赞哈!
Android 卡顿优化 4 布局优化实际技巧的更多相关文章
- Android性能优化之布局优化
最新最准确内容建议直接访问原文:Android性能优化之布局优化 本文为Android性能优化的第二篇——布局优化,主要介绍使用抽象布局标签(include, viewstub, merge).去除不 ...
- 【转】Android性能优化之布局优化篇
转自:http://blog.csdn.net/feiduclear_up/article/details/46670433 Android性能优化之布局优化篇 分类: andorid 开发2015 ...
- Android UI学习 - FrameLayou和布局优化(viewstub)
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://android.blog.51cto.com/268543/308090 Fram ...
- Android 性能优化 三 布局优化ViewStub标签的使用
小黑与小白的故事,通过虚拟这两个人物进行一问一答的形式来共同学习ViewStub的使用 小白:Hi,小黑,ViewStub是什么?听说能够用来进行布局优化. 小黑:ViewStub 是一个隐藏的,不占 ...
- Android实习生 —— 屏幕适配及布局优化
为什么要进行屏幕适配.对哪些设备进行适配?在近几年的发展当中,安卓设备数量逐渐增长,由于安卓设备的开放性,导致安卓设备的屏幕尺寸大小碎片化极为严重.从[友盟+]2016年手机生态发展报告H1中看截止1 ...
- Android 卡顿优化 3 布局优化 工具 Hierarchy Viewer
欲善其事, 先利其器. 分析布局, 就不得不用到Hierarchy Viewer了. 本文工具使用皆以GithubApp的详情界面RepoDetailActivity为例说明. 为了不影响阅读体验, ...
- Android 卡顿优化 2 渲染优化
1.概述 2015年初google发布了Android性能优化典范,发了16个小视频供大家欣赏,当时我也将其下载,通过微信公众号给大家推送了百度云的下载地址(地址在文末,ps:欢迎大家订阅公众号),那 ...
- Android 卡顿优化 1 卡顿解析
1, 感知卡顿 用户对卡顿的感知, 主要来源于界面的刷新. 而界面的性能主要是依赖于设备的UI渲染性能. 如果我们的UI设计过于复杂, 或是实现不够好, 设备又不给力, 界面就会像卡住了一样, 给用户 ...
- Android开发学习之路--性能优化之布局优化
Android性能优化方面也有很多文章了,这里就做一个总结,从原理到方法,工具等做一个简单的了解,从而可以慢慢地改变编码风格,从而提高性能. 一.Android系统是如何处理UI组件的更新操作的 ...
随机推荐
- 15:django 缓存架构
动态网站的一个基本权衡就是他们是动态的,每次一个用户请求一个页面,web服务器进行各种各样的计算-从数据库查询到模板渲染到业务逻辑-从而生成站点访问者看到的页面.从处理开销的角度来看,相比标准的从文件 ...
- 多路复用I/O模型poll() 模型 代码实现
多路复用I/O模型poll() 模型 代码实现 poll()机制和select()机制是相似的,都是对多个描述符进行轮询的方式. 不同的是poll()没有描述符数目的限制. 是通过struct pol ...
- selenium+python自动化80-文件下载(不弹询问框)【转载】
转至博客:上海-悠悠 前言 上一篇是点弹出框上的按钮去保存文件,本篇介绍一种更加优雅的方法,加载Firefox和Chrome的配置文件,不弹出询问框后台下载. 一.FirefoxProfile 1.点 ...
- 《逐梦旅程 WINDOWS游戏编程之从零开始》笔记8——载入三维模型&Alpha混合技术&深度测试与Z缓存
第17章 三维游戏模型的载入 主要是如何从3ds max中导出.X文件,以及如何从X文件加载三维模型到DirextX游戏程序里.因为复杂的3D物体,要用代码去实现,那太反人类了,所以我们需要一些建模软 ...
- jmeter+Jenkins 持续集成中发送邮件报错:MessagingException message: Exception reading response
已经配置好了发送邮件的相关信息,但是执行完脚本出现报错:MessagingException message: Exception reading response 1.查看Jenkins本次构建的控 ...
- Jmeter------查看JSON Extractor获取的值
在接口的使用中,我们会经常用到上个接口response中的值作为下个接口的参数来使用,因此我们为了确保值的正确性,需要知道上个接口返回的值是否正确,因此我们使用到了如下的方法来查看返回值. 1.首先在 ...
- 深度理解onmouseover事件和onmouseout事件
今天简单的讲解下onmouseover事件和onmouseout事件,一直以为它们只是简单的分别实现鼠标指针移动到元素上时触发事件和在鼠标指针移出指定的对象时触发事件,但是突然发现这些只是对它们简单的 ...
- 走进 Akka.NET
官方文档:https://getakka.net/index.html 官网:https://petabridge.com/ 一.Akka.NET 是什么? Akka 是一个构建高并发.分布式和弹性消 ...
- cordova plugin 大全及安装命令
前言:用cordova开发hybrid app的过程中,由于是html5开发的app,在手机上很多权限受限制,这就导致了我们需要安装很多插件来弥补这一缺点.在网上搜了好久,感觉都不是很全,所以自己整理 ...
- JS获取网页高度和宽度
注:此文属于转载自他人博客 网页可见区域宽: document.body.clientWidth 网页可见区域高: document.body.clientHeight 网页可见区域宽: docume ...