Android布局优化

前言

本篇文章为Android优化的布局部分,该部分应该是Android中很重要的,无论是在自定义控件中,还是在简单的书写布局时,都应该尽量遵循一些优化原则,这样布局的绘制效率才会更高,体验才能更好。

优化layout的层级

Layout结构如果太复杂,Android的绘制过程就会很复杂,measure过程就会很复杂,我分析的View绘制机制中详细介绍了整个测量、布局和绘制过程,过于复杂、嵌套的布局会造成性能问题。如果布局中可以使用LinearLayout和RelativeLayout就使用LinearLayout,RelativeLayout比较复杂,花费的cpu时间更多。

避免嵌套

嵌套的 LinearLayout 可能会使得 View 的层级结构很深。使用LinearLayout时,通常我们喜欢用嵌套的布局来动态设置一个View的Visibility ,由于LinearLayout是线性的,因此即使隐藏一个View也不会影响到其它View的排列。而在RelativeLayout中,View的位置都是相对于其它View的,因此,隐藏之后,会导致之前的View没有参考对象了,导致的相对位置改变,这时你可以使用alignWithParentIfMissing=”true”来处理这种情况。

此外,嵌套使用了 layout_weight 参数的 LinearLayout 的计算量会尤其大,因为每个子元素都需要被测量两次。这对需要多次重复 inflate 的 Layout 尤其需要注意,比如使用 ListView 或 GridView 时。

使用merge标签优化层级

在使用了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 的根节点。例如:

<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 被嵌套。

二 使用标签重用Layout

如果你的程序 UI 在不同地方重复使用某个 Layout,那本节教你如何创建高效的,可重用的 Layout 部件,并把它们“包含”到 UI Layout 中。

为了高效重用整个的 Layout,你可以使用 和 标签把其他 Layout 嵌入当前 Layout。

按需载入视图

除了简单的把一个 Layout 包含到另一个中,你可能还想在程序开始后,仅当你的 Layout 对用户可见时才开始载入。

3.1 不需要立即加载的布局,设置为GONE,系统会跳过,不加载

3.2 使用ViewStub 实现按需加载

ViewStub 是一个轻量的视图,不需要大小信息,也不会在被加入的 Layout 中绘制任何东西。每个 ViewStub 只需要设置 android:layout 属性来指定需要被 inflate 的 Layout 类型。viewstub常用来引入那些默认不会显示,只在特殊情况下显示的布局,如进度布局、网络失败显示的刷新布局、信息出错出现的提示布局等。

以下 ViewStub 是一个半透明的进度条覆盖层。功能上讲,它应该只在新的数据项被导入到应用程序时可见。

<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代码如下:

<?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)方式

View viewStub = ((ViewStub)findViewById(R.id.stub_import));
viewStub.setVisibility(View.VISIBLE);
netErrorLayout = findViewById(R.id.net_error_layout))

inflate方式

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

四 ListView的优化

如果你有一个包含复杂或者每个项 (item) 包含很多数据的 ListView ,那么上下滚动的性能可能会降低。本节给你一些关于如何把滚动变得更流畅的提示。
保持程序流畅的关键,是让主线程(UI 线程)不要进行大量运算。你要确保在其他线程执行磁盘读写、网络读写或是 SQL 操作等。为了测试你的应用的状态,你可以启用 StrictMode。

4.1 使用后台线程
你应该把主线程中的耗时间的操作,提取到一个后台线程中,使得主线程只关注 UI 绘画。

4.2 在 View Holder 中填入视图对象
使用convertView、
你的代码可能在 ListView 滑动时经常使用 findViewById(),这样会降低性能。即使是 Adapter 返回一个用于回收的 convertView,你仍然需要查找这个元素并更新它。避免频繁调用 findViewById() 的方法之一,就是使用 View Holder(视图占位符)设计模式。

ViewHolder 存储了标签下的每个视图。这样你不用频繁查找这个元素:

static class ViewHolder {
  TextView text;
  TextView timestamp;
  ImageView icon;
  ProgressBar progress;
  int position;
}
```  
然后,在 Layout 的类中生成一个 ViewHolder 对象:
```java
ViewHolder holder = new ViewHolder();
holder.icon = (ImageView) convertView.findViewById(R.id.listitem_image);
...
convertView.setTag(holder);

这样你就可以轻松获取每个视图,而不是用 findViewById() 来不断查找视图,节省了宝贵的运算时间。

4.3 getView不要做复杂的操作
因为每一条Item移入屏幕的时候,都会调用getView,不要在getView中做复杂的操作,不要频繁的创建对象。Item点击的处理不要提前做。特别是在快速滑动的时候,会导致频繁的调用getView。

五 优化提示

尽量使用RelativeLayout,可以减少层级的嵌套。
慎用LinearLayout的layout_weight属性,可以使用RelativeLayout的centerHorizontal=”true”、toLeft、toRight代替

六 include的用法以及注意点

 

include标签

include标签常用于将布局中的公共部分提取出来供其他layout共用,以实现布局模块化,也是平常我们设计布局时用的最多的

include标签使用:

一、定义要实现(抽取)的layout布局:

//include_test.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:gravity="center_horizontal"
              android:orientation="vertical">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="@string/textview"
        android:textSize="24sp"/>


    <EditText
        android:id="@+id/editText"
        android:hint="@string/divide"
        android:layout_width="300dp"
        android:layout_height="wrap_content"/>

</LinearLayout>

---------------------------------------------------------------

//include_text_relative

<?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="wrap_content"
                android:gravity="center_horizontal"
    >

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="TextView_Relative"
        android:textSize="24sp"/>


    <EditText
        android:id="@+id/editText"
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textView"
        android:hint="@string/divide"/>

</RelativeLayout>

---------------------------------------------------------------

//include_toolbar.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/tb_toolbar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    android:background="#00f"
    app:theme="@style/AppTheme"
    app:title="这是一个ToolBar"
    app:titleTextColor="@android:color/white"/>

二、Activity的XML布局文件调用include标签:

<?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"
    >
    <!--测试layout和<include>都设置ID的情况-->
    <include
        android:id="@+id/tb_toolbar"
        layout="@layout/include_toolbar"/>

    <!--如果只有单个include 这样写就可以,加载的布局的子View,直接findViewByID就能找到-->
    <include layout="@layout/include_text"/>

    <!--如果有多个include,需要添加ID属性-->
    <include
        android:id="@+id/include_text1"
        layout="@layout/include_text"/>

    <!--这个layout用RelativeLayout 实现-->
    <!--如果要使用layout_margin这样的属性,要同时加上layout_w/h属性,不然没反应-->
    <include
        android:id="@+id/include_text2"
        layout="@layout/include_text_relative"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="50dp"/>


</LinearLayout>

三、Activity中调用include标签layout中的子View:

private void initView() {
        //如果include布局根容器和include标签中的id设置的是不同的值,这里获取的mToolbar值将为null
        Toolbar mToolbar = (Toolbar) findViewById(R.id.tb_toolbar);
        setSupportActionBar(mToolbar);

        //普通include标签用法,直接拿子View属性实现
        TextView textView = (TextView) findViewById(R.id.textView);
        textView.setText("不加ID实现的include标签");

        //多个include标签用法,添加ID,findViewByID找到layout,再找子控件
        View view_include = findViewById(R.id.include_text1);
        TextView view_include_textView = (TextView) view_include.findViewById(R.id.textView);
        view_include_textView.setText("加了ID实现的include标签");

        //多个include标签用法,添加ID,findViewByID找到layout,再找子控件
        View view_include_Relative = findViewById(R.id.include_text2);
        TextView view_textView_relative = (TextView) view_include_Relative.findViewById(R.id.textView);
        view_textView_relative.setText("加了ID实现的include标签(RelaviteLayout)");

    }

include使用注意

  • 一个xml布局文件有多个include标签需要设置ID,才能找到相应子View的控件,否则只能找到第一个include的layout布局,以及该布局的控件
  • include标签如果使用layout_xx属性,会覆盖被include的xml文件根节点对应的layout_xx属性,建议在include标签调用的布局设置好宽高位置,防止不必要的bug
  • include 添加id,会覆盖被include的xml文件根节点ID,这里建议include和被include覆盖的xml文件根节点设置同名的ID,不然有可能会报空指针异常
  • 如果要在include标签下使用RelativeLayout,如layout_margin等其他属性,记得要同时设置layout_width和layout_height,不然其它属性会没反应

merge 标签

merge标签主要用于辅助include标签,在使用include后可能导致布局嵌套过多,多余的layout节点或导致解析变慢(可通过hierarchy viewer工具查看布局的嵌套情况)

官方文档说明:merge用于消除视图层次结构中的冗余视图,例如根布局是Linearlayout,那么我们又include一个LinerLayout布局就没意义了,反而会减慢UI加载速度

merge标签常用场景:

  • 根布局是FrameLayout且不需要设置background或padding等属性,可以用merge代替,因为Activity的ContentView父元素就是FrameLayout,所以可以用merge消除只剩一个.
  • 某布局作为子布局被其他布局include时,使用merge当作该布局的顶节点,这样在被引入时顶结点会自动被忽略,而将其子节点全部合并到主布局中.
  • 自定义View如果继承LinearLayout(ViewGroup),建议让自定义View的布局文件根布局设置成merge,这样能少一层结点.

 

merge标签使用:

在XML布局文件的根布局如RelativeLayout直接改成merge即可

merge使用注意

  • 因为merge标签并不是View,所以在通过LayoutInflate.inflate()方法渲染的时候,第二个参数必须指定一个父容器,且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点.
  • 因为merge不是View,所以对merge标签设置的所有属性都是无效的.
  • 注意如果include的layout用了merge,调用include的根布局也使用了merge标签,那么就失去布局的属性了
  • merge标签必须使用在根布局
  • ViewStub标签中的layout布局不能使用merge标签

ViewStub 标签

ViewStub 标签最大的优点是当你需要时才会加载,使用它并不会影响UI初始化时的性能.各种不常用的布局像进度条、显示错误消息等可以使用ViewStub标签,以减少内存使用量,加快渲染速度.ViewStub是一个不可见的,实际上是把宽高设置为0的View.效果有点类似普通的view.setVisible(),但性能体验提高不少

第一次初始化时,初始化的是ViewStub View,当我们调用inflate()或setVisibility()后会被remove掉,然后在将其中的layout加到当前view hierarchy中

//官方例子

<ViewStub  
    android:id="@+id/stub_import"  
    <!--android:inflateId:重写ViewStub的父布局控件的Id-->
    android:inflatedId="@+id/panel_import"  
    < <!--android:layout:设置ViewStub被inflate的布局-->
    android:layout="@layout/progress_overlay"  
    android:layout_width="fill_parent"  
    android:layout_height="wrap_content"  
    android:layout_gravity="bottom" />  

-------------------------------------------------

    //当你想加载布局时,可以使用下面其中一种方法:
    ((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);  
    // or  
    View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();

判断ViewStub(做单例)是否已经加载过: 

  • 如果通过setVisibility来加载,那么通过判断可见性即可;
  • 如果通过inflate()来加载,判断ViewStub 的ID是否为null来判断(findViewById(…))
public class ViewStubActivity extends AppCompatActivity implements View.OnClickListener, ViewStub.OnInflateListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view_stub);
    }

    private View networkErrorView;

    private void showNetError() {
        // not repeated infalte
        if (networkErrorView != null) {
            //setVisibility()方式加载布局,加载次数不限
            networkErrorView.setVisibility(View.VISIBLE);
            return;
        }
        //inflate()方式加载布局,只能加载一次
        ViewStub stub = (ViewStub) findViewById(R.id.network_error_layout);
        stub.setOnInflateListener(this);
        networkErrorView = stub.inflate();
        Button networkSetting = (Button) networkErrorView.findViewById(R.id.network_miss);
        networkSetting.setOnClickListener(this);
        Button refresh = (Button) findViewById(R.id.network_refresh);
        refresh.setOnClickListener(this);

    }

    private void showNormal() {
        if (networkErrorView != null) {
            networkErrorView.setVisibility(View.GONE);
        }
    }

    public void show(View view) {
        showNetError();
    }

    public void refresh(View view) {
        showNormal();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.network_miss:
                Toast.makeText(this, "network_miss", Toast.LENGTH_SHORT).show();
                break;
            case R.id.network_refresh:
                Toast.makeText(this, "network_refresh", Toast.LENGTH_SHORT).show();
                break;
        }

    }

    /**
     *
     * @param stub 当前待inflate的ViewStub控件
     * @param inflated 当前被inflate的View视图
     */
    @Override
    public void onInflate(ViewStub stub, View inflated) {

    }
}

ViewStub标签使用注意

  • ViewStub标签不支持merge标签
  • ViewStub的inflate只能被调用一次,第二次调用会抛出异常,setVisibility可以被调用多次,但不建议这么做(ViewStub 调用过后,可能被GC掉,再调用setVisibility()会报异常)
  • 为ViewStub赋值的android:layout_XX属性会替换待加载布局文件的根节点对应的属性

Space组件

在ConstraintLayout出来前,我们写布局都会使用到大量的margin或padding,但是这种方式可读性会很差,加一个布局嵌套又会损耗性能

鉴于这种情况,我们可以使用space,使用方式和View一样,不过主要用来占位置,不会有任何显示效果

七 书写规范上的优化

7.1 Id的命名
为了便于识别,你可以根据自己的业务来对当前界面的资源进行命名,比如当前是登陆界面,那么你可以这样命名:

  • login_edit_username
  • login_edit_password
  • login_btn_submit
  • login_txv_forgot_pass

7.2 资源的命名

  • ic_action_add, ic_action_location (ActionBar Icons)
  • ic_play, ic_save (General Icons)
  • ic_tab_music, ic_tab_more (Tab Icons)

7.3 通用的资源命名
对style.xml和dimens.xml的命名可以通用的尽量通用,因为一个项目的基本视图很多都是通用的,比如ActionBar、ListView等,规范通用的命名可以很方便的移植到其它项目中。

<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>

文章哀怨:

https://blog.youkuaiyun.com/u012792686/article/details/72901531

https://blog.youkuaiyun.com/xjbclz/article/details/52661763

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值