ConstraintLayout教程

原文地址:https://www.raywenderlich.com/9475-constraintlayout-tutorial-for-android-complex-layouts

  1. 轉(zhuǎn)換其他類型的布局到ConstraintLayout
  2. 相對于在屏幕上的其他元素動態(tài)定位UI元素
  3. 讓你的Views實現(xiàn)動畫

在這篇教程中,會構(gòu)建一個星際旅行的app,火箭將會繞著行星不停的轉(zhuǎn)動。

星際旅行

在這個app中有很多元素,你能夠?qū)W到如何使用復(fù)雜的ConstraintLayout來正常的顯示他們。

轉(zhuǎn)換一個布局到ConstraintLayout

Component Tree面板中,在最頂層的布局中右鍵并且選擇Convert LinearLayout to ConstraintLayout

image

接著會看到一個彈出框并且有一些選項:


image

閱讀完所有內(nèi)容后不改變默認(rèn)的選中狀態(tài),然后點擊ok關(guān)閉對話框,AndroidStudio接下來就會把你的布局轉(zhuǎn)換成ConstraintLayout。
轉(zhuǎn)換過后,你的布局就變成下面的這個樣子:

image

如果此時所有的視圖都跑到了左上角,請不要驚慌,

請確保關(guān)閉AutoConnect

image

移除推斷約束

執(zhí)行轉(zhuǎn)換的過程中,AndroidStudio會執(zhí)行好幾個步驟,最后一步就是進(jìn)行推斷約束,但是結(jié)果并不是你想要的,此時,你只需要轉(zhuǎn)到編輯菜單并選擇撤銷推斷約束。


image

或者,執(zhí)行cmd+Z,現(xiàn)在你的界面看起來如下:


image

你可以稍微拖動視圖,讓他看起來更像原始的樣子:


image

如果在拖動過程中Android Studio自動添加了任何的約束,可以點擊Clear All Constraints按鈕去清除他們。

image

調(diào)整Image

通過點擊頂部的每個圖標(biāo),spaceStationIcon,flightIconroverIcon來修復(fù)圖像的大小,接著在屬性面板,將layout_widthlayout_heigthwrap_content改為30dp。

image

此時你將會在Comoonent Tree中看到一堆錯誤,這是因為沒有任何約束信息告訴它在哪里定位,現(xiàn)在開始解決這個問題。

添加約束:找出對齊方式

使用自上而下的方法設(shè)置約束,從屏幕頂部的元素開始,一直向下設(shè)置。

希望頂部三個圖標(biāo)水平分布排列,然后標(biāo)簽置于圖標(biāo)下方

約束第一個圖標(biāo)

單擊第一個圖標(biāo)并且顯示約束錨點,點擊上面的錨點并且拖動到頂部的view,該圖標(biāo)就會自動滑動到頂部。先不要連接左側(cè)的約束、

接著,切換到Code界面檢查第一個圖標(biāo)xml的更新,發(fā)現(xiàn)添加了一個新的約束app:layout_constraintTop_toTopOf="parent",XML看起來如下:

<ImageView
  android:id="@+id/spaceStationIcon"
  android:layout_width="30dp"
  android:layout_height="30dp"
  android:layout_marginTop="15dp"
  android:src="@drawable/space_station_icon"
  app:layout_constraintTop_toTopOf="parent" />

你也可以在設(shè)計視圖中調(diào)整邊距,切換到設(shè)計視圖點擊Attributestab。

接著點擊第一個圖標(biāo)并且查看屬性,你會看到margins的圖形表示。
你可以通過從下拉菜單中選擇邊距或單擊數(shù)字并輸入新值來為邊距選擇新值。


image

水平對齊頂部的三個圖標(biāo):使用Chains

接著,希望頂部的三個圖標(biāo)在同一條水平線上并且平均分布,需要為每一個圖標(biāo)添加一系列的約束,這里有個更快速的方法就是使用chains。

Chains

如果有雙向約束,就會出現(xiàn)鏈條,當(dāng)你使用了菜單中的對齊約束時,Android Studio實際上就使用了鏈條,你可以將不同的樣式、權(quán)重、邊距應(yīng)用到鏈條.

接著切換到設(shè)計面板,同時選中頂部的三個圖標(biāo),右鍵然后選擇Center -> Horizontally,此時會自動創(chuàng)建一個鏈條并且生成約束。

在設(shè)計面板你就能看到鏈與其他約束的不同,其他的是波浪線表示,而鏈條是一條鏈。

image

探索鏈條

要探索某些鏈的模式,選擇一個元素,單擊圖標(biāo)底部顯示的循環(huán)鏈模式按鈕。

image

模式有:

  1. Packed:元素會被壓縮到一起
  2. Spread:如上所示,元素被分布到可用空間上
  3. Spread inside: 與spread類似,但鏈的端點不會分散。
image

確保以spread為鏈的模式,修改方法有兩種:

  1. 視圖將以實力屏幕截圖中的圖標(biāo)間隔顯示
  2. 在其中一個圖標(biāo)的xml中將會出現(xiàn)app:layout_constraintHorizontal_chainStyle="spread",更新該屬性,可以將鏈的模式改變成其他的。

對齊Views

接著,再次選中三個圖標(biāo),從tool bar中,選擇Align -> Vertical Centers.Android Studio會添加約束用來使每一個view的底部和頂部與相鄰的view對齊。
布局看起來如下:

image

此時三個圖標(biāo)的xml如下所示:

<ImageView
  android:id="@+id/spaceStationIcon"
  android:layout_width="30dp"
  android:layout_height="30dp"
  android:layout_marginTop="15dp"
  android:src="@drawable/space_station_icon"
  app:layout_constraintEnd_toStartOf="@+id/flightsIcon"
  app:layout_constraintHorizontal_chainStyle="spread"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintTop_toTopOf="parent" />

<ImageView
  android:id="@+id/flightsIcon"
  android:layout_width="30dp"
  android:layout_height="30dp"
  android:src="@drawable/rocket_icon"
  app:layout_constraintBottom_toBottomOf="@+id/spaceStationIcon"
  app:layout_constraintEnd_toStartOf="@+id/roverIcon"
  app:layout_constraintStart_toEndOf="@+id/spaceStationIcon"
  app:layout_constraintTop_toTopOf="@+id/spaceStationIcon" />

<ImageView
  android:id="@+id/roverIcon"
  android:layout_width="30dp"
  android:layout_height="30dp"
  android:src="@drawable/rover_icon"
  app:layout_constraintBottom_toBottomOf="@+id/flightsIcon"
  app:layout_constraintEnd_toEndOf="parent"
  app:layout_constraintStart_toEndOf="@+id/flightsIcon"
  app:layout_constraintTop_toTopOf="@+id/flightsIcon" />

如果你的界面跟圖片不匹配,檢查Text和Design面板,就重新再做一次。

對齊每個圖標(biāo)的文本

接著設(shè)置圖標(biāo)下面的文字,將第一個Text的左側(cè)的約束連接到第一個圖標(biāo)的左側(cè),右側(cè)的約束連接到右側(cè),上面的約束添加到上面,其他三個Text做同樣的操作。
然后將工具欄中的默認(rèn)邊距更改為15dp,只需要將頂部錨點拖動到圖標(biāo)的底部錨點,即可在一個步驟中設(shè)置約束和邊距。


image

現(xiàn)在上面兩行的約束錯誤已經(jīng)消失了,XML中圖標(biāo)和標(biāo)簽的代碼如下:

<TextView
  android:id="@+id/roverLabel"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_marginTop="15dp"
  android:text="@string/rovers"
  app:layout_constraintEnd_toEndOf="@+id/roverIcon"
  app:layout_constraintStart_toStartOf="@+id/roverIcon"
  app:layout_constraintTop_toBottomOf="@+id/roverIcon" />

<TextView
  android:id="@+id/flightsLabel"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_marginTop="15dp"
  android:text="@string/flights"
  app:layout_constraintEnd_toEndOf="@+id/flightsIcon"
  app:layout_constraintStart_toStartOf="@+id/flightsIcon"
  app:layout_constraintTop_toBottomOf="@+id/flightsIcon" />

<TextView
  android:id="@+id/spaceStationLabel"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_marginTop="15dp"
  android:text="@string/space_stations"
  app:layout_constraintEnd_toEndOf="@+id/spaceStationIcon"
  app:layout_constraintStart_toStartOf="@+id/spaceStationIcon"
  app:layout_constraintTop_toBottomOf="@+id/spaceStationIcon" />

Using Guidelines

在最終的布局中,雙箭頭圖像應(yīng)居中并且與兩個綠色視圖重疊


image

設(shè)置水平和垂直的Guidelines

選中雙箭頭圖標(biāo)并且設(shè)置寬高為60dp,然后在該圖標(biāo)上點擊右鍵,選擇Center -> Horizontally in Parent.

image

為每一個綠色的TextView設(shè)置寬為124dp,高為98dp。

確保雙箭頭圖片在兩個綠色的TextView之上,將左側(cè)TextView的右側(cè)約束到雙箭頭圖片的右側(cè),并將右邊距設(shè)置為40dp。
同樣的將右側(cè)綠色的TextView的左側(cè)約束到雙箭頭圖標(biāo)的左側(cè),并將左側(cè)邊距設(shè)置為40dp。
最后,將兩個TextView的上下分別約束到雙箭頭圖標(biāo)的上下。
[圖片上傳失敗...(image-5a6638-1550557283059)]

最后,點擊Guidelines,選擇Add Horizontal Guideline

image

將會添加一條水平的虛線到布局中。

Conponent Tree中選中水平guideline,在Attributes 檢查器中,改變ID為guideline1,注意guideline屬性:layout_constraintGuide_beginlayout_constraintGuide_percent
對于水平的guideline1,設(shè)置layout_constraintGuide_begin為200dp

image

最后,添加一個豎直的guideline,設(shè)置id為guideline2并且設(shè)置layout_constraintGuide_percent為0.05,這將guideline2定位到距離屏幕左側(cè)為屏幕寬度5%的位置。

定位Guidelines

定位guideline使用下面三個屬性:

  1. layout_constraintGuide_begin: 從左側(cè)或其父級的頂部定位具有指定dp的guideline
  2. layout_constraintGuide_end: 從右側(cè)或其父級底部定位指定dp的guideline
  3. layout_constraintGuide_percent: 使用百分比來定位guideline

添加約束到Guidelines

現(xiàn)在guidelines已經(jīng)設(shè)置了,可以給他們添加一些約束。
首先,對于雙箭頭圖標(biāo):

  1. 將底部約束到水平guideline
  2. 設(shè)置底部邊距為40dp

對于開關(guān):

  1. 設(shè)置寬度為160dp
  2. 設(shè)置左側(cè)約束到垂直guideline
  3. 設(shè)置top約束到父布局
  4. 設(shè)置top margin為200dp

對于開關(guān)下面的標(biāo)簽:

  1. 設(shè)置左邊的約束到垂直guideline
  2. 設(shè)置頂部約束到開關(guān)的底部

對于galaxy icon

  1. 設(shè)置寬高為90dp
  2. 設(shè)置top約束到水平guideline
  3. 約束bottom到父布局的底部,這樣就會在水平guideline和底部之間居中
  4. 在父視圖中將其水平居中

對于rocket icon:

  1. 設(shè)置寬和高為30dp
  2. 約束rocket icon的上下和右到galaxy圖標(biāo)的上下和左邊

最后,對于DEPART按鈕:

  1. 將寬度從wrap_content修改為match_parent
  2. 將底部約束到parent的底部

此時,你已經(jīng)設(shè)置完了所有的約束,在Component Tree中也沒有任何錯誤了,布局此時看起來如下所示:

image

圓形位置約束

除了上面的,還可以使用距離和角度來約束UI元素。允許你將他們防止到一個圓上,其中一個元素位于圓的中心,另一個元素位于圓周上。


image

選擇rocket icon,并在代碼視圖中更新其代碼,代碼如下:

<ImageView
  android:id="@+id/rocketIcon"
  android:layout_width="30dp"
  android:layout_height="30dp"
  android:src="@drawable/rocket_icon"
  app:layout_constraintCircle="@id/galaxyIcon"
  app:layout_constraintCircleAngle="270"
  app:layout_constraintCircleRadius="100dp" />

第一個約束屬性layout_constraintCircle指示將位于圓心的UI元素的ID,另外兩個屬性表示角度和半徑。

讓UI元素在屏幕上動起來

約束集

使用ConstraintLayout,你可以設(shè)置幀動畫從而是你的views動起來,為此,你需要提供布局文件的副本,稱為ConstraintSet,ConstraintSet只需要包含給定ConstraintLayout中元素的約束,邊距以及填充。

如果你使用的是kotlin代碼,那么你可以直接將ConstraintSet應(yīng)用到你的ConstraintLayout。
要構(gòu)建動畫,你需要指定單個布局文件和ConstraintSet作為起始和結(jié)束關(guān)鍵幀,你也可以應(yīng)用過渡是動畫更有趣。

設(shè)置動畫的起始布局

在項目中復(fù)制布局文件并命名為keyframe1.xml,并將此布局設(shè)置為應(yīng)用程序的起始布局。
打開keyframe1.xml,將guideline1的layout_constraintGuide_begin屬性值從200dp改為0dp,這樣會移動guideline,限制在guideline中的元素,將會移除屏幕

接著將guideline2的layout_constraintGuide_percent屬性值從0.05修改成1,這會將指南移動到屏幕最右側(cè),從而受其約束的元素被移動到屏幕外。

接著修改MainActivity中的setContentView中的R.layout.activity_main為R.layout.keyframe1

動畫視圖

將MainActivity中的:

import kotlinx.android.synthetic.main.activity_main.*

改為:

import kotlinx.android.synthetic.main.keyframe1.*

可以讓你直接飲用UI中的id,而不用使用findViewById(),
接著,添加如下的代碼:

private val constraintSet1 = ConstraintSet()
private val constraintSet2 = ConstraintSet()

private var isOffscreen = true

Transition Manager

