性能优化--布局优化技巧
性能优化--布局优化技巧
android-key 发表于6个月前
性能优化--布局优化技巧
  • 发表于 6个月前
  • 阅读 16
  • 收藏 0
  • 点赞 0
  • 评论 0

标题:腾讯云 新注册用户域名抢购1元起>>>   

1.重用布局文件

1.)include标签

首先用得最多的应该是include,按照官方的意思,include就是为了解决重复定义相同布局的问题。使用起来很方便,我这里就不举例子。

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  android:id="@+id/my_title_parent_id"   
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:orientation="vertical" >  
  
    <include 
      android:id="@+id/my_title_ly"  
      layout="@layout/titlebar" />  
  
</LinearLayout>  

注意事项:

 1.如果你只想单独修改某个布局文件的titleBar而其他布局文件的titleBar不受影响的话,我们可以使用覆写<include>属性的方式

在<include>标签当中,我们是可以覆写所有layout属性的,即include中指定的layout属性将会覆盖掉titlebar中根视图指定的layout属性。因此,这里我们希望将titlebar的高度设置成wrap_content,就可以这样写:

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  android:id="@+id/my_title_parent_id"   
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:orientation="vertical" >  
  
    <include  
        android:id="@+id/my_title_ly"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        layout="@layout/titlebar" />  
  
</LinearLayout>  

而非layout属性则无法在<include>标签当中进行覆写。另外需要注意的是,如果我们想要在<include>标签当中覆写layout属性,必须要将layout_width和layout_height这两个属性也进行覆写,否则覆写效果将不会生效。

2.使用include最常见的问题就是findViewById查找不到目标控件,这个问题出现的前提是在include时设置了id,而在findViewById时却用了被include进来的布局的根元素id。例如上述例子中,include时设置了该布局的id为my_title_ly,而my_title_layout.xml中的根视图的id为my_title_parent_id。此时如果通过findViewById来找my_title_parent_id这个控件,然后再查找my_title_parent_id下的子控件则会抛出空指针。代码如下 :

View titleView = findViewById(R.id.my_title_parent_id) ;  
// 此时 titleView 为空,找不到。此时空指针
 TextView titleTextView = (TextView)titleView.findViewById(R.id.title_tv) ;  
titleTextView.setText("new Title");  

正确的方式:

// 使用include时设置的id,即R.id.my_title_ly
View titleView = findViewById(R.id.my_title_ly) ;  
// 通过titleView找子控件
TextView titleTextView = (TextView)titleView.findViewById(R.id.title_tv) ;  
titleTextView.setText("new Title");  

或者更简单的直接查找它的子控件:

TextView titleTextView = (TextView)findViewById(R.id.title_tv) ;  
titleTextView.setText("new Title");

通过看源码出现这样的原因是:解析过程中会首先判断include标签的id如果不是View.NO_ID的话会把该id设置给被引入的布局根元素的id,即此时在我们的例子中被引入的id为my_title_parent_id的根元素RelativeLayout的id被设置成了include标签中的id,即RelativeLayout的id被动态修改成了”my_title_ly”。因此此时我们再通过“my_title_parent_id”这个id来查找根元素就会找不到了! 
所以结论就是: 如果include中设置了id,那么就通过include的id来查找被include布局根元素的View;如果include中没有设置Id, 而被include的布局的根元素设置了id,那么通过该根元素的id来查找该view即可。拿到根元素后查找其子控件都是一样的。

2.)Merge标签

<merge>标签是作为<include>标签的一种辅助扩展来使用的,它的主要作用是为了防止在引用布局文件时产生多余的布局嵌套。大家都知道,Android去解析和展示一个布局是需要消耗时间的,布局嵌套的越多,那么解析起来就越耗时,性能也就越差,因此我们在编写布局文件时应该让嵌套的层数越少越好。

比如我们写一个公共的确定和取消按钮ok_cancel_layout.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:orientation="vertical" >  
  
    <Button  
        android:id="@+id/ok"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_marginLeft="20dp"  
        android:layout_marginRight="20dp"  
        android:text="OK" />  
  
    <Button  
        android:id="@+id/cancel"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_marginLeft="20dp"  
        android:layout_marginRight="20dp"  
        android:layout_marginTop="10dp"  
        android:text="Cancel" />  
  
</LinearLayout>  

然后再另一个界面main.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="match_parent"  
    android:orientation="vertical" >  
      
    <EditText  
        android:id="@+id/edit"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_marginBottom="10dp"  
        android:layout_marginLeft="20dp"  
        android:layout_marginRight="20dp"  
        android:layout_marginTop="10dp"  
        android:hint="Edit something here" />  
      
    <include 
    layout="@layout/ok_cancel_layout"/>  
  
</LinearLayout>  

目前main.xml这个界面当中其实已经存在着多余的布局嵌套了!感觉还没写几行代码呢,怎么这就已经有多余的布局嵌套了?不信的话我们可以通过View Hierarchy工具来查看一下,如下图所示:

相信大家已经可以看出来了吧,这个内部的LinearLayout就是一个多余的布局嵌套,实际上并不需要这样一层,让两个按钮直接包含在外部的LinearLayout当中就可以了。而这个多余的布局嵌套其实就是由于布局引入所导致的,那么应该怎样优化掉这个问题呢?当然就是使用<merge>标签来完成了,修改ok_cancel_layout.xml中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>  
<merge xmlns:android="http://schemas.android.com/apk/res/android">  
  
    <Button  
        android:id="@+id/ok"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_marginLeft="20dp"  
        android:layout_marginRight="20dp"  
        android:text="OK" />  
  
    <Button  
        android:id="@+id/cancel"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_marginLeft="20dp"  
        android:layout_marginRight="20dp"  
        android:layout_marginTop="10dp"  
        android:text="Cancel" />  
  
</merge>  

我们在通过View Hierarchy工具来查看一下,如下图所示:

可以看到,这里我们将ok_cancel_layout最外层的LinearLayout布局删除掉,换用了<merge>标签,这就表示当有任何一个地方去include这个布局时,会将<merge>标签内包含的内容直接填充到include的位置,不会再添加任何额外的布局结构.

让我意想不到的是Merge竟然是个activity,并且还动态的加载了LinearLayout对象.

/** 
 * Exercise <merge /> tag in XML files. 
 */  
public class Merge extends Activity {  
    private LinearLayout mLayout;  

    @Override  
    protected void onCreate(Bundle icicle) {  
        super.onCreate(icicle);  

        mLayout = new LinearLayout(this);  
        mLayout.setOrientation(LinearLayout.VERTICAL);  
        LayoutInflater.from(this).inflate(R.layout.merge_tag, mLayout);  

        setContentView(mLayout);  
    }  

    public ViewGroup getLayout() {  
        return mLayout;  
    }  
}  

通过看源码发现:在解析的过程中,如果是merge标签,那么第一步:首先获取merge标签的parent ,第二步:获取布局参数 ,第三部:递归解析每个子元素 ,第四步:将子元素直接添加到merge标签的parent view中 ,这样就保证了不会引入额外的层级。
3.)ViewStub标签

根据官方的解释大致可翻译成:其实ViewStub就是一个宽高都为0的一个View,它默认是不可见的,只有通过调用setVisibility函数或者Inflate函数才会将其要装载的目标布局给加载出来,从而达到延迟加载的效果,这个要被加载的布局通过android:layout属性来设置。

有的时候我们会遇到这样的场景,就是某个布局当中的元素非常多,但并不是所有元素都一起显示出来的,而是普通情况下只显示部分常用的元素,而那些不常用的元素只有在用户进行特定操作的情况下才会显示出来。

这里举个大家都非常熟悉的例子,我们在添加联系人的时候其实可以编辑的字段真的非常多,姓名、电话、email、传真、住址、昵称等等等等,但其实基本上大家最常用的就是填一个姓名,填一个电话而已。那么将这么多繁杂的字段都一起显示在界面上其实并不是一种很好的做法,因为大多数人都是用不到这些字段的。比较聪明的做法就是把最常用的姓名和电话显示在界面上,然后给用户提供一个添加更多字段的选项,当用户真的有需要去添加其它信息的时候,我们才将另外的元素显示到界面上。

说到实现这样一个功能,我相信大多数人的第一反应就是将不常用的元素使用INVISIBLE或者GONE进行隐藏,然后当用户需要使用这些元素的时候再把它们置成VISIBLE显示出来。使用这种方式肯定可以实现功能的,但是性能方面就表现得一般了,因为即使是将元素进行隐藏,它们其实还是在布局当中的,每个元素还拥有着自己的宽、高、背景等等属性,解析布局的时候也会将这些隐藏的元素一一解析出来。

那么我们如何才能让这些不常用的元素仅在需要时才去加载呢?Android为此提供了一种非常轻量级的控件,ViewStub。ViewStub虽说也是View的一种,但是它没有大小,没有绘制功能,也不参与布局,资源消耗非常低,将它放置在布局当中基本可以认为是完全不会影响性能的。

还是举例说明例子1:比如我们在添加三个EditText构成另一个布局edittext_extra_layout.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="match_parent"  
    android:orientation="vertical" >  
  
    <EditText  
        android:id="@+id/edit_extra1"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_marginLeft="20dp"  
        android:layout_marginRight="20dp"  
        android:hint="Extra field 1" />  
  
    <EditText  
        android:id="@+id/edit_extra2"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_marginLeft="20dp"  
        android:layout_marginRight="20dp"  
        android:layout_marginTop="10dp"  
        android:hint="Extra field 2" />  
  
    <EditText  
        android:id="@+id/edit_extra3"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_marginLeft="20dp"  
        android:layout_marginRight="20dp"  
        android:layout_marginTop="10dp"  
        android:hint="Extra field 3" />  
  
</LinearLayout>  

接下来我们修改main.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="match_parent"  
    android:orientation="vertical" >  
  
    <EditText  
        android:id="@+id/edit"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:layout_marginBottom="10dp"  
        android:layout_marginLeft="20dp"  
        android:layout_marginRight="20dp"  
        android:layout_marginTop="10dp"  
        android:hint="@string/edit_something_here" />  
  
    <Button  
        android:id="@+id/more"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_gravity="right"  
        android:layout_marginRight="20dp"  
        android:layout_marginBottom="10dp"  
        android:text="More" />  
      
    <ViewStub   
        android:id="@+id/view_stub"  
        android:layout="@layout/profile_extra"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        />  
  
    <include layout="@layout/ok_cancel_layout" />  
  
</LinearLayout>  

可以看到,这里我们新增了一个More Button,这个按钮就是用于去加载那些不常用的元素的,然后在Button的下面定义了一个ViewStub。在ViewStub控件中,我们先是通过id属性给它指定了一个唯一标识,又通过layout属性将profile_extra布局传入进来,接着给ViewStub指定了一个宽高。注意,虽然ViewStub是不占用任何空间的,但是每个布局都必须要指定layout_width和layout_height属性,否则运行就会报错。

private EditText editExtra1;  
private EditText editExtra2;  
private EditText editExtra3;  
  
public void onMoreClick() {  
    ViewStub viewStub = (ViewStub) findViewById(R.id.view_stub);  
    if (viewStub != null) {  
        View inflatedView = viewStub.inflate();  
        editExtra1 = (EditText) inflatedView.findViewById(R.id.edit_extra1);  
        editExtra2 = (EditText) inflatedView.findViewById(R.id.edit_extra2);  
        editExtra3 = (EditText) inflatedView.findViewById(R.id.edit_extra3);  
    }  
}  

调用inflate()方法之后会将加载出来的布局进行返回,之后我们就可以对这个布局进行任意的操作了,再次隐藏显示,或者获取子元素的实例等。注意这里我对ViewStub的实例进行了一个非空判断,这是因为ViewStub在XML中定义的id只在一开始有效,一旦ViewStub中指定的布局加载之后,这个id也就失败了,那么此时findViewById()得到的值也会是空。

例子2

<ViewStub  
    android:id="@+id/stub_import"  
    android:inflatedId="@+id/stub_comm_lv"  
    android:layout="@layout/my_comment_layout"  
    android:layout_width="fill_parent"  
    android:layout_height="wrap_content"  
    android:layout_gravity="bottom" /  
<?xml version="1.0" encoding="utf-8"?>  
<ListView xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:id="@+id/my_comm_lv"  
    android:layout_height="match_parent" >  

</ListView>  
public class MainActivity extends Activity {  

    public void onCreate(Bundle b){  
        // main.xml中包含上面的ViewStub  
        setContentView(R.layout.main);  

        // 方式1,获取ViewStub,  
        ViewStub listStub = (ViewStub) findViewById(R.id.stub_import);  
        // 加载评论列表布局  
        listStub.setVisibility(View.VISIBLE);  
        // 获取到评论ListView,注意这里是通过ViewStub的inflatedId来获取  
            ListView commLv = findViewById(R.id.stub_comm_lv);  
                if ( listStub.getVisibility() == View.VISIBLE ) {  
                       // 已经加载, 否则还没有加载  
                }  
            }  
       }  

通过setVisibility(View.VISIBILITY)来加载评论列表,此时你要获取到评论ListView对象的话,则需要通过findViewById来查找,而这个id并不是ViewStub的id。 
这是为什么呢 ?

通过看源码原因是:

1、加载目标布局

2、如果ViewStub的inflatedId不是NO_ID则把inflatedId设置为目标布局根元素的id,即评论ListView的id

3、将ViewStub自身从parent中移除
4、将目标布局的根元素添加到parent中

public class MainActivity extends Activity {  

    // 把commLv2设置为类的成员变量  
    ListView commLv2 = null;  
    //  
    public void onCreate(Bundle b){  
        // main.xml中包含上面的ViewStub  
        setContentView(R.layout.main);  

        // 方式二  
        ViewStub listStub2 = (ViewStub) findViewById(R.id.stub_import) ;  
        // 成员变量commLv2为空则代表未加载  
        if ( commLv2 == null ) {  
        // 加载评论列表布局, 并且获取评论ListView,inflate函数直接返回ListView对象  
          commLv2 = (ListView)listStub2.inflate();  
        } else {  
        // ViewStub已经加载  
        }  

    }  

}  
  1. 判断是否已经加载过, 如果通过setVisibility来加载,那么通过判断可见性即可;如果通过inflate()来加载是不可以通过判断可见性来处理的,而需要使用方式2来进行判断。
  2. findViewById的问题,注意ViewStub中是否设置了inflatedId,如果设置了则需要通过inflatedId来查找目标布局的根元素。

 

共有 人打赏支持
粉丝 5
博文 77
码字总数 149231
×
android-key
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: