本篇文章已受權微信公衆號 guolin_blog (郭霖)獨家發佈java
ConstraintLayout在 1.0 的時候提供了 GuideLine 輔助佈局,在 1.1 時提供了 Group 和 Barrier,在 2.0 時候提供了Layer以及放開了限制,開發者能夠自定義 Helper 了。android
Group能夠用來控制一組view的可見性git
<android.support.constraint.Group android:id="@+id/group" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="visible" app:constraint_referenced_ids="button4,button9" /> 複製代碼
能夠經過控制 group 的 hide/show 來直接控制一組 view(button4,button9) 的可見性。github
來看一個場景,下面是一個表單,Email 和 Password 左對齊 中間的虛線爲 GuideLine,具體字段都和GuideLine左對齊。微信
如今若是須要作多語言,翻譯爲德文後變成了下面的效果markdown
這時候就須要Barrier出場了,Barrier是柵欄的意思,能夠理解爲擋着不讓超過。app
改進方法ide
這樣 Email 和 Password就不會超出Barrier,大體代碼以下(有刪減,完整代碼參考這裏)函數
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout> <TextView android:id="@+id/tv_email" app:layout_constraintBottom_toTopOf="@+id/tv_password" app:layout_constraintStart_toStartOf="@+id/tv_password" app:layout_constraintTop_toTopOf="parent" tools:text="E-mail Addresse" /> <EditText android:id="@+id/et_email" android:text="me@gmail.com" app:layout_constraintBaseline_toBaselineOf="@+id/tv_email" app:layout_constraintStart_toEndOf="@+id/barrier" /> <TextView android:id="@+id/tv_password" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@+id/tv_email" /> <EditText android:id="@+id/et_password" android:inputType="textPassword" android:text="2321321" app:layout_constraintBaseline_toBaselineOf="@+id/tv_password" app:layout_constraintStart_toEndOf="@+id/barrier" /> <android.support.constraint.Barrier android:id="@+id/barrier" app:barrierDirection="right" app:constraint_referenced_ids="tv_email,tv_password" /> </android.support.constraint.ConstraintLayout> 複製代碼
Layer 能夠看做是它引用的 view 的邊界(能夠理解爲包含這些 view 的一個 ViewGroup,可是Layer並非ViewGroup,Layer並不會增長 view 的層級)。另外Layer支持對裏面的 view 一塊兒作變換。oop
考慮這麼一個場景,若是一個頁面裏面有部分 view 須要加個背景,使用Layer引用這幾個 view,而後給Layer設置背景就能夠了。若是不用Layer,只能另外加個 ViewGroup 包住這幾個 View 了,這樣會增長 view 的層級,不利於性能。
看一個示例(完整代碼):
圖中Layer包住了中間的 6 個按鈕,綠色邊線白色填充是經過 Layer設置背景完成的。另外對Layer裏面的全部按鈕一塊兒作動畫,出來的效果就是這樣子
ConstraintLayout2.0 除了提供幾個默認實現的ConstraintHelper外,還提供開發者自定義ConstraintHelper的方式。
爲何須要自定義?
如何自定義?
對一張圖片做出CircularReveal的效果 ViewAnimationUtils給咱們提供了createCircularReveal這個函數
public static Animator createCircularReveal(View view, int centerX, int centerY, float startRadius, float endRadius) 複製代碼
藉助這個函數只須要計算出中心點(centerX,centerY)和 endRadius(半徑)就能夠很方便實現CircularReveal的效果
class CircularRevealHelper @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : ConstraintHelper(context, attrs, defStyleAttr) { override fun updatePostLayout(container: ConstraintLayout) { super.updatePostLayout(container) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { val views = getViews(container) for (view in views) { val anim = ViewAnimationUtils.createCircularReveal(view, view.width / 2, view.height / 2, 0f, Math.hypot((view.height / 2).toDouble(), (view.width / 2).toDouble()).toFloat()) anim.duration = 3000 anim.start() } } } } 複製代碼
updatePostLayout會在 onLayout 以後調用,在這裏作動畫就能夠。
有了CircularRevealHelper以後能夠直接在 xml 裏面使用,在CircularRevealHelper的constraint_referenced_ids裏面指定須要作動畫 view。
<?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"> <ImageView android:id="@+id/img_mario" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:srcCompat="@drawable/mario" /> <cn.feng.constraintLayout2.helps.CircularRevealHelper android:id="@+id/helper" android:layout_width="wrap_content" android:layout_height="wrap_content" app:constraint_referenced_ids="img_mario" tools:ignore="MissingConstraints" /> </android.support.constraint.ConstraintLayout> 複製代碼
後面若是要對 view 作CircularReveal直接在 xml 裏面指定就能夠了,作到了很好的複用。
再來看看這個 Flyin 的飛入效果,view 從四周飛入到各自的位置。
這個動畫的關鍵在於計算出每一個 view 該從什麼方向飛入。
紅色邊框的位置能夠藉助前面介紹的的Layer找到(固然也能夠不借助Layer,本身算,稍顯複雜),從而計算出紅色框框部分的中間點位置, 再和圖中每一個 view 的中間點比較(圖中每一個白點的位置)從而得出每一個 view 該從哪一個方向飛入。
計算每一個view 的初始位置代碼以下,藉助上面的圖形應該很好理解。
for (view in views) { val viewCenterX = (view.left + view.right) / 2 val viewCenterY = (view.top + view.bottom) / 2 val startTranslationX = if (viewCenterX < centerPoint.x) -2000f else 2000f val startTranslationY = if (viewCenterY < centerPoint.y) -2000f else 2000f view.translationX = (1 - animatedFraction) * startTranslationX view.translationY = (1 - animatedFraction) * startTranslationY } 複製代碼
FlyinHelper 的完整代碼參考這裏
每一個 view 不但能夠接受一個ConstraintHelper,還能夠同時接受多個ConstraintHelper。
左邊的四個 ImageView 和右下的 FloatingActionButton 都有 Flyin 的效果,同時左邊的四個ImageView還在繞 Y 軸作 3D 旋轉。上方的 Seekbar的背景在作CircularReveal的效果。有了前面編寫的CircularRevealHelper以及 FlyInHelper 咱們能夠很方便作到這樣的效果。
Flow 是 VirtualLayout,Flow 能夠像 Chain 那樣幫助快速橫向/縱向佈局constraint_referenced_ids裏面的元素。 經過flow_wrapMode能夠指定具體的排列方式,有三種模式
下面看下如何實現這個計算器佈局:
<?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=".activity.MainActivity"> <android.support.constraint.helper.Flow android:id="@+id/flow" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#FFC107" android:padding="20dp" app:constraint_referenced_ids="tv_num_7,tv_num_8,tv_num_9,tv_num_4,tv_num_5,tv_num_6,tv_num_1,tv_num_2,tv_num_3,tv_num_0,tv_operator_div,tv_dot,tv_operator_times" app:flow_horizontalGap="10dp" app:flow_maxElementsWrap="3" app:flow_verticalGap="10dp" app:flow_wrapMode="aligned" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> <TextView android:id="@+id/tv_num_7" style="@style/text_view_style" android:text="7" /> <TextView android:id="@+id/tv_num_8" style="@style/text_view_style" android:text="8" /> <TextView android:id="@+id/tv_num_9" style="@style/text_view_style" android:text="9" /> <TextView android:id="@+id/tv_num_4" style="@style/text_view_style" android:text="4" /> <TextView android:id="@+id/tv_num_5" style="@style/text_view_style" android:text="5" /> <TextView android:id="@+id/tv_num_6" style="@style/text_view_style" android:text="6" /> <TextView android:id="@+id/tv_num_1" style="@style/text_view_style" android:text="1" /> <TextView android:id="@+id/tv_num_2" style="@style/text_view_style" android:text="2" /> <TextView android:id="@+id/tv_num_3" style="@style/text_view_style" android:text="3" /> <TextView android:id="@+id/tv_num_0" style="@style/text_view_style" android:text="0" /> <TextView android:id="@+id/tv_operator_div" style="@style/text_view_style" android:text="/" tools:layout_editor_absoluteX="156dp" tools:layout_editor_absoluteY="501dp" /> <TextView android:id="@+id/tv_operator_times" style="@style/text_view_style" android:text="*" /> <TextView android:id="@+id/tv_dot" style="@style/text_view_style" android:text="." tools:layout_editor_absoluteX="278dp" tools:layout_editor_absoluteY="501dp" /> <TextView android:id="@+id/KE" android:layout_width="0dp" android:layout_height="0dp" android:background="#00BCD4" android:gravity="center" android:text="Compute" android:textColor="@android:color/white" android:textSize="24sp" app:layout_constraintBottom_toBottomOf="@+id/tv_operator_times" app:layout_constraintEnd_toEndOf="@+id/tv_dot" app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toStartOf="@+id/tv_operator_div" app:layout_constraintTop_toTopOf="@+id/tv_operator_times" /> <TextView android:id="@+id/KR" android:layout_width="0dp" android:layout_height="0dp" android:background="#03A9F4" android:gravity="right|center_vertical" android:paddingEnd="16dp" android:text="0" android:textColor="@android:color/white" android:textSize="58sp" app:layout_constraintBottom_toTopOf="@+id/flow" app:layout_constraintEnd_toEndOf="@+id/flow" app:layout_constraintStart_toStartOf="@+id/flow" app:layout_constraintTop_toTopOf="parent" /> </android.support.constraint.ConstraintLayout> 複製代碼
藉助 flow 很快能夠佈局出來,這裏flow_wrapMode使用的是aligned,id 爲KE的TextView能夠對齊到 Flow 裏面的 view,id 爲KR的TextView能夠對齊到 Flow,另外 Flow 也是ConstraintHelper,因此Flow 也是個 View,能夠設置背景,padding等元素。 那麼這樣佈局有什麼優點? 這樣的佈局 view 都在一個層級,不使用 ViewGroup,減小層級。
1.1 以前須要這樣修改屬性
val set = ConstraintSet() set.clone(constraintLayout) set.setTranslationZ(R.id.image, 32f) set.setMargin(R.id.image, ConstraintSet.START, 43) set.applyTo(constraintLayout) 複製代碼
2.0 提供了ConstraintProperties 能夠使用流式 API 修改屬性
val properties = ConstraintProperties(findViewById(R.id.image)) properties.translationZ(32f) .margin(ConstraintSet.START, 43) .apply() 複製代碼
關於 MotionLayout 能夠參考ConstraintLayout開發者 Nicolas Roard 寫的系列文章,
Introduction to MotionLayout (part I)
Introduction to MotionLayout (part II)
Introduction to MotionLayout (part III)
Defining motion paths in MotionLayout
完整代碼參考 Github,喜歡的話 star 哦
參考資料
ConstraintLayout Deep Dive (Android Dev Summit '18)
ConstraintLayout 2.0 by Nicolas Roard and John Hoford, Google EN