android之布局优化

标签(空格分隔): android 性能优化 xml


前期知识储备

1. LayoutInflate

  • 主要是用于加载布局的.其实setContentView()方法的内部也是使用LayoutInflater来加载布局的.只不过这部分源码是internal的,不太容易查看到。
  • LayoutInflater技术广泛应用于需要动态添加View的时候,比如在ScrollView和ListView中,经常都可以看到LayoutInflater的身影。
  • LayoutInflater其实就是使用Android提供的pull解析方式来解析布局文件的.
  • 在setContentView()方法中,Android会自动在布局文件的最外层再嵌套一个FrameLayout.
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {  
    synchronized (mConstructorArgs){ ... }

平时我们经常使用layout_width和layout_height来设置View的大小,就好像这两个属性确实是用于设置View的大小的。而实际上则不然,它们其实是用于设置View在布局中的大小的.也就是说,首先View必须存在于一个布局中,之后如果将layout_width设置成match_parent表示让View的宽度填充满布局,如果设置成wrap_content表示让View的宽度刚好可以包含其内容,如果设置成具体的数值则View的宽度会变成相应的数值。这也是为什么这两个属性叫作layout_width和layout_height,而不是width和height。

2.View绘制流程

每一个视图的绘制过程都必须经历三个最主要的阶段,即onMeasure()、onLayout()和onDraw()。

2.1 onMeasure()
  • 其内部调用View的measure()方法。
  • measure()方法接收两个参数,widthMeasureSpec和heightMeasureSpec,这两个值分别用于确定视图的宽度和高度的规格和大小。
  • measure()这个方法是final的,因此我们无法在子类中去重写这个方法,说明Android是不允许我们改变View的measure框架的。
  • MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。
    说明父视图会在一定程度上决定子视图的大小。
  • 根视图总是会充满全屏的。
  • 一个界面的展示可能会涉及到很多次的measure,因为一个布局中一般都会包含多个子视图,每个视图都需要经历一次measure过程。ViewGroup中定义了一个measureChildren()方法来去测量子视图的大小。
  • onMeasure()方法是可以重写的,也就是说,如果你不想使用系统默认的测量方式,可以按照自己的意愿进行定制,比如:
public class MyView extends View {  
    ......  
    @Override  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
        setMeasuredDimension(200, 200);  
    }  
} 

这样的话就把View默认的测量流程覆盖掉了,不管在布局文件中定义MyView这个视图的大小是多少,最终在界面上显示的大小都将会是200*200。
需要注意的是,在setMeasuredDimension()方法调用之后,我们才能使用getMeasuredWidth()和getMeasuredHeight()来获取视图测量出的宽高,以此之前调用这两个方法得到的值都会是0。
由此可见,视图大小的控制是由父视图、布局文件、以及视图本身共同完成的,父视图会提供给子视图参考的大小,而开发人员可以在XML文件中指定视图的大小,然后视图本身会对最终的大小进行拍板。

2.2 onLayout()
  • 这个方法是用于给视图进行布局的,也就是确定视图的位置.
  • getWidth()方法和getMeasureWidth()方法到底有什么区别呢?首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。另外,getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。
2.3 onDraw()
  • 对视图进行绘制。
  • View是不会帮我们绘制内容部分的,因此需要每个视图根据想要展示的内容来自行绘制.
  • 重写onDraw(),绘制的方式主要是借助Canvas这个类,它会作为参数传入到onDraw()方法中,供给每个视图使用。

3.view的机制

3.1 view的状态
  • enabled
  • focused
  • selected
  • window_focused
  • pressed
3.2 视图重绘
  • invalidate()
  • 调用视图的invalidate()方法后确实会走到performTraversals()方法中,然后重新执行绘制流程。
  • invalidate()方法虽然最终会调用到performTraversals()方法中,但这时measure和layout流程是不会重新执行的,因为视图没有强制重新测量的标志位,而且大小也没有发生过变化,所以这时只有draw流程可以得到执行。而如果你希望视图的绘制流程可以完完整整地重新走一遍,就不能使用invalidate()方法,而应该调用requestLayout()了。这个方法中的流程比invalidate()方法要简单一些,但中心思想是差不多的,这里也就不再详细进行分析了。

4. 自定义view

4.1 完全自定义View控件
4.2 组合控件
4.3 继承控件

布局优化方法

1.布局标签灵活使用(详细使用方法和场景参考底部blog)

  • include
  • merge
  • viewstub

2.布局调优工具

  • hierarchy viewer
  • lint

优化建议

  • 去除不必要的嵌套和View节点
  • 减少不必要的infalte
  • 使用RenderScript
  • 使用OpenGL绘图
  • 尽量为所有分辨率创建资源-减少不必要的硬件缩放,这会降低UI的绘制速度,可借助Android asset studio。
  • 尽量多使用RelativeLayout和LinearLayout,在布局层次一样的情况下, 建议使用LinearLayout代替RelativeLayout, 因为LinearLayout性能要稍高一点,但往往RelativeLayout可以简单实现LinearLayout嵌套才能实现的布局。

需要注意的地方

  • 所有xml界面在通过setContentView()时,会把FrameLayout作为根节点插入。(思考:将setContentView()注释后,运行App,可正常显示,一块白色区域而已,界面渲染前状态,这块白色区域是什么?),因此,当xml中的根节点为FrameLayout时,并且没有其它特殊属性,可去掉,再用《merge》 来包裹。
  • 一些blog上说多使用RelativeLayout,并不是因为它性能高,而是可避免使用复杂的层级关系。
  • 使用include最常见的问题就是findViewById查找不到目标控件,如下,先设置include标签的id,编码中先找到include的id,在基于其找子id。
View titleView = findViewById(R.id.my_title_ly) ;  
TextView titleTextView = (TextView)titleView.findViewById(R.id.title_tv) ;  
titleTextView.setText("new Title");  
参考文章:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值