ConstraintLayout 2.0 新特性詳解及實戰

本篇文章已受權微信公衆號 guolin_blog (郭霖)獨家發佈java

ConstraintHelper

ConstraintLayout在 1.0 的時候提供了 GuideLine 輔助佈局,在 1.1 時提供了 Group 和 Barrier,在 2.0 時候提供了Layer以及放開了限制,開發者能夠自定義 Helper 了。android

Group (Added in 1.1)

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

Barrier (Added in 1.1)

來看一個場景,下面是一個表單,Email 和 Password 左對齊 中間的虛線爲 GuideLine,具體字段都和GuideLine左對齊。微信

如今若是須要作多語言,翻譯爲德文後變成了下面的效果markdown

這時候就須要Barrier出場了,Barrier是柵欄的意思,能夠理解爲擋着不讓超過。app

改進方法ide

  • 把中間的虛線GuideLine換成Barrier
  • 把①和②加入到Barrier的referenced_ids中
  • 指定barrierDirection爲right(右側不超過)
  • 把③和④左邊對齊到Barrier的右邊

這樣 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 (Added in 2.0)

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的方式。

自定義 Helper

爲何須要自定義?

  • 保持 view 的層級不變,不像 ViewGroup 會增長 view 的層級
  • 封裝一些特定的行爲,方便複用
  • 一個 View 能夠被多個 Helper引用,能夠很方便組合出一些複雜的效果出來

如何自定義?

  • Helper持有 view 的引用,因此能夠獲取 view (getViews)而後操做 view
  • 提供了 onLayout 先後的 callback(updatePreLayout/updatePreLayout)
  • Helper 繼承了 view,因此Helper自己也是 view

CircularRevealHelper

對一張圖片做出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 裏面指定就能夠了,作到了很好的複用。

FlyinHelper

再來看看這個 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 的完整代碼參考這裏

ComposeMultipleHelper

每一個 view 不但能夠接受一個ConstraintHelper,還能夠同時接受多個ConstraintHelper。

左邊的四個 ImageView 和右下的 FloatingActionButton 都有 Flyin 的效果,同時左邊的四個ImageView還在繞 Y 軸作 3D 旋轉。上方的 Seekbar的背景在作CircularReveal的效果。有了前面編寫的CircularRevealHelper以及 FlyInHelper 咱們能夠很方便作到這樣的效果。

代碼參考這裏

Flow (VirtualLayout)

Flow 是 VirtualLayout,Flow 能夠像 Chain 那樣幫助快速橫向/縱向佈局constraint_referenced_ids裏面的元素。 經過flow_wrapMode能夠指定具體的排列方式,有三種模式

  • wrap none : 簡單地把constraint_referenced_ids裏面的元素組成chain,即便空間不夠
  • wrap chain : 根據空間的大小和元素的大小組成一條或者多條 chain
  • wrap aligned : wrap chain相似,可是會對齊

下面看下如何實現這個計算器佈局:

<?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,減小層級。

流式 APIs

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

關於 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

What's New in ConstraintLayout (Google I/O'19)

相關文章
相關標籤/搜索