「译」MotionLayout介绍 (part III)

原文连接php

前言

在本系列的第一部分和第二部分中,咱们介绍了 MotionLayout,并给出了各类示例:android

  • basic motion
  • swipe handling
  • custom attribute interpolation
  • keyframes

第一部分和第二部分概述了 MotionLayout 中引入的基本概念。git

如今将介绍如何在现有应用程序中使用 MotionLayout,并将其集成到现有布局(如 CoordinatorLayout,DrawerLayout或ViewPager)中。github

配合 CoordinatorLayout 使用

(注意,MotionLayout 能够用来实现相似于 CoordinatorLayout的行为。咱们将在下一篇文章中展现这些例子)app

利用 MotionLayout 的一个简单方法让屏幕中的部份内容作指定动画。经过这种方式,你能够向应用程序中的现有布局添加更多有趣的运动,而没必要从头开始。ide

例如,你想要的效果可能以下:函数

with-coordinatorlayout-1

这里的基本思路是用 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 使用

DrawerLayout 是另外一个 Android framework 中的类,用于打开一个侧边栏。

相比一般的菜单,咱们可能但愿来一些更有趣的东西:

with_drawerlayout-1

和把 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 。

with-viewpag-1

咱们也可使用相似的技巧来集成 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 使用

前面的例子用了简单的图片来做为头部。你也能够 Lottie集成到你的 MotionLayout中,而后直接设置它的进度从而播放它。

让咱们把上一个例子改为 LottieAnimationView:

with-lottie-1

简单起见,咱们把基于 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找到这些例子的源码。

本系列文章还有更多内容:

  • Introduction to MotionLayout (part I)
  • Custom attributes, image transitions, keyframes (part II)
  • Taking advantage of MotionLayout in your existing layouts (CoordinatorLayout, DrawerLayout, ViewPager) (part III)
  • All about Keyframes! (part IV)
  • MotionLayout as a choreographer of root layout
  • Nesting MotionLayout & other Views
  • MotionLayout with fragments
相关文章
相关标签/搜索