布局优化是性能优化的一个方向点,包括了根据需求应该选用哪一种布局容器、ViewStub懒加载,如何减小布局层级等,今天咱们要探讨的就是如何使用ConstraintLayout来优化咱们的布局层级。html
ConstraintLayout就是为性能而生,目标就是减小布局嵌套,提升measure+layout性能,来看看官方给出的数据。android
ConstraintLayout 在测量/布局阶段的性能比 RelativeLayout大约高40%!而它使用的性能检测工具是Android 7.0(API 级别 24)中引入的 OnFrameMetricsAvailableListener。经过该类,你能够收集有关应用界面渲染的逐帧时间信息,进而比较分析不一样布局每次测量和布局操做所花费的时间。git
另外使用AS的图形化界面能够很是方便的完成布局操做,这里不赘述了文末有参考文章。github
引入最新版本constraint layout库性能优化
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
复制代码
文中示例github项目 github.com/wanderingGu…bash
首先,AS支持一键将已有的布局文件转成ConstraintLayout,是否是很贴心,要作就作全套的。app
xxx表示该控件在哪一个方向的约束,YYY表示该约束指向的控件的方向(我已经尝试说人话了...),它们的能够是 left/right/top/bottom/start/end的任意一种,包含了水平方向和垂直方向的约束,属性的值为目标控件的id,看个例子item1_constraint.xml。ide
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/titleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World1!"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/subtitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World2!"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/titleTextView" />
</android.support.constraint.ConstraintLayout>
复制代码
能够看到两个控件水平方向的约束都指向parent也就是父控件ConstraintLayout,因为两边拉力做用就成了居中的样式,垂直方向上因为第二个textview使用了约束app:layout_constraintTop_toBottomOf="@id/titleTextView"
,即它的顶部约束为在第一个textview的下面,也就造成了效果图的样子。工具
因为ConstraintLayout的设计就是不想出现层次嵌套,这致使传统布局里使用的match_parent也就不适用了。那如何实现match_parent的效果呢?来看一个例子test_match_constraint.xml。布局
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Button
android:id="@+id/bt1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="button1" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="button1"
app:layout_constraintLeft_toRightOf="@id/bt1"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/bt1"/>
</android.support.constraint.ConstraintLayout>
复制代码
能够看到将第二个button的layout_width设置为0dp后,它并无不显示出来而是占据的水平方向的剩余空间。没错,在ConstraintLayout中宽高指定为0dp表示的是在知足约束条件下的最大值,也即match_constraint。
很少BB直接看示例test_dimension_ratio.xml
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="@+id/banner"
android:layout_width="300dp"
android:layout_height="0dp"
android:text="banner"
android:gravity="center"
android:textSize="30sp"
android:background="@android:color/holo_blue_light"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintDimensionRatio="2:1"/>
</android.support.constraint.ConstraintLayout>
复制代码
app:layout_constraintDimensionRatio="2:1
属性表示宽高比为2:1,在已经限定控件宽度为300dp时,高度指定为0dp则可本身算出实际高度。
秒懂的属性,直接来看例子test_radius.xml
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮"
app:layout_constraintCircle="@id/fab_menu"
app:layout_constraintCircleRadius="100dp"
app:layout_constraintCircleAngle="45"/>
<Button
android:id="@+id/fab_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tint="#fff"
android:text="btn1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
复制代码
示例test_bias.xml
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:layout_width="50dp"
android:layout_height="wrap_content"
app:layout_constraintHorizontal_bias="0.8"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
</android.support.constraint.ConstraintLayout>
复制代码
能够看到虽然水平方向都有拉力,但设置了layout_constraintHorizontal_bias
属性后产生了偏移,属性值从0到1,0为最左侧1为最右侧,须要注意的是该属性只有水平方向上都有约束才会生效。
水平或垂直方向上相互约束的两个或更多view组成一个约束链,一般可用于实现底部导航样式,示例test_chain.xml
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Button
android:id="@+id/bt1"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintHorizontal_weight="2"
android:text="第一个"
android:textSize="30sp"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintRight_toLeftOf="@id/bt2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" />
<Button
android:id="@+id/bt2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="第二个"
android:textSize="30sp"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/bt1"
app:layout_constraintRight_toLeftOf="@id/bt3"/>
<Button
android:id="@+id/bt3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="第三个"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toRightOf="@id/bt2" />
</android.support.constraint.ConstraintLayout>
复制代码
效果图
其中layout_constraintHorizontal_chainStyle
属性可设置一个约束链中view的分布状况。
layout_constraintHorizontal_weight
属性实现了weight chain效果。
为方便的指定约束的参照对象,ConstraintLayout内部可经过GuideLine来实现,它不会显示在界面中。 示例test_guideline.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<android.support.constraint.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="100dp"/>
<Button
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="btn1"
android:textSize="30sp"
app:layout_constraintLeft_toRightOf="@+id/guideline"
app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
复制代码
layout_constraintGuide_begin
指定具体偏移值,而经过
layout_constraintGuide_percent
可指定偏移百分比。
当约束对象可见性设置为gone后生效,示例test_gone_margin.xml。
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Button
android:id="@+id/bnt1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
app:layout_constraintLeft_toLeftOf="parent"
android:visibility="gone"
android:text="bnt1"/>
<Button
android:id="@+id/bnt2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
app:layout_constraintLeft_toRightOf="@id/bnt1"
app:layout_goneMarginLeft="200dp"
android:visibility="visible"
android:text="bnt2"/>
</android.support.constraint.ConstraintLayout>
复制代码
实际开发过程当中不免须要使用代码动态布局,总体思路为下面的代码,具体示例参照ProgramActivity.class。
//1.建立约束条件
ConstraintSet set = new ConstraintSet();
//2.从一个ConstraintLayout中克隆出全部的约束条件。
set.clone(layout)
//3.约束条件和constraintLayout绑定
set.applyTo(constraintLayout);
复制代码
下面是最重要的一趴,ConstraintLayout真的如官方宣传的具有高性能吗?咱们使用简单布局和复杂布局两种场景模拟实际开发状况,使用的单元测试为LayoutTest.class,获得以下结果。
简单布局表示布局中仅有一层嵌套,上图横坐标表示一次测量和布局的时间,单位为ms,纵轴表示直观上这个简单布局使用哪一种传统布局更方便实现,好比咱们要实现一个纵向的简单列表,显然线性布局更易实现。经过结果能够看到ConstraintLayout的性能比传统布局的性能差至少一倍。
若是说简单布局耗时原本就极少参考意义不大,那复杂布局呢?复杂布局状况下中使用传统布局会致使3-4层的嵌套,符合常见的开发场景,但性能方面仍优于ConstraintLayout,其中FrameLayout的性能表现最好。
关于性能的评测国外的文章也不少,都有得出相似的结论,他们大都归咎于随着ConstrainLayout版本的升级,引入更多的功能而致使性能降低,但博主使用许多更低版本的库测试,结果差异也不大,这就是为何文章要起这个标题。若是性能没跟上,那因为ConstrainLayout许多缺点的存在,咱们彻底没有理由选择使用它,期待官方会作进一步优化工做。固然博主后续也会使用其余工具(systrace等)进一步验证此结论。
以上评测使用的是模拟器,笔者也不解为什么会出现明显与google大大结果不一致的状况。考虑到真机与模拟器的差别性,笔者使用真机再次验证发现结果与模拟器截然不同。测试过的真机Android版本为4.4/5.0/6.0。
使用ConstraintLayout做为根布局比其余布局 性能至少提高25%!
至于缘由笔者暂时还不清楚,欢迎大神指点迷津。同时对以前评测的结果与得出的结论若有误导已读读者表示十分抱歉。
另有一点遗漏:从1.1版本后官方还提供了一个实验性的优化参数。
app:layout_optimizationLevel
复制代码
官方文档附上,小伙伴能够尝试。