原文連接php
在本系列的第一部分和第二部分中,咱們介紹了 MotionLayout,並給出了各類示例:android
第一部分和第二部分概述了 MotionLayout 中引入的基本概念。git
如今將介紹如何在現有應用程序中使用 MotionLayout,並將其集成到現有佈局(如 CoordinatorLayout,DrawerLayout或ViewPager)中。github
(注意,MotionLayout 能夠用來實現相似於 CoordinatorLayout的行爲。咱們將在下一篇文章中展現這些例子)app
利用 MotionLayout 的一個簡單方法讓屏幕中的部份內容作指定動畫。經過這種方式,你能夠嚮應用程序中的現有佈局添加更多有趣的運動,而沒必要從頭開始。ide
例如,你想要的效果可能以下:函數
這裏的基本思路是用 MotionLayout 替換 AppBarLayout 中的 Toolbar 元素。而後讓 CoordinatorLayout 驅動動畫進度。佈局
因爲你能夠經過調用setProgress()
來控制 MotionLayout 的過渡進度,所以咱們能夠建立一個簡單的子類,經過監聽 AppBarLayout 偏移量來跟蹤改變:動畫
package com.google.androidstudio.motionlayoutexample.utils
import android.content.Context
import android.support.constraint.motion.MotionLayout
import android.support.design.widget.AppBarLayout
import android.util.AttributeSet
class CollapsibleToolbar @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : MotionLayout(context, attrs, defStyleAttr), AppBarLayout.OnOffsetChangedListener {
override fun onOffsetChanged(appBarLayout: AppBarLayout?, verticalOffset: Int) {
progress = -verticalOffset / appBarLayout?.totalScrollRange?.toFloat()!!
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
(parent as? AppBarLayout)?.addOnOffsetChangedListener(this)
}
}
複製代碼
而後用這個子類代替 CoordinatorLayout XML 文件中的 Toolbar。ui
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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:id="@+id/content" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="false" android:background="@color/contentBackground">
<android.support.design.widget.AppBarLayout android:id="@+id/app_bar" android:layout_width="match_parent" android:layout_height="@dimen/app_bar_height" android:theme="@style/AppTheme.AppBarOverlay">
<include layout="@layout/motion_09_coordinatorlayout_header"/>
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_scrolling" />
</android.support.design.widget.CoordinatorLayout>
複製代碼
接下來惟一要作的就是建立一個包含了咱們想要動畫的控件的 MotionLayout 文件。這裏咱們有一個 做爲背景的 ImageView 和一個 TextView:
<?xml version="1.0" encoding="utf-8"?>
<com.google.androidstudio.motionlayoutexample.utils.CollapsibleToolbar xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/motionLayout" app:layoutDescription="@xml/scene_09" android:layout_width="match_parent" android:layout_height="match_parent" android:minHeight="50dp" android:fitsSystemWindows="false" app:layout_scrollFlags="scroll|enterAlways|snap|exitUntilCollapsed">
<ImageView android:id="@+id/background" android:layout_width="match_parent" android:layout_height="200dp" android:background="@color/colorAccent" android:scaleType="centerCrop" android:src="@drawable/monterey"/>
<TextView android:id="@+id/label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:transformPivotX="0dp" android:transformPivotY="0dp" android:text="Monterey" android:textColor="#FFF" android:textSize="32dp" />
</com.google.androidstudio.motionlayoutexample.utils.CollapsibleToolbar>
複製代碼
最後在 MotionScene 中定義它的動畫效果:
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android" xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition motion:constraintSetStart="@+id/start" motion:constraintSetEnd="@+id/end" />
<ConstraintSet android:id="@+id/start">
<Constraint android:id="@+id/background" android:layout_width="match_parent" android:layout_height="match_parent" android:alpha="1.0" motion:layout_constraintBottom_toBottomOf="parent"/>
<Constraint android:id="@+id/label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:rotation="-90.0" motion:layout_constraintBottom_toBottomOf="@+id/background" motion:layout_constraintStart_toStartOf="parent"/>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint android:id="@+id/background" android:layout_width="match_parent" android:layout_height="match_parent" android:alpha="0.2" motion:layout_constraintBottom_toBottomOf="parent"/>
<Constraint android:id="@+id/label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginBottom="8dp" android:rotation="0.0" motion:layout_constraintBottom_toBottomOf="@+id/background" motion:layout_constraintStart_toStartOf="parent" />
</ConstraintSet>
</MotionScene>
複製代碼
DrawerLayout 是另外一個 Android framework 中的類,用於打開一個側邊欄。
相比一般的菜單,咱們可能但願來一些更有趣的東西:
和把 MotionLayout 整合到 CoordinatorLayout 中相似,咱們須要建立一個設置 MotionLayout 進度的子類:
package com.google.androidstudio.motionlayoutexample.utils
import android.content.Context
import android.support.constraint.motion.MotionLayout
import android.support.v4.widget.DrawerLayout
import android.util.AttributeSet
import android.view.View
class DrawerContent @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : MotionLayout(context, attrs, defStyleAttr), DrawerLayout.DrawerListener {
override fun onDrawerStateChanged(newState: Int) {
}
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
progress = slideOffset
}
override fun onDrawerClosed(drawerView: View) {
}
override fun onDrawerOpened(drawerView: View) {
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
(parent as? DrawerLayout)?.addDrawerListener(this)
}
}
複製代碼
這個子類將經過onDrawerSlide()
回調來跟蹤過渡進度。
使用這個子類,咱們能夠很容易地在DrawerLayout中集成MotionLayout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/motionLayout" android:background="@color/colorPrimaryDark">
<include layout="@layout/motion_12_drawerlayout_content"/>
<include layout="@layout/motion_13_drawerlayout_menu"/>
</android.support.v4.widget.DrawerLayout>
複製代碼
這個 xml
文件像咱們前面 CoordinatorLayout 例子同樣,包含了幾個簡單控件。
下面是一個使用了 MotionLayout 的菜單文件 (menu file):
<?xml version="1.0" encoding="utf-8"?>
<com.google.androidstudio.motionlayoutexample.utils.DrawerContent 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:id="@+id/menu" android:layout_width="180dp" android:layout_height="match_parent" android:layout_gravity="start" app:layoutDescription="@xml/scene_13_menu" android:background="@color/colorPrimaryDark">
<TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="32dp" android:text="Monterey" android:textSize="20sp" android:textStyle="italic" android:typeface="serif" android:textColor="#FFF" app:layout_constraintBottom_toTopOf="@+id/textView3" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.0" app:layout_constraintVertical_chainStyle="packed" />
<TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="Information" app:fontFamily="sans-serif-smallcaps" android:textColor="#FFF" app:layout_constraintBottom_toTopOf="@+id/textView4" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/view" />
<TextView android:id="@+id/textView4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="Directions" app:fontFamily="sans-serif-smallcaps" android:textColor="#FFF" app:layout_constraintBottom_toTopOf="@+id/textView5" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView2" />
<TextView android:id="@+id/textView5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="Sight-Seeing" app:fontFamily="sans-serif-smallcaps" android:textColor="#FFF" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView4" />
<View android:id="@+id/view" android:background="#c2c1c1" android:layout_width="100dp" android:layout_height="1dp" android:layout_marginTop="16dp" app:layout_constraintBottom_toTopOf="@+id/textView2" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView3" />
<TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="California" android:textColor="#FFF" app:fontFamily="cursive" app:layout_constraintBottom_toTopOf="@+id/view" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView" />
</com.google.androidstudio.motionlayoutexample.utils.DrawerContent>
複製代碼
MotionScene 文件只是旋轉不一樣的元素 (檢查 rotation
屬性)
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android" xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition motion:constraintSetEnd="@+id/end" motion:constraintSetStart="@+id/start" motion:duration="250" />
<ConstraintSet android:id="@+id/start">
<Constraint android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="32dp" android:rotation="90" android:translationX="100dp" motion:layout_constraintBottom_toTopOf="@+id/textView3" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintTop_toTopOf="parent" motion:layout_constraintVertical_chainStyle="spread" />
<Constraint android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:rotation="90" android:translationX="100dp" motion:layout_constraintBottom_toTopOf="@+id/textView4" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintTop_toBottomOf="@+id/view" />
<Constraint android:id="@+id/textView4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:rotation="90" android:translationX="100dp" motion:layout_constraintBottom_toTopOf="@+id/textView5" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintTop_toBottomOf="@+id/textView2" />
<Constraint android:id="@+id/textView5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:rotation="90" android:translationX="100dp" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintTop_toBottomOf="@+id/textView4" />
<Constraint android:id="@+id/view" android:layout_width="100dp" android:layout_height="1dp" android:layout_marginTop="16dp" android:rotation="90" android:translationX="100dp" motion:layout_constraintBottom_toTopOf="@+id/textView2" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintTop_toBottomOf="@+id/textView3" />
<Constraint android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:rotation="90" android:translationX="100dp" motion:layout_constraintBottom_toTopOf="@+id/view" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintTop_toBottomOf="@+id/textView" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="32dp" motion:layout_constraintBottom_toTopOf="@+id/textView3" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintHorizontal_bias="0.5" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintTop_toTopOf="parent" motion:layout_constraintVertical_bias="0.0" motion:layout_constraintVertical_chainStyle="packed" />
<Constraint android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" motion:layout_constraintBottom_toTopOf="@+id/textView4" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintHorizontal_bias="0.5" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintTop_toBottomOf="@+id/view" />
<Constraint android:id="@+id/textView4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" motion:layout_constraintBottom_toTopOf="@+id/textView5" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintHorizontal_bias="0.5" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintTop_toBottomOf="@+id/textView2" />
<Constraint android:id="@+id/textView5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintHorizontal_bias="0.5" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintTop_toBottomOf="@+id/textView4" />
<Constraint android:id="@+id/view" android:layout_width="100dp" android:layout_height="1dp" android:layout_marginTop="16dp" motion:layout_constraintBottom_toTopOf="@+id/textView2" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintHorizontal_bias="0.5" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintTop_toBottomOf="@+id/textView3" />
<Constraint android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" motion:layout_constraintBottom_toTopOf="@+id/view" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintHorizontal_bias="0.5" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintTop_toBottomOf="@+id/textView" />
</ConstraintSet>
複製代碼
相似的,咱們可能但願有一個有趣的 ViewPager 。
咱們也可使用相似的技巧來集成 ViewPager 。建立一個子類來傳遞當前的位置。
package com.google.androidstudio.motionlayoutexample.utils
import android.content.Context
import android.support.constraint.motion.MotionLayout
import android.support.v4.view.ViewPager
import android.util.AttributeSet
class ViewpagerHeader @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : MotionLayout(context, attrs, defStyleAttr), ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
var numPages = 3
progress = (position + positionOffset) / (numPages - 1)
}
override fun onPageSelected(position: Int) {
}
}
複製代碼
計算很是簡單——onPageScrolled()
給咱們提供了頁面的位置索引(咱們有三個頁面的時候,這裏是0,1或2),偏移量(對應偏移位置從0到1)。動畫的進度更新咱們能夠經過下面的公式來獲得:
progress = (position + positionOffset) / (numPages-1)
前面的例子用了簡單的圖片來做爲頭部。你也能夠 Lottie集成到你的 MotionLayout中,而後直接設置它的進度從而播放它。
讓咱們把上一個例子改爲 LottieAnimationView:
簡單起見,咱們把基於 MotionLayout 的 ViewPager Header 改爲只包含一個 LottieAnimationView 的樣式:
<?xml version="1.0" encoding="utf-8"?>
<com.google.androidstudio.motionlayoutexample.utils.ViewpagerHeader xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/motionLayout" app:layoutDescription="@xml/scene_23" android:layout_width="match_parent" app:progress="0" android:layout_height="230dp">
<com.airbnb.lottie.LottieAnimationView android:id="@+id/animation_view" android:layout_width="match_parent" android:layout_height="match_parent" app:lottie_rawRes="@raw/walkthrough"/>
</com.google.androidstudio.motionlayoutexample.utils.ViewpagerHeader>
複製代碼
在 MotionScene 中關鍵的修改是使用motion:progress
屬性:
<Constraint android:id="@+id/animation_view" android:layout_width="match_parent" android:layout_height="match_parent" motion:progress="0"/>
複製代碼
因爲 LottieAnimationView 有一個setProgress()
函數,這將致使 MotionLayout 經過它直接播放 Lottie 的對應進度。
完整的 MotionScene 文件以下:
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android" xmlns:motion="http://schemas.android.com/apk/res-auto">
<Transition motion:constraintSetStart="@+id/start" motion:constraintSetEnd="@+id/end">
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint android:id="@+id/animation_view" android:layout_width="match_parent" android:layout_height="match_parent" motion:progress="0"/>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint android:id="@+id/animation_view" android:layout_width="match_parent" android:layout_height="match_parent" motion:progress="1"/>
</ConstraintSet>
</MotionScene>
複製代碼
本篇介紹瞭如何在現有佈局中輕鬆集成 MotionLayout。
你能夠在 ConstraintLayout examples github repository找到這些例子的源碼。
本系列文章還有更多內容: