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>
複製代碼
雖然MotionLayout
是ConstraintLayout
的子類,可是在此佈局文件中沒有聲明約束 - 它們都在名爲@xml/collapsing_toolbar
的layoutDescriptor
文件中聲明。ui
這個文件包含咱們MotionLayout
的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">
<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
中聲明擴展和摺疊的約束,並將其關聯爲拖動手勢,以便拖動兩個狀態之間的過渡。 對於不熟悉這一點的人:earlierMotionLayout
article更詳細地介紹了這一點。
咱們應用於各個視圖的大多數動畫都在縮放它們。 若是你看看早期的動畫GIF顯示動畫的外觀,並專一於頂部的文本F.A. Cup Final 2019
,它只是變得愈來愈小,ConstraintSet
s中的約束是:
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提供了本文的源代碼。