android motionLayout從入門到實戰..

語言: CN / TW / HK

本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家發佈

android MotionLayout一篇帶你瞭解最新android動畫布局!

環境

  • system : macOS
  • android studio : 4.1.3
  • constraintLayout : 2.0.4
  • gradle : gradle-6.7.1-bin
  • kotlin : 1.4.23

你需要知道什麼

  • MotionLayout 是ConstraintLayout的子類,具有ConstraintLayout所有功能 >implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
  • 使用as預覽MotionLayout, as版本必須 >= 4.0

使用 MotionLayout 創建動畫

將原ConstraintLayout轉換為MotionLayout 佈局

在這裏插入圖片描述

轉換之後:

在這裏插入圖片描述

可以看出,這裏定義了一個Button,如果説在ConstraintLayout,這樣寫會有警告,讓約束起來,但是這裏並沒有

沒有的原因是因為定義了MotionLayout的layoutDescription屬性,需要在layoutDescription屬性中寫

點擊動畫[OnClick]

在這裏插入圖片描述

onClick#clickAction説明:

|類型|説明|效果圖 |--|--|--| |motion:clickAction="toggle"|反轉| |motion:clickAction="transitionToEnd"|動畫到第二個| | motion:clickAction="jumpToEnd"|不添加動畫直接結束| | motion:clickAction="jumpToStart" |不添加動畫到開始 |見名之意.. |motion:clickAction="transitionToStart" |添加動畫到 |見名之意..

多個view一起聯動

例如這樣:

在這裏插入圖片描述

效果圖:

手指拖動 [OnSwipe]

在這裏插入圖片描述

參數介紹: - motion:touchAnchorId 指的是您可以滑動並拖動的視圖。 - motion:touchAnchorSide 表示我們從右側拖動視圖。 - motion:dragDirection 表示拖動的進度方向。 例如,motion:dragDirection="dragRight" 表示當您向右拖動時,進度會增加。

效果圖:

輔助工具

motionLayout默認會自帶調試工具.

在這裏插入圖片描述

説明表:

|狀態|説明|效果圖| |--|--|--| |app:motionDebug="SHOW_ALL"|輔助線和進度顯示| |app:motionDebug="SHOW_PATH"|輔助線顯示| |app:motionDebug="SHOW_PROGRESS"|進度顯示|

官方説明圖:

在這裏插入圖片描述 - 圓圈 代表一個視圖的開始或結束位置。 - 線條 代表一個視圖的路徑。 - 菱形 代表KeyPosition修改路徑。

還有一種使用視圖的方式來調試:


修改路徑(KeyPosition)

修改路徑可以通過右側可視化工具來進行:


詳解圖: |詳解1|詳解2 |--|--| ||

再來看看自動生成的代碼:

在這裏插入圖片描述

KeyPosition參數詳解: - motion:motionTarget="@+id/button" 需要移動軌跡的view - motion:framePosition="[0-100]" framePosition是一個介於 0 和 100 之間的數字。它定義了在動畫KeyPosition中的應用時間,1 表示 1% 的動畫,99 表示 99% 的動畫 - motion:keyPositionType='' '' keyPositionType這是如何KeyPosition修改路徑。它可以是parentRelativepathRelative,或deltaRelative - percentX | percentY是修改路徑的量framePosition(值介於 0.0 和 1.0 之間,允許負值和值 >1)

這裏motion:keyPositionType='' ''參數解釋一下: |類型|説明|座標系|效果圖| |--|--|--|--| |pathRelative|它與其他兩個完全不同,因為 X 軸從頭到尾遵循運動路徑。所以,(0,0)是的起始位置,並且(1,0)是結束位置。|| |deltaRelative|deltaRelative也是“相對變化”的一種説法。在deltaRelative座標中(0,0)是視圖的起始位置,(1,1)是結束位置。X 軸和 Y 軸與屏幕對齊。X 軸在屏幕上始終是水平的,Y 軸在屏幕上始終是垂直的。與 相比parentRelative,主要區別在於座標僅描述了視圖將在其中移動的屏幕部分。||效果和pathRelative類似,不做演示 |parentRelative|parentRelative使用相同的座標系作為屏幕。它定義(0, 0)在整個 的左上角MotionLayout和(1, 1)右下角。||

parentRelative: 這裏設置了4個點 分別為:

在這裏插入圖片描述

假設現在移動起始點1,deltaRelative會以開始點起始點2來構建一個‘貝塞爾環境’來生成對應的路徑,這裏和pathRelative效果類似但也有不同之處!

pathRelativedeltaRelative的區別: - pathRelative不需要依靠起始點和結束點就可以拖動 - deltaRelative 起始點和結束點在同一x軸上會導致動畫路徑不按照貝塞爾路徑執行.而是執行一條直線.

如圖所示: |類型|説明|效果圖| |--|--|--| deltaRelative|需要依靠起始點和結束點|| pathRelative 或 parentRelative|不需要依靠起始點和結束點|

最終效果長這樣:


效果都一樣,就只放一個嘍,需要的請下載源碼觀看...

KeyPosition配合pathMotionArc進階

pathMotionArc見名之意就知道是用來畫弧形的.

先來看pathMotionArc簡單的例子:


可以看出,想要畫一條優雅的弧線很簡單,只需要在開始點設置 motion:pathMotionArc="startHorizontal"即可

⚠️:motion:pathMotionArc是需要2個點才生效的,默認是開始點結束點

如何設置多個點呢?例如這樣:

也可以設置兩個弧形的比例大小等


位置座標説明:

在這裏插入圖片描述

pathMotionArc類型介紹:

  • startVertical 向下的弧形
  • startHorizontal 向上的弧形
  • none 直線
  • flip 和上一個點如果上一個點是startVertical,那麼當前就是startHorizontal,可以理解為反轉

類型|説明|效果圖| |--|--|--| |效果一|開始點: motion:pathMotionArc="startVertical"
起始點1: motion:pathMotionArc="startHorizontal"
起始點2: motion:pathMotionArc="startHorizontal"| 效果二|開始點: motion:pathMotionArc="startVertical"
起始點1: motion:pathMotionArc="none"
起始點2: motion:pathMotionArc="startHorizontal"| 效果三|開始點: motion:pathMotionArc="startVertical"
起始點1: motion:pathMotionArc="startVertical"
起始點2: motion:pathMotionArc="flip"|

效果一,效果二和效果三的代碼高度重複,這裏就只貼一下效果三的代碼:

效果三代碼:

```xml

<!--    start -->
<ConstraintSet android:id="@+id/start">
    <!--  開始點 和起始點1設置"關聯"  -->
    <Constraint
        android:id="@+id/button6"
        android:layout_width="80dp"
        android:layout_height="80dp"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintLeft_toLeftOf="parent"
        motion:layout_constraintTop_toTopOf="parent"
        motion:pathMotionArc="startVertical" />
</ConstraintSet>

<!--    end -->
<!--  結束點不設置關聯。。  -->
<ConstraintSet android:id="@+id/end">
    <Constraint
        android:id="@+id/button6"
        android:layout_width="64dp"
        android:layout_height="64dp"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintRight_toRightOf="parent" />
</ConstraintSet>

```

這裏比例也是能改變的,例如這樣:


這裏還是比較簡單的,自己動手試試就懂啦!

改變屬性狀態(KeyAttribute)

看一眼代碼:

```xml

<Transition
    motion:constraintSetEnd="@+id/結束了"
    motion:constraintSetStart="@id/開始了"
    motion:duration="1000">
    <KeyFrameSet>

        <!-- 改變屬性狀態 -->
        <KeyAttribute
            motion:motionTarget="@+id/button7"
            motion:framePosition="22"
            android:alpha="0.2" />
    </KeyFrameSet>

    <OnClick
        motion:clickAction="toggle"
        motion:targetId="@id/button7" />
</Transition>

<!--    開始 這裏也可以定義中文,但是不建議使用。。 -->
<ConstraintSet android:id="@+id/開始了">
    <Constraint
        android:id="@+id/button7"
        android:layout_width="60dp"
        android:layout_height="60dp"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintLeft_toLeftOf="parent"
        motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>

<!--    結束 這裏也可以定義中文,但是不建議使用。。-->
<ConstraintSet android:id="@+id/結束了">
    <Constraint
        android:id="@+id/button7"
        android:layout_width="60dp"
        android:layout_height="60dp"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintRight_toRightOf="parent"
        motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>

```

改變alpha的值:


當然也可以設置多個屬性,例如這樣:


自行探索:

  • android:visibility
  • android:alpha
  • android:elevation
  • android:rotation
  • android:rotationX
  • android:rotationY
  • android:scaleX
  • android:scaleY
  • android:translationX
  • android:translationY
  • android:translationZ

KeyAttribute配合CustomAttribute設置顏色

  • CustomAttribute 常用來設置view顏色的,是KeyAttribute的屬性

來看看代碼:

```xml

<Transition
    motion:constraintSetEnd="@+id/end"
    motion:constraintSetStart="@id/start"
    motion:duration="1000">
    <!--點擊-->
    <OnClick
        motion:clickAction="toggle"
        motion:targetId="@id/imageView8" />

    <KeyFrameSet>

        <!-- 旋轉3圈到 100點 -->
        <KeyAttribute
            android:rotation="-720"
            motion:framePosition="100"
            motion:motionTarget="@id/imageView8" />

        <!-- 0點 的時候黑色 -->
        <KeyAttribute
            motion:framePosition="0"
            motion:motionTarget="@id/imageView8">
            <CustomAttribute
                motion:attributeName="colorFilter"
                motion:customColorValue="#000000" />
        </KeyAttribute>

        <!-- 50點的時候紅色 -->
        <KeyAttribute
            motion:framePosition="50"
            motion:motionTarget="@id/imageView8">
            <CustomAttribute
                motion:attributeName="colorFilter"
                motion:customColorValue="#E91E63" />
        </KeyAttribute>

        <!-- 100點的時候黑色 -->
        <KeyAttribute
            motion:framePosition="100"
            motion:motionTarget="@id/imageView8">
            <CustomAttribute
                motion:attributeName="colorFilter"
                motion:customColorValue="#000000" />
        </KeyAttribute>
    </KeyFrameSet>
</Transition>

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

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

``` 在裏面CustomAttribute你必須指定一個attributeName和一個值來設置。

  • motion:attributeName是此自定義屬性將調用的 setter 的名稱。在這個例子中, setColorFilteronDrawable將被調用。
  • motion:customColorValue 是名稱中註明的類型的自定義值,在此示例中,自定義值是指定的顏色。 自定義值可以具有以下任何類型:
    • Color
    • Integer
    • Float
    • String
    • Dimension
    • Boolean

來看效果圖就懂了:

請添加圖片描述

設置抖動[KeyCycle]

如何創建:

請添加圖片描述 關鍵代碼:

```xml

    <KeyFrameSet>
        <!--
         motion:waveOffset 添加到屬性的偏移值
         motion:wavePeriod 要在此區域附近循環的循環數
         motion:waveShape="cos"
                 sin|square|triangle|sawtooth|reverseSawtooth|cos|bounce
                 正弦| 方形  |三角形  | 鋸齒    |    反向鋸齒    |cos| 反彈
         -->
          <KeyCycle
            android:alpha="0.5"
            android:scaleY="1.2"
            android:scaleX="1.2"
            motion:framePosition="51"
            motion:motionTarget="@+id/imageView8"
            motion:waveOffset="2"
            motion:wavePeriod="1"
            motion:waveShape="sin" />
    </KeyFrameSet>
</Transition>

``` 參數介紹: - motion:waveOffset 添加到屬性的偏移值 - motion:wavePeriod 要在此區域附近循環的循環數 - motion:waveShape="cos" - sin|square|triangle|sawtooth|reverseSawtooth|cos|bounce

各大類型效果圖: sin|square|triangle|sawtooth|reverseSawtooth|cos|bounce |--|--|--|--|--|--|-- |正弦| 方形 |三角形 | 鋸齒 | 反向鋸齒 |cos| 反彈 |||||||

設置抖動(KeyTimeCycle)

KeyTimeCycle和KeyCycle使用起來是一樣的,參數也是一樣的.

有一點不同的是,一般KeyTimeCycle 是三個一起使用,通過 3 個 KeyTimeCycle 定義一個準確的循環關鍵幀

請添加圖片描述

可以看出,只有在第50幀的時候,會發生改變,因為第50幀的時候設置motion:wavePeriod 為1

  • motion:wavePeriod:要在此區域附近循環的循環數

改變控件屬性(KeyTrigger)

什麼叫改控件屬性? 如何控制?

先來看效果:

首先自定義ImageView,裏面就2個方法,showhide ```kotlin class KeyTriggerImageView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : AppCompatImageView(context, attrs, defStyleAttr) {

// 顯示 view
fun show() {
    visibility = View.VISIBLE
}
// 隱藏當前 view
fun hide() {
    visibility = View.GONE
}

} ``` 如何使用:

```kotlin

<Transition
    motion:constraintSetEnd="@+id/end"
    motion:constraintSetStart="@id/start"
    motion:duration="2000">
    <!--點擊-->
    <OnClick
        motion:clickAction="toggle"
        motion:targetId="@id/imageView11" />
    <KeyFrameSet>
        <KeyTrigger
            motion:framePosition="0"
            motion:motionTarget="@+id/imageView11"
            motion:onCross="show" />
        <KeyTrigger
            motion:framePosition="20"
            motion:motionTarget="@+id/imageView11"
            motion:onCross="hide" />
        <KeyTrigger
            motion:framePosition="60"
            motion:motionTarget="@+id/imageView11"
            motion:onCross="show" />
        <KeyTrigger
            motion:framePosition="79"
            motion:motionTarget="@+id/imageView11"
            motion:onCross="hide" />
        <KeyTrigger
            motion:framePosition="100"
            motion:motionTarget="@+id/imageView11"
            motion:onCross="show" />
    </KeyFrameSet>
</Transition>

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

``` KeyTrigger參數介紹: - motion:onCross 調用的方法名字 - motion:framePosition 當前是第幾幀 (0-100) - motion:motionTarget 設置的控件id 來看一眼效果:


根據這個思路,是不是就可以在滑動的過程中替換圖標顯示,例如這樣:


再來一張輔助圖,現在應該是非常清晰了!!

在這裏插入圖片描述

加速與減速(Easing)

也是一個參數的效果:使用很簡單,直接看代碼:

```xml

<Transition
    motion:constraintSetEnd="@+id/end"
    motion:constraintSetStart="@id/start"
    motion:duration="1000">
    <!--點擊-->
    <OnClick
        motion:clickAction="toggle"
        motion:targetId="@id/imageView13" />
    <KeyFrameSet>

    </KeyFrameSet>
</Transition>
<!--   開始 -->
<ConstraintSet android:id="@+id/start">
    <!--   
        motion:transitionEasing 設置加速度 or 減速 類型  
    -->
    <Constraint
        android:id="@+id/imageView13"
        android:layout_width="100dp"
        android:layout_height="100dp"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintLeft_toLeftOf="parent"
        motion:transitionEasing="acclerate" />
</ConstraintSet>
<!--    結束-->
<ConstraintSet android:id="@+id/end">
    <Constraint
        android:id="@+id/imageView13"
        android:layout_width="100dp"
        android:layout_height="100dp"
        motion:layout_constraintRight_toRightOf="parent"
        motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>

``` motion:transitionEasing類型介紹: |類型|輔助圖|效果 |--|--|--| |standard| | acclerate|| decelerate||

