【翻譯】MotionLayout實現(xiàn)折疊工具欄(Part 2)

motionlayout_part2.jpg

一、前言

本篇是續(xù)集,第一篇翻譯直達鏈接:【翻譯】MotionLayout實現(xiàn)折疊工具欄(Part 1)

本文特點:沒有 Kotlin/Java 代碼,講解部分全為 XML 代碼,閱讀時間短,獲取技能: MotionLayout 的入門和使用!發(fā)布時間: 8 月 17 號 ,作者: Mark Allison ,原文鏈接: https://blog.stylingandroid.com/motionlayout-collapsing-toolbar-part-2/

二、正文

谷歌 IO 2018 發(fā)布了 ConstraintLayout 2.0 版本,其中最重要的部分就是 MotionLayout 了,這玩意就是一個全新的、超牛的布局動畫工具! Nicolas Roard 哥們早已發(fā)布了一個關(guān)于 MotionLayout 的完美詳情介紹,我強烈推薦大家去閱讀一下,從中理解 MotionLayout 組件的基礎(chǔ)架構(gòu)。本系列教程中,我會講解如何使用 MotionLayout 來創(chuàng)建一個我們已經(jīng)非常熟悉的動畫行為:一個折疊工具欄動畫( a Collapsing Toolbar )。

通過上一篇文章我們了解了基本的折疊工具欄動畫行為,使用的是 MotionLayout ,第一次嘗試的效果與在 CoordinatorLayout 中使用 CollapsingToolbarLayout 的效果非常接近。不過有一個細(xì)微的小動畫在 MotionLayout 中沒有實現(xiàn)出來。移動和縮放動畫在文字上表現(xiàn)確實已經(jīng)非常接近,但是背景圖片的漸變在最邊緣上卻沒有完全相同。讓我們先看下 CoordinatorLayout 版本的實現(xiàn)效果,注意圖片在工具欄幾乎快要完全折疊之前是不會開始漸變到主色彩動畫的:

motionlayout_part2_traditional.gif

現(xiàn)在我們看看 MotionLayout 的實現(xiàn),我們會發(fā)現(xiàn)圖片漸變在整個過渡動畫中是統(tǒng)一穩(wěn)定的。也就是說:隨著工具欄折疊動畫的開始,圖片便立刻發(fā)生漸變,一直持續(xù)到工具欄完全到達折疊狀態(tài):

motionlayout_part2_motion_basic.gif

這個問題實際上很容易解決,這要感謝 MotionLayout 的另一個非常重要的特性:關(guān)鍵幀。我們已經(jīng)討論過 MotionLayout 是如何在 ConstraintSets 中所定義的固定布局之間進行過渡動畫了。而關(guān)鍵幀允許我們在兩個固定布局之間定義一個中間點,并對此點的屬性值進行操作控制。

我們之前在 ImageView 控件上定義的關(guān)于 imageAlpha 屬性的過渡動畫,設(shè)定的是從展開位置的值 255 到折疊位置的值 0 之間進行,同時 MotionLayout 在動畫過程中會進行插值運算。因此我們得到的是一個非常平滑的過渡動畫,從工具欄開始發(fā)生折疊一直到工具欄完全達到折合狀態(tài)為止。這也很好的解釋了我們所看到的在 MotionLayout 中對動畫行為的實現(xiàn)。

利用關(guān)鍵幀特性我們甚至可以做到修改相關(guān)的行為動畫,使得這些行為動畫時間在整個過渡動畫中往后延遲。為了實現(xiàn)這個目標(biāo),我們首先需要在展開狀態(tài) ConstraintSet 的定義中刪除自定義屬性 imageAlpha 字段:

  <ConstraintSet android:id="@+id/expanded">
    <Constraint
      android:id="@id/toolbar_image"
      android:layout_height="200dp"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />

同時也需要在折疊狀態(tài) ConstraintSet 的定義中進行同樣的操作:

  <ConstraintSet android:id="@+id/collapsed">
    <Constraint
      android:id="@id/toolbar_image"
      android:layout_height="?attr/actionBarSize"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />

這樣同時把透明度的漸變動畫一起刪除了,不過接下來我們會使用一個 KeyFrameSet 來代替它,這個關(guān)鍵幀設(shè)置 KeyFrameSet 字段是作為過渡元素的一個子元素:

<?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" />
 
    <KeyFrameSet>
      <KeyAttribute
        app:framePosition="60"
        app:target="@id/toolbar_image">
        <CustomAttribute
          app:attributeName="imageAlpha"
          app:customIntegerValue="255" />
      </KeyAttribute>
      <KeyAttribute
        app:framePosition="90"
        app:target="@id/toolbar_image">
        <CustomAttribute
          app:attributeName="imageAlpha"
          app:customIntegerValue="0" />
      </KeyAttribute>
    </KeyFrameSet>
  </Transition>
  ...
</MotionScene>

這里 KeyFrameSet 包含了兩個 KeyAttribute 字段,每一個字段分別定義了指定位置下的一個狀態(tài),第一個位于第 60 幀,也就是說整個過渡動畫過程中的 60% 的位置,而第二個在 90 的位置,同樣的道理,這意味著位于過渡動畫的 90% 的位置。這兩個字段通過設(shè)置 ID 分別指定作用目標(biāo)控件對象(在這里兩個字段都是指定的 @id/toolbar_image )。每一個字段還定義了一個 CustomAttribute 元素,它的意思和我們之前在開頭、結(jié)尾狀態(tài)中定義的意思是一樣的。

目前來說,發(fā)生的情況是:圖片的透明度在過渡動畫還沒有達到 60% 之前是不會發(fā)生變化的(也就是至少超過一半的折疊狀態(tài)下不發(fā)生變化),接下來會慢慢開始淡出,直到工具欄達到 90% 折疊時完全透明。

motionlayout_part2_motion_offset_fade.gif

現(xiàn)在已經(jīng)更加接近我們所見到的 CoordinatorLayout 所實現(xiàn)的標(biāo)準(zhǔn)動畫了。不過仍然并非完全一樣,但是至少我們能看到,通過這種方式我們可以取得對動畫過渡的更好的控制權(quán),如果使用 CoordinatorLayout 來進行這樣的調(diào)整那會非常的麻煩。

事實上關(guān)鍵幀是非常非常強大的, Nicolas Roard 已經(jīng)對此作了一個深入介紹。我們在此不會重復(fù) Nicolas Roard 所介紹的那樣,相反我們來嘗試一些其他的方式并投入使用。

首先我們并不局限于目前僅使用兩個關(guān)鍵幀的限制,事實上我們可以創(chuàng)建更多精細(xì)動畫。甚至使用關(guān)鍵幀我們都能夠創(chuàng)建出自定義的漸進曲線來(對于安卓開發(fā)者來說也就是所謂的插值)。舉個例子,假設(shè)我們設(shè)置 imageAlpha 的開始和結(jié)束值分別是 255 和 0 ,然后在 25% 的位置添加一個關(guān)鍵幀,設(shè)置值為 205 ,在 75% 的位置設(shè)置另一個關(guān)鍵幀值為 50 。結(jié)果會給我們實現(xiàn)一個和加速-減速插值器一樣的效果。

更牛逼的是,我們可以在動畫進行時對動畫進行動態(tài)更改。標(biāo)題文字的移動和縮放在整個過渡動畫中是同時進行的,但是通過添加一個單獨關(guān)鍵幀后我們可以做到在不更改 ConstraintSets 代碼的前提下,也不用改變縮放速度就能讓標(biāo)題文本更快地到達動畫最終位置:

<?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" />
 
    <KeyFrameSet>
      <KeyAttribute
        app:framePosition="60"
        app:target="@id/toolbar_image">
        <CustomAttribute
          app:attributeName="imageAlpha"
          app:customIntegerValue="255" />
      </KeyAttribute>
      <KeyAttribute
        app:framePosition="90"
        app:target="@id/toolbar_image">
        <CustomAttribute
          app:attributeName="imageAlpha"
          app:customIntegerValue="0" />
      </KeyAttribute>
      <KeyPosition
        app:type="pathRelative"
        app:framePosition="50"
        app:target="@id/title"
        app:percentX="0.9" />
    </KeyFrameSet>
  </Transition>

以上代碼能實現(xiàn)在 50% 的過渡動畫進程中完成 90% 的移動效果。最終標(biāo)題文本會走在工具欄折疊動畫之前,接著在折疊完全結(jié)束的時候直接回落到正確的位置上:

motionlayout_part2_motion_key_position.gif

雖然這只是棄用 CoordinatorLayout 過渡動畫的一個開始,但是恰恰通過這個例子告訴了我們,如何使用關(guān)鍵幀來幫助我們動態(tài)地進行過渡動畫修改,實現(xiàn)在同樣的過渡中產(chǎn)生不同的動畫效果。

最后值得一提的是:有時候它還能幫我們實現(xiàn)過渡動畫的可視化,我們可以通過開啟布局中的 showPaths 屬性來實現(xiàn):

<?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"
  tools:showPaths="true"
  app:showPaths="true">
  ...
</androidx.constraintlayout.motion.widget.MotionLayout>

這里的 tools:showPaths="true" 設(shè)置如果在 Android Studio 編輯器里配合使用會更爽(這個功能應(yīng)該會出現(xiàn)在 Android Studio 3.4 的 alpha 版本中)。但是在目前來說,添加 tools:showPaths="true" 這段代碼能夠讓 MotionLayout 計算并顯示這三個被過渡動畫所影響的視圖控件的軌跡路線:標(biāo)題文本控件(頂部,中心左側(cè)),工具欄的海灘小排屋圖片(頂部中心),以及列表 RecyclerView 控件(中心位置):

motionlayout_part2_motion_show_paths.gif

值得注意的是,我們在文本控件上添加的關(guān)鍵幀就是位于左邊路徑頂部下方的那一個紅點。如果你仔細(xì)查看標(biāo)題文本的移動,你會清楚的看到這一行軌跡始終穿行在字母 n 和 g 之間,并且它到達關(guān)鍵點位置要相對快些。這種顯示路徑的方式有助于我們理解剛才創(chuàng)建的關(guān)鍵幀是如何影響到過渡動畫的特定部分的。你只需要記得在最終發(fā)布版本中要關(guān)閉這個功能——我建議定義一個布爾值資源,在布局中使用,然后你就可以在發(fā)布版本時總能設(shè)置它為 false 就可以了。

好吧,這次就到這里。即使如此,我相信大多數(shù)人還是會認(rèn)同 MotionLayout 不僅靈活、強大,而且還為設(shè)計用戶交互控制的布局動畫開辟了一個非常有趣的可能性哦。 :sunglasses:

三、總結(jié)

本篇的源代碼請移步這里

? 2018 , Mark Allison 。保留所有版權(quán)。

我的博客地址: http://liuqingwen.me ,歡迎關(guān)注我的微信公眾號:

IT自學(xué)不成才

?著作權(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)容