android ConstraintLayout佈局從入門到放棄

佈局優化是性能優化的一個方向點,包括了根據需求應該選用哪一種佈局容器、ViewStub懶加載,如何減小布局層級等,今天咱們要探討的就是如何使用ConstraintLayout來優化咱們的佈局層級。html

概述.png

提出問題

  1. 爲何要用這個佈局?
  2. 怎麼用這個佈局?
  3. 不足在哪裏?

優點

ConstraintLayout就是爲性能而生,目標就是減小布局嵌套,提升measure+layout性能,來看看官方給出的數據。android

性能比較結果.jpeg

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

舊界面轉換爲ConstraintLayout

首先,AS支持一鍵將已有的佈局文件轉成ConstraintLayout,是否是很貼心,要作就作全套的。app

佈局轉換.png

用法

1. 屬性layout_constraintXXX_toYYYOf

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>
複製代碼

效果圖.png

能夠看到兩個控件水平方向的約束都指向parent也就是父控件ConstraintLayout,因爲兩邊拉力做用就成了居中的樣式,垂直方向上因爲第二個textview使用了約束app:layout_constraintTop_toBottomOf="@id/titleTextView",即它的頂部約束爲在第一個textview的下面,也就造成了效果圖的樣子。工具

2. match_constraint

因爲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>
複製代碼

效果圖2.png

能夠看到將第二個button的layout_width設置爲0dp後,它並無不顯示出來而是佔據的水平方向的剩餘空間。沒錯,在ConstraintLayout中寬高指定爲0dp表示的是在知足約束條件下的最大值,也即match_constraint。

3. 寬高比layout_constraintDimensionRatio

很少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>
複製代碼

效果圖3.png
能夠看到設置 app:layout_constraintDimensionRatio="2:1屬性表示寬高比爲2:1,在已經限定控件寬度爲300dp時,高度指定爲0dp則可本身算出實際高度。

4. radius角度約束

秒懂的屬性,直接來看例子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>
複製代碼

效果圖4.png

5. bias偏向拉力比例

示例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>
複製代碼

效果圖5.png

能夠看到雖然水平方向都有拉力,但設置了layout_constraintHorizontal_bias屬性後產生了偏移,屬性值從0到1,0爲最左側1爲最右側,須要注意的是該屬性只有水平方向上都有約束纔會生效。

6. 鏈chain

水平或垂直方向上相互約束的兩個或更多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>
複製代碼

效果圖

效果圖6.png

其中layout_constraintHorizontal_chainStyle屬性可設置一個約束鏈中view的分佈狀況。

chain.jpeg
本例結合 layout_constraintHorizontal_weight屬性實現了weight chain效果。

7. GuideLine參照線

爲方便的指定約束的參照對象,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>
複製代碼

效果圖7.png
能夠看到經過 layout_constraintGuide_begin指定具體偏移值,而經過 layout_constraintGuide_percent可指定偏移百分比。

8. 屬性goneMargin

當約束對象可見性設置爲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>
複製代碼

效果圖8.png

9. 其餘

  • layout_constrainedWidth 針對view寬高爲wrap_content時,控件的實際寬高超過了約束條件時應該如何顯示,默認是false,示例test_constraint_width.xml。
  • 控件Barrier-- 限定範圍,示例test_barrier.xml。
  • 控件Group--引用的組件可批量設置屬性,示例test_group.xml。
  • 控件PlaceHolder--佔位符,示例test_place_holder.xml。
  • 其餘綜合練習參照test_common.xml、test_video_constraint.xml。

注意事項

  • 沒有約束的layout_marginTop等屬性無效。

代碼佈局

實際開發過程當中不免須要使用代碼動態佈局,總體思路爲下面的代碼,具體示例參照ProgramActivity.class。

//1.建立約束條件
ConstraintSet set = new ConstraintSet();
//2.從一個ConstraintLayout中克隆出全部的約束條件。
set.clone(layout)
//3.約束條件和constraintLayout綁定
set.applyTo(constraintLayout);
複製代碼

缺點

  • xml文件沒有佈局層次,可讀性差
  • 代碼佈局複雜性高
  • 約束條件複雜時排查難度大

性能對比

下面是最重要的一趴,ConstraintLayout真的如官方宣傳的具有高性能嗎?咱們使用簡單佈局和複雜佈局兩種場景模擬實際開發狀況,使用的單元測試爲LayoutTest.class,獲得以下結果。

簡單佈局性能比較.png

簡單佈局表示佈局中僅有一層嵌套,上圖橫座標表示一次測量和佈局的時間,單位爲ms,縱軸表示直觀上這個簡單佈局使用哪一種傳統佈局更方便實現,好比咱們要實現一個縱向的簡單列表,顯然線性佈局更易實現。經過結果能夠看到ConstraintLayout的性能比傳統佈局的性能差至少一倍。

模擬器--複雜佈局性能比較.png

若是說簡單佈局耗時原本就極少參考意義不大,那複雜佈局呢?複雜佈局狀況下中使用傳統佈局會致使3-4層的嵌套,符合常見的開發場景,但性能方面仍優於ConstraintLayout,其中FrameLayout的性能表現最好。

結語

關於性能的評測國外的文章也不少,都有得出相似的結論,他們大都歸咎於隨着ConstrainLayout版本的升級,引入更多的功能而致使性能降低,但博主使用許多更低版本的庫測試,結果差異也不大,這就是爲何文章要起這個標題。若是性能沒跟上,那因爲ConstrainLayout許多缺點的存在,咱們徹底沒有理由選擇使用它,期待官方會作進一步優化工做。固然博主後續也會使用其餘工具(systrace等)進一步驗證此結論。

後記勘誤

以上評測使用的是模擬器,筆者也不解爲什麼會出現明顯與google大大結果不一致的狀況。考慮到真機與模擬器的差別性,筆者使用真機再次驗證發現結果與模擬器截然不同。測試過的真機Android版本爲4.4/5.0/6.0。

真機--複雜佈局性能比較.png

使用ConstraintLayout做爲根佈局比其餘佈局 性能至少提高25%!

至於緣由筆者暫時還不清楚,歡迎大神指點迷津。同時對以前評測的結果與得出的結論若有誤導已讀讀者表示十分抱歉。

另有一點遺漏:從1.1版本後官方還提供了一個實驗性的優化參數。

app:layout_optimizationLevel
複製代碼

官方文檔附上,小夥伴能夠嘗試。

experimental_optimizer.png

參考文章:

相關文章
相關標籤/搜索