Android一些你需要知道的布局优化技巧

转载请注明出处:http://blog.csdn.net/qq_17766199/article/details/52863741

今天分享一些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?效果是不是杠杠的!其实上面说了这么多,具体的情景使用还是要看项目的具体情况,在性能面前有些还是要取舍的,但千万不能为了优化而优化。优化也是不断积累的过程,不要指望立竿见影。愿大家都能写出一手漂亮的布局。最后觉得不错的点个赞哈!

                                    (function () {('pre.prettyprint code').each(function () { var lines = (this).text().split(\n).length;var numbering = $('
                                    • ').addClass('pre-numbering').hide(); (this).addClass(hasnumbering).parent().append( numbering); for (i = 1; i
                                    • 0
                                      点赞
                                    • 1
                                      收藏
                                      觉得还不错? 一键收藏
                                    • 0
                                      评论

                                    “相关推荐”对你有帮助么?

                                    • 非常没帮助
                                    • 没帮助
                                    • 一般
                                    • 有帮助
                                    • 非常有帮助
                                    提交
                                    评论
                                    添加红包

                                    请填写红包祝福语或标题

                                    红包个数最小为10个

                                    红包金额最低5元

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

                                    抵扣说明:

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

                                    余额充值