LinearLayout容器中的组件一个挨一个排列,通过控制android:orientation属性,可控制各组件是横向排列还是纵向排列。
TableLayout继承自Linearout,本质上仍然是线性布局管理器。表格布局采用行、列的形式来管理UI组件,并不需要明确地声明包含多少行、多少列,而是通过添加TableRow、其他组件来控制表格的行数和列数。
帧布局或叫层布局,从屏幕左上角按照层次堆叠方式布局,后面的控件覆盖前面的控件。帧布局为每个加入其中的组件创建一个空白的区域(称为一帧),每个子组件占据一帧,这些帧会根据gravity属性执行自动对齐。
该布局在开发中设计地图经常用到,因为是按层次方式布局,我们需要实现层面显示的样式时就可以
采用这种布局方式,比如我们要实现一个类似百度地图的布局,我们移动的标志是在一个图层的上面。
相对布局可以让子控件相对于兄弟控件或父控件进行布局,可以设置子控件相对于兄弟控件或父控件进行上下左右对齐。
RelativeLayout能替换一些嵌套视图,当我们用LinearLayout来实现一个简单的布局但又使用了过多的嵌套时,就可以考虑使用RelativeLayout重新布局。
GridLayout把整个容器划分为rows × columns个网格,每个网格可以放置一个组件。提供了setRowCount(int)和setColumnCount(int)方法来控制该网格的行和列的数量。
ConstraintLayout则是使用约束的方式来指定各个控件的位置和关系的,它有点类似于RelativeLayout,但远比RelativeLayout要更强大。
a.非常适合使用可视化的方式来编写界面(不适合用XML书写)
b.有效地解决布局嵌套过多的问题(复杂的布局总会伴随着多层的嵌套,而嵌套越多,程序的性能也就越差)
a.引入
b.添加约束/删除约束
c.Inspector设置当前控件的所有属性,如文本内容、颜色、点击事件等等。
d.Guidelines
e.自动添加约束(Autoconnect、Inference)
详见:Android新特性介绍,ConstraintLayout完全解析
问题核心在于:当RelativeLayout和LinearLayout分别作为ViewGroup表达相同布局时谁的绘制过程更快一点。
通过网上的很多实验结果我们得之,两者绘制同样的界面时layout和draw的过程时间消耗相差无几,关键在于measure过程RelativeLayout比LinearLayout慢了一些。故从RelativeLayout和LinearLayout的onMeasure过程来探索耗时问题的根源。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//...
View[] views = mSortedHorizontalChildren;
int count = views.length;
for (int i = 0; i < count; i++) {
View child = views[i];
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
applyHorizontalSizeRules(params, myWidth);
measureChildHorizontal(child, params, myWidth, myHeight);
if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
offsetHorizontalAxis = true;
}
}
}
views = mSortedVerticalChildren;
count = views.length;
for (int i = 0; i < count; i++) {
View child = views[i];
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
applyVerticalSizeRules(params, myHeight);
measureChild(child, params, myWidth, myHeight);
if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
offsetVerticalAxis = true;
}
if (isWrapContentWidth) {
width = Math.max(width, params.mRight);
}
if (isWrapContentHeight) {
height = Math.max(height, params.mBottom);
}
if (child != ignore || verticalGravity) {
left = Math.min(left, params.mLeft - params.leftMargin);
top = Math.min(top, params.mTop - params.topMargin);
}
if (child != ignore || horizontalGravity) {
right = Math.max(right, params.mRight + params.rightMargin);
bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
}
}
}
//...
}
根据源码我们发现RelativeLayout会根据2次排列的结果对子View各做一次measure。首先RelativeLayout中子View的排列方式是基于彼此的依赖关系,而这个依赖关系可能和Xml布局中View的顺序不同,在确定每个子View的位置的时候,需要先给所有的子View排序一下。又因为RelativeLayout允许ViewB在横向上依赖ViewA,ViewA在纵向上依赖B。所以需要横向纵向分别进行一次排序测量。
同时需要注意的是View.measure()方法存在以下优化:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
...
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
}
即如果我们或者我们的子View没有要求强制刷新,而父View给子View传入的值也没有变化(也就是说子View的位置没变化),就不会做无谓的测量。RelativeLayout在onMeasure中做横向测量时,纵向的测量结果尚未完成,只好暂时使用myHeight传入子View系统。这样会导致在子View的高度和RelativeLayout的高度不相同时(设置了Margin),上述优化会失效,在View系统足够复杂时,效率问题就会很明显。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
//LinearLayout会先做一个简单横纵方向判断,我们选择纵向这种情况继续分析
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
//...
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
//... child为空、Gone以及分界线的情况略去
//累计权重
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
//计算
if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
//精确模式的情况下,子控件layout_height=0dp且weight大于0无法计算子控件的高度
//但是可以先把margin值合入到总值中,后面根据剩余空间及权值再重新计算对应的高度
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
} else {
if (lp.height == 0 && lp.weight > 0) {
//如果这个条件成立,就代表 heightMode不是精确测量以及wrap_conent模式
//也就是说布局是越小越好,你还想利用权值多分剩余空间是不可能的,只设为wrap_content模式
lp.height = LayoutParams.WRAP_CONTENT;
}
// 子控件测量
measureChildBeforeLayout(child, i, widthMeasureSpec,0, heightMeasureSpec,totalWeight== 0 ? mTotalLength :0);
//获取该子视图最终的高度,并将这个高度添加到mTotalLength中
final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
//...
}
源码中已经标注了一些注释,需要注意的是在每次对child测量完毕后,都会调用child.getMeasuredHeight()获取该子视图最终的高度,并将这个高度添加到mTotalLength中。但是getMeasuredHeight暂时避开了lp.weight>0且高度为0子View,因为后面会将把剩余高度按weight分配给相应的子View。因此可以得出以下结论:
(1)如果我们在LinearLayout中不使用weight属性,将只进行一次measure的过程。
(2)如果使用了weight属性,LinearLayout在第一次测量时获取所有子View的高度,之后再将剩余高度根据weight加到weight>0的子View上。
由此可见,weight属性对性能是有影响的。
1、RelativeLayout慢于LinearLayout是因为它会让子View调用2次measure过程,而LinearLayout只需一次,但是有weight属性存在时,LinearLayout也需要两次measure。
2、RelativeLayout的子View如果高度和RelativeLayout不同,会导致RelativeLayout在onMeasure()方法中做横向测量时,纵向的测量结果尚未完成,只好暂时使用自己的高度传入子View系统。而父View给子View传入的值也没有变化就不会做无谓的测量的优化会失效,解决办法就是可以使用padding代替margin以优化此问题。
3、在不响应层级深度的情况下,使用Linearlayout而不是RelativeLayout。
4、新建一个Android项目SDK会为我们自动生成的avtivity_main.xml布局文件,然后它的根节点默认是RelativeLayout?
DecorView的层级深度已知且固定的,上面一个标题栏,下面一个内容栏,采用RelativeLayout并不会降低层级深度,因此这种情况下使用LinearLayout效率更高。
5、作为顶级View的DecorView就是个垂直方向的LinearLayout,上面是标题栏,下面是内容栏,我们常用的setContentView()方法就是给内容栏设置布局?
为开发者默认新建RelativeLayout是希望开发者能采用尽量少的View层级,很多效果是需要多层LinearLayout的嵌套,这必然不如一层的RelativeLayout性能更好。因此我们应该尽量减少布局嵌套,减少层级结构,使用比如viewStub,include等技巧。可以进行较大的布局优化。
1、Android需要把XML布局文件转换成GPU能够识别并绘制的对象。这个操作是在DisplayList的帮助下完成的。DisplayList持有所有将要交给GPU绘制到屏幕上的数据信息。
2、CPU负责把UI组件计算成Polygons,Texture纹理,然后交给GPU进行栅格化渲染。
3、GPU进行栅格化渲染。(栅格化:把组件拆分到不同的像素上进行显示)
4、硬件展示在屏幕上。
需要注意的是:任何时候View中的绘制内容发生变化时,都会重新执行创建DisplayList,渲染DisplayList,更新到屏幕上等一系列操作。这个流程的表现性能取决于View的复杂程度,View的状态变化以及渲染管道的执行性能
Overdraw(过度绘制):描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次的UI结构里面,如果不可见的UI也在做绘制的操作,就会导致某些像素区域被绘制了多次,浪费大量的CPU以及GPU资源。(可以通过开发者选项,打开Show GPU Overdraw的选项,观察UI上的Overdraw情况)
所以我们需要尽量减少Overdraw。
减少层级,越简单越好,减少overdraw,就能更好的突出性能
在RelativeLayout和LinearLayout同时能够满足需求时,尽量使用RelativeLayout,这一点可以从我们MainActivity默认布局就可以看出,默认是RelativeLayout,因为可以通过扁平的RelativeLayout降低LinearLayout嵌套所产生布局树的层级。
Android提供了几种方便的布局管理器,大多数时候,你只需要这些布局的一部分基本特性去实现UI。 一般情况下用LinearLayout的时候总会比RelativeLayout多一个View的层级。而每次往应用里面增加一个View,或者增加一个布局管理器的时候,都会增加运行时对系统的消耗,因此这样就会导致界面初始化、布局、绘制的过程变慢。
同一个布局LinearLayout与RelativeLayout用Hierarchy View查看层级树:
LinearLayout
RelativeLayout
很明显的可以看出来RelativeLayout比LinearLayout少了一个层级,当然渲染的时间也是大大减少了
include标签常用于将布局中的公共部分提取出来,比如我们要在activity_main.xml中需要上述LinearLayout的数据,那么就可以直接include进去了。
merge标签是作为include标签的一种辅助扩展来使用,它的主要作用是为了防止在引用布局文件时产生多余的布局嵌套,Android渲染需要消耗时间,布局越复杂,性能就越差。如上述include标签引入了之前的LinearLayout之后导致了界面多了一个层级。这个时候用merge的话,就可以减少一个层级。
activity_main就可以直接include了
viewstub是view的子类。他是一个轻量级View, 隐藏的,没有尺寸的View。他可以用来在程序运行时简单的填充布局文件。
ConstraintLayout允许你在不适用任何嵌套的情况下创建大型而又复杂的布局。它与RelativeLayout非常相似,所有的view都依赖于兄弟控件和父控件的相对关系。但是,ConstraintLayout比RelativeLayout更加灵活。目前在AndroidStudio中使用也十分方便。
从Android Studio中启动Android Device Monitor: Tools -> Android -> Android Device Monitor. 使用方法很简单,如下图
依次点击菜单Tools>Android>Android Device Monitor,如下图:
或者直接点击菜单下面,问号旁边的图标,如下图:
启动Android Device Monitor成功之后,在新的的窗口中点击切换视图图标,选择Hierarchy Viewe,如下图: