MotionLayout:動態工具欄

Styling Android的常規讀者可能已經猜到我喜歡動畫的東西。 MotionLayout爲動畫提供了驚人的範圍,而且可使用它建立一些很是有趣的動畫。咱們以前在Styling Android上看過how to implement a Collapsing Toolbar,但咱們不只限於模仿可使用其餘Android API實現的現有行爲,MotionLayout給了咱們一些真正的範圍來得到時髦。在本文中,咱們將略微突破界限並探索一些咱們能夠與MotionLayout一塊兒使用的有趣技術。原文android

在咱們開始以前,值得指出的是,在動畫方面很容易超越頂部。雖然很容易在動畫中添加愈來愈多的複雜組件,但有時知道什麼時候中止添加其餘組件,甚至刪除那些不適合總體流程的組件,是實現感受天然的動畫的關鍵。此外,最有效的動畫一般是那些結合了很是簡單的組件動畫的動畫,這些動畫相互補充,並創造出比實際更難實現的動畫。git

讓咱們首先看看咱們將要實現的總體效果。這是一個工具欄,可能會出如今一個體育應用程序中,該應用程序顯示正在進行的足球比賽(美國足球比賽)的信息:github

(我應該指出,我對2019年F.A.杯決賽的結果並不感到痛苦。沒有絲毫。)swift

雖然動畫中有不少東西在進行,但各類動做都在一塊兒,所以總體效果感受很是流暢。實際的綠色矩形塊Toolbar實際上並無改變大小,可是當前的匹配時間文本(89.59)移動到Toolbar的邊界以外,而且有一種從Toolbar底部擴展的氣泡形狀以包含它。這是動畫中可能最有趣的部分,所以本文的大部份內容都將關注這一點。app

我不打算完整描述MotionLayout的機制,由於previous article已經涵蓋了。關鍵是咱們有效地定義了兩個靜態,每一個靜態表示爲ConstraintSet。展開狀態以下所示:ide

崩潰的州看起來像這樣:工具

MotionLayout自己聲明以下:佈局

RES/佈局/activity_main.xml動畫

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout 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"
    app:layoutDescription="@xml/collapsing_toolbar"
    tools:context=".MainActivity">

    <View
        android:id="@+id/toolbar"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@color/colorPrimary" />

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/event_name"
        android:textAppearance="@style/TextAppearance.AppCompat.Widget.ActionBar.Subtitle.Inverse" />

    <TextView
        android:id="@+id/score"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/score"
        android:textAppearance="@style/TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse" />

    <ImageView
        android:id="@+id/man_city_logo"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        android:adjustViewBounds="true"
        android:contentDescription="@null"
        android:paddingStart="16dp"
        android:paddingEnd="16dp"
        android:src="@drawable/man_city" />

    <TextView
        android:id="@+id/man_city"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/man_city"
        android:textAppearance="@style/TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse"
        app:layout_constraintBaseline_toBaselineOf="@id/score"
        app:layout_constraintEnd_toStartOf="@id/man_city_logo"/>

    <ImageView
        android:id="@+id/watford_logo"
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        android:adjustViewBounds="true"
        android:contentDescription="@null"
        android:paddingStart="16dp"
        android:paddingEnd="16dp"
        android:src="@drawable/watford" />

    <TextView
        android:id="@+id/watford"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/watford"
        android:textAppearance="@style/TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse"
        app:layout_constraintBaseline_toBaselineOf="@id/score"
        app:layout_constraintStart_toEndOf="@id/watford_logo"/>

    <View
        android:id="@+id/toolbar_extension"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@drawable/bubble"
        android:backgroundTint="@color/colorPrimary"
        app:layout_constraintEnd_toEndOf="@id/time"
        app:layout_constraintStart_toStartOf="@id/time" />

    <TextView
        android:id="@+id/time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingStart="24dp"
        android:paddingEnd="24dp"
        android:text="@string/time"
        android:textAppearance="@style/TextAppearance.AppCompat.Widget.ActionBar.Subtitle.Inverse" />

    <FrameLayout
        android:id="@+id/recyclerview"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/toolbar_extension" />
</androidx.constraintlayout.motion.widget.MotionLayout>
複製代碼

雖然MotionLayoutConstraintLayout的子類,可是在此佈局文件中沒有聲明約束 - 它們都在名爲@xml/collapsing_toolbarlayoutDescriptor文件中聲明。ui

這個文件包含咱們MotionLayoutMotionScene

RES/XML/collapsing_toolbar.xml

<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <Transition
        app:constraintSetEnd="@id/collapsed"
        app:constraintSetStart="@id/expanded">

        <OnSwipe
            app:dragDirection="dragUp"
            app:touchAnchorId="@id/recyclerview"
            app:touchAnchorSide="top" />

    </Transition>

    <ConstraintSet android:id="@+id/collapsed">
        ...
    </ConstraintSet>

    <ConstraintSet android:id="@+id/expanded">
        ...
    </ConstraintSet>

</MotionScene>
複製代碼

這將在其本身的ConstraintSet中聲明擴展和摺疊的約束,並將其關聯爲拖動手勢,以便拖動兩個狀態之間的過渡。 對於不熟悉這一點的人:earlierMotionLayoutarticle更詳細地介紹了這一點。

咱們應用於各個視圖的大多數動畫都在縮放它們。 若是你看看早期的動畫GIF顯示動畫的外觀,並專一於頂部的文本F.A. Cup Final 2019,它只是變得愈來愈小,ConstraintSets中的約束是:

RES/XML/collapsing_toolbar.xml

<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    ...
    <ConstraintSet android:id="@+id/collapsed">
        ...
        <Constraint android:id="@id/title">
            <Transform
                android:scaleX="0.5"
                android:scaleY="0.5" />
            <Layout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintBottom_toTopOf="@id/score"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="@id/toolbar" />
        </Constraint>
        ...
    </ConstraintSet>

    <ConstraintSet android:id="@+id/expanded">
        ...
        <Constraint android:id="@id/title">
            <Transform
                android:scaleX="1.0"
                android:scaleY="1.0" />
            <Layout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
        </Constraint>
        ...
    </ConstraintSet>

</MotionScene>
複製代碼

儘管layout_constraint*屬性存在一些細微差異,但這裏的關鍵是Transform,它將在collpased和expanded狀態之間縮放文本。 MotionLayout將自動爲咱們進行這種縮放。 咱們免費得到的是,若是咱們將其餘View限制在View的底部,他們將隨着縮放應用於此View而移動。 因此視覺效果是它下面的View隨着它的增加和收縮而移動。 在GIF中查看團隊名稱和分數如何隨着標題文本的大小變化而移動。

咱們使用相同的技術來縮放團隊徽標和匹配時間文本(89:59)。 我不會單獨介紹這些內容,但請查看accompanying source code以查看此內容。

可是匹配時間文本值得一看:

RES/XML/collapsing_toolbar.xml

<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    ...
    <ConstraintSet android:id="@+id/collapsed">
        ...
        <Constraint android:id="@id/toolbar">
            <Layout
                android:layout_width="0dp"
                android:layout_height="?attr/actionBarSize"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
        </Constraint>
        ...
        <Constraint android:id="@id/time">
            <Transform
                android:scaleX="0.5"
                android:scaleY="0.5" />
            <Layout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintBottom_toBottomOf="@id/toolbar"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/score" />
        </Constraint>
        ...
    </ConstraintSet>

    <ConstraintSet android:id="@+id/expanded">
        ...
        <Constraint android:id="@id/toolbar">
            <Layout
                android:layout_width="0dp"
                android:layout_height="0dp"
                app:layout_constraintBottom_toTopOf="@id/time"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
        </Constraint>
        ...
        <Constraint android:id="@id/time">
            <Transform
                android:scaleX="1.0"
                android:scaleY="1.0" />
            <Layout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@id/score"
                android:layout_marginTop="8dp"/>
        </Constraint>
        ...
    </ConstraintSet>

</MotionScene>
複製代碼

正在進行相同類型的縮放,但在摺疊狀態下它位於Toolbar內部,但在展開狀態下它位於其下方。

就其自身而言,這對於擴展狀態來講效果不佳,由於文本自己很輕,而Toolbar下面的背景也很輕。 所以,咱們須要將綠色「泡沫"降至Toolbar如下,以確保文本具備對比鮮明的背景。

泡沫自己就是一個VectorDrawable

RES/抽拉/bubble.xml

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="48dp"
    android:height="16dp"
    android:viewportWidth="150"
    android:viewportHeight="50">
    <path
        android:fillColor="@android:color/white"
        android:pathData="M0,0 a 25,25 0 0 1 25,25 a 25,25 0 0 0 25,25 h 50 a 25,25 0 0 0 25,-25 a 25,25 0 0 1 25,-25 Z" />
</vector>
複製代碼

它由一些弧線和水平線組成,實際上看起來像這樣:

形狀應該在咱們以前看到的靜態展開狀態圖像中可見,而且在佈局中應用綠色。 它變得有趣的地方是如何在MotionScene中應用它:

RES/XML/collapsing_toolbar.xml

<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    ...
    <ConstraintSet android:id="@+id/collapsed">
        ...
        <Constraint android:id="@id/toolbar_extension">
            <Layout
                android:layout_width="0dp"
                android:layout_height="0dp"
                app:layout_constraintBottom_toBottomOf="@id/toolbar"
                app:layout_constraintEnd_toEndOf="@id/time"
                app:layout_constraintStart_toStartOf="@id/time"
                app:layout_constraintTop_toBottomOf="@id/toolbar" />
        </Constraint>
        ...
    </ConstraintSet>

    <ConstraintSet android:id="@+id/expanded">
        ...
        <Constraint android:id="@id/toolbar_extension">
            <Layout
                android:layout_width="0dp"
                android:layout_height="0dp"
                app:layout_constraintBottom_toBottomOf="@id/time"
                app:layout_constraintEnd_toEndOf="@id/time"
                app:layout_constraintStart_toStartOf="@id/time"
                app:layout_constraintTop_toTopOf="@id/time" />
        </Constraint>
        ...
    </ConstraintSet>

</MotionScene>
複製代碼

在摺疊狀態下,此View的頂部和底部都被約束到Toolbar的底部。結果沒有高度。

在展開狀態下,此View的頂部和底部都被約束到匹配時間TextView的頂部和底部。它的高度與匹配時間TextView相匹配。

MotionLayout在摺疊狀態和展開狀態之間轉換時,氣泡的高度將發生變化,這會產生從Toolbar底部生長的氣泡的錯覺。咱們實際上能夠獲得一個微妙的不一樣效果,咱們保持泡沫與匹配時間TextView保持一致。但我我的更喜歡這種效果,由於它會使氣泡看起來增加而不是從Toolbar中滑出 - 我以爲若是感受更有機。

咱們完成了。若是咱們把全部這些放在一塊兒,咱們獲得如下結果:

這種特定的實現可能不適合某些狀況,由於匹配時間將覆蓋Toolbar下面的任何位置,但我認爲形狀改變工具欄擴展是一個有趣的想法,這就是我在這裏分享它的緣由。

here提供了本文的源代碼。

相關文章
相關標籤/搜索