可以使用Transition Manager類來處理從一個keyframe到另一個的過渡,創(chuàng)建一個布局動畫,你只需要向Transition Manager提供要設(shè)置動畫的ConstraintSet,他將會處理其余的部分。

將如下的代碼添加到onCreate方法中:

constraintSet1.clone(constraintLayout) //1
constraintSet2.clone(this, R.layout.activity_main) //2

departButton.setOnClickListener { //3
  //apply the transition
  TransitionManager.beginDelayedTransition(constraintLayout) //4
  val constraint = if (!isOffscreen) constraintSet1 else constraintSet2
  isOffscreen = !isOffscreen
  constraint.applyTo(constraintLayout) //5
}

動畫視圖的界限

不僅可以通過影響其約束來更改屏幕上元素的位置,還可以改變其大小。
打開keyframe1.xml選擇galaxy icon,id為galaxyIcon,將高度從90dp改為10dp。

接著運(yùn)行app可以看到大小的改變。

image

使用自定義過渡使動畫更簡單

創(chuàng)建一個自定義動畫來替代默認(rèn)的動畫,可以自定義動畫的時長。
添加如下方法到MainActivity中。

override fun onEnterAnimationComplete() {
    super.onEnterAnimationComplete()
    constraintSet2.clone(this, R.layout.activity_main)

    val transition = AutoTransition()
    transition.duration = 1000
    TransitionManager.beginDelayedTransition(constraintLayout,transition)

    constraintSet2.applyTo(constraintLayout)
  }
  1. 動畫執(zhí)行過程中,Activity無法繪制任何內(nèi)容,onEnterAnimationComplete()方法表示動畫執(zhí)行完成,可以調(diào)用繪制代碼。
  2. 會將布局信息從最終布局拉入constraintSet2
  3. 創(chuàng)建一個自定義過渡,使用AutoTransition,首先淡出要消失的目標(biāo),然后移動并調(diào)整現(xiàn)有目標(biāo)的大小,最后淡出出現(xiàn)的目標(biāo)。
  4. 動畫執(zhí)行時長為1000毫秒
  5. 調(diào)用Transition Manager的beginDelayedTransition方法,但這次提供的是自定義過渡
  6. 應(yīng)用一個新的ConstraintSet到當(dāng)前消失的ConstraintLayout上。

效果如下:


image

使圓形約束動起來

要在火星周圍制作火箭動畫,必須改變兩個屬性:圓形約束的角度,他將火箭的位置移動到圓周,以及火箭的旋轉(zhuǎn)來完成動畫,你還可以檢查單向/往返開關(guān)值以確定火箭是飛行半圈還是一整圈。

替換DEPART button的點擊事件的代碼為如下的代碼:

departButton.setOnClickListener {
//            TransitionManager.beginDelayedTransition(constraintLayout)
//            val constraint = if (!isOffscreen) constraintSet1 else constraintSet2
//            isOffscreen = !isOffscreen
//            constraint.applyTo(constraintLayout)

            val layoutParams = rocketIcon.layoutParams as ConstraintLayout.LayoutParams
            val startAngle = layoutParams.circleAngle
            val endAngle = startAngle + (if (switch1.isChecked) 360 else 180)

            val anim = ValueAnimator.ofFloat(startAngle,endAngle)
            anim.addUpdateListener { valueAnimator ->
                val animatedValue = valueAnimator.animatedValue as Float
                val layoutParams = rocketIcon.layoutParams as ConstraintLayout.LayoutParams
                layoutParams.circleAngle = animatedValue
                rocketIcon.layoutParams = layoutParams
                
                rocketIcon.rotation = (animatedValue % 360 - 270)
            }
            
            anim.duration = if(switch1.isChecked) 2000 else 1000
            
            anim.interpolator = LinearInterpolator()
            anim.start()
        }
  1. 在動畫開始之前,將火箭的startAngle設(shè)置為火箭的當(dāng)前角度,依賴one way/ Round Trip切換,endAngle在startAngle的之上添加180或360
  2. 使用startAngle和endAngle創(chuàng)建一個ValueAnimator
  3. 在動畫監(jiān)聽器中,獲取動畫值并將其設(shè)置給rocket的layoutParams中的circleAngle屬性
  4. 用動畫值旋轉(zhuǎn)火箭,將使火箭飛的更加自然。
  5. 單向動畫需要一秒,而往返動畫需要2秒
  6. 使用LinearInterpolator,可以試試AnticipateOvershootInterpolator看看會發(fā)生什么!
image

最后

如果你比較喜歡view動畫,那么可以使用MotionEvent嘗試更多的動畫,https://youtu.be/S3FeIRKu_Z8?t=1275

這篇文章內(nèi)容真的挺豐富的,寫的很不錯,所以就簡單翻譯了一下,如果有朋友覺得哪些翻譯的不夠好的,歡迎給我評論,我會及時糾正。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容