一个应用使用起来是否流畅对于用户来说是首要体验感,因为对于非开发者的用户来说,很难理解到 Android 中一个功能实现的难易程度,他们能判断这个应用是否好用的依据可能就是在功能实现上、动画上、流畅度上等。其中流畅度基本上是最重要的,当用户使用一个软件,如果页面加载很慢,或者经常发生 OOM 导致异常退出,那么这个应用一定不会收用户喜爱,所以对于 Android 应用的优化是非常重要的。
其中页面优化便是其中之一,如果你的布局文件嵌套太多,Android 绘制时必然要花费更多的时间。这个不太了解的可以看看Android View 的工作原理。
进行布局优化时,除了可以删除无用的控件和层级之外,还要学会选择总性能最高的 ViewGroup。在 Android 中,RelativeLayout 的性能较低 ,如果当一个布局能够使用 RelativeLayout 实现,也可以使用 LinearLayout 实现时,优先选择 LinearLayout。但是当一个布局需要使用 多个 LinearLayout 或者 多个 FrameLayout 来嵌套实现时,可能相对来说使用不嵌套的 RelativeLayout 能够实现的话性能会更高效。
布局优化还可以使用 <include> 和 <merge> 标签和 ViewStub。
- <include>标签
<include>标签可以将一个指定的布局添加到当前布局文件中,比如我们先建立一个 layout_button.xml:
然后我们新建一个 layout_test.xml,使用<include>标签,将 layout_button.xml 插入进去:
<include>标签的好处就是,当使用 layout="" 属性指定了一个布局文件,就可以不用重复再写一遍这个布局的内容了。但是需要注意的是,<include> 标签只支持以 layout_ 开头的属性。而且当布局文件和<include>标签都指定了 id 属性,以<include>标签为主。
- <merge>标签
<merge>标签一般是用来和<include>标签搭配使用减少嵌套的,当<include>标签的父标签布局属性是啥,<merge>标签的布局属性就是啥,比如<include>标签的父标签是一个按 vertical 排列的 LinearLayout,那么<merge>标签内各控件就是默认按 vertical 排列的 LinearLayout 排列,比如<include>标签的父标签是一个按 horizontal 排列的 LinearLayout,那么<merge>标签内各控件就是默认按 horizontal 排列的 LinearLayout 排列。
先新建一个 layout_merge.xml 的布局:
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<Button
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="Button"/>
<Button
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="Button"/>
</merge>
如果<include>标签的父标签是一个按 vertical 排列的 LinearLayout,那么布局结果如下:
如果<include>标签的父标签是一个按 horizontal 排列的 LinearLayout(当然为了保证<merge>标签内元素能够横向排列,我们得先把两个 Button 的 layout_width="" 属性修改一下):
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<Button
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="Button"/>
<Button
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="Button"/>
</merge>
结果如下:
当然,如果<include>标签的父标签是一个 RelativeLayout,<merge>内元素排列便是按照 RelativeLayout,其它布局类型也一样。
- ViewStub
ViewStub 继承自 View,非常轻量级且高宽都是0,这个可以通过 ViewStub 的 onMeasure() 方法得知:
ViewStub 的作用是可以按照需求来加载显示需要的布局文件,比如在网络异常或者注册时信息过多时,完全不需要一下子加载所以布局,就可以使用 ViewStub 来需要时再加载布局,提高程序的性能。
比如我们先建立两个布局文件:layout_text_a.xml 和 layout_text_b.xml:
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv_a"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Im Text A"
android:gravity="center_horizontal"
android:textSize="24sp"/>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv_b"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Im Text B"
android:gravity="center_horizontal"
android:textSize="24sp"/>
然后我们去 layout_main.xml 中去添加两个 <ViewStub>:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns: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"
android:orientation="vertical"
tools:context="com.example.akon.apptest.MainActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="按钮1"
android:onClick="showAText"/>
<ViewStub
android:id="@+id/viewstub_a"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/layout_text_a"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="按钮1"
android:onClick="showBText"/>
<ViewStub
android:id="@+id/viewstub_b"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/layout_text_b"/>
</LinearLayout>
<ViewStub>标签内的 layout 属性是用来放要按需加载的布局的,我们给两个按钮分别添加点击事件 showAText 和 showBText 用来显示我们的 ViewStub。
接下来是 MainActivity.java 的代码:
public class MainActivity extends AppCompatActivity {
private ViewStub viewStubA;
private ViewStub viewStubB;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewStubA = (ViewStub) findViewById(R.id.viewstub_a);
viewStubB = (ViewStub) findViewById(R.id.viewstub_b);
}
//按钮1的点击事件,展现 Text A
public void showAText(View view){
//使用 inflate() 方法显示 Text A
try {
viewStubA.inflate();
}catch (Exception e){
Log.e("Error","只能 inflate 一次");
}
}
//按钮1的点击事件,展现 Text B
public void showBText(View view){
//使用 detVisibility() 显示 Text B
viewStubB.setVisibility(View.VISIBLE);
}
}
展现 ViewStub 有如上两种方法,但是需要注意的是 ViewStub 只能 inflate() 一次,如果两次便会出现错误导致程序异常退出,所以我们要抛出异常。
下面是效果图:
电脑上没有 gif 图录制工具,大家将就看一下,大致就是这个流程。