常見的就這幾種,其他的還有很多,就不一一演示了.

效果變化很細微,自己手動搞一遍就明白啦!!

實戰

先來看看實戰效果:


佈局長這樣:

在這裏插入圖片描述 需求分析: - 類似皮皮蝦點擊評論功能 - 點擊評論按鈕時候,圖片縮小,底下彈出一個recyclerview來顯示評論 - recyclerview顯示的時候,評論按鈕不顯示 - recyclerview不顯示的時候,評論按鈕顯示

activity_motion_layout_9_scene.xml: ```xml

<Transition
    motion:constraintSetEnd="@+id/end"
    motion:constraintSetStart="@id/start"
    motion:duration="400">

    <OnClick
        motion:clickAction="toggle"
        motion:targetId="@id/imageComment" />

    <OnSwipe
        motion:dragDirection="dragUp"
        motion:touchAnchorId="@id/recyclerView" />
</Transition>

<!-- 開始 -->
<ConstraintSet android:id="@+id/start">
    <Constraint
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintLeft_toLeftOf="parent"
        motion:layout_constraintRight_toRightOf="parent"
        motion:layout_constraintTop_toTopOf="parent" />

    <Constraint
        android:id="@+id/imageComment"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_marginRight="@dimen/dp_10"
        android:alpha="1"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintRight_toRightOf="parent"
        motion:layout_constraintTop_toTopOf="parent"
        motion:layout_constraintVertical_bias="0.7" />
    <Constraint
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        motion:layout_constraintTop_toBottomOf="@id/image" />
</ConstraintSet>

<!-- 結束 -->
<ConstraintSet android:id="@+id/end">
    <Constraint
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        motion:layout_constraintBottom_toTopOf="@id/recyclerView"
        motion:layout_constraintLeft_toLeftOf="parent"
        motion:layout_constraintRight_toRightOf="parent"
        motion:layout_constraintTop_toTopOf="parent" />
    <Constraint
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="500dp"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintTop_toBottomOf="@id/image" />

    <Constraint
        android:id="@+id/imageComment"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_marginRight="@dimen/dp_10"
        android:alpha="0"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintRight_toRightOf="parent"
        motion:layout_constraintTop_toTopOf="parent"
        motion:layout_constraintVertical_bias="0.7" />
</ConstraintSet>

```

走到這裏基本上就完事了

總結

大致結構

```xml

<!-- 點擊 -->
<OnClick />
<KeyFrameSet >

        <KeyAttribute>
                <CustomAttribute/>
        </KeyAttribute>

        <KeyPostion/>

        <KeyCycle/>

        <KeyTimeCycle/>
</KeyFrameSet>

    <!--用於過渡動畫的起始點狀態參數配置-->
   <Constraint android:id="@id/viewId">
            <!-- 運動模型: 弧線路路徑,時間模型等 -->
            <Motion/>
            <!--
            佈局相關
            注意: width 、 height 和 margin 的命名空間是 android: (beta1 開始)
                 而約束相關的命名空間是 app (或 motion )
            -->
            <Layout/>
            <!-- 動畫變換:做旋轉,位移,縮放,海海拔等屬性 -->
            <Transform/>
            <!--
            自定義屬性
                attributeName 會加上 set/get 反射找到真正的函數名,
                ⽐如 backgroundColor 就會調用 setBackgroundColor() 函數
                custom(xxx)Value 對應屬性的數據類型
            -->
            <CustomAttribute/>
            <!--
            特定的屬性
                visibility 、alpha 等屬性
            -->
            <PropertySet/>
</Constraint>

<!--用於過渡動畫的結束點狀態參數配置-->
<ConstraintSet android:id="@+id/end"../>

```

完整代碼

其他: - 官方地址: git clone https://github.com/googlecodelabs/motionlayout.git

原創不易,您的點贊就是對我最大的支持!