炫酷的卡片动画[译]

2,513

我在之前翻译的一篇文章中介绍了MotionLayout,如果你还不知道MotionLayout是什么,先看这篇MotionLayout介绍

今天想要介绍一个用MotionLayout实现的非常炫酷的翻页动画,翻译自Medium

这是效果

可滑动的卡片

如何实现上面的卡片呢

  • 我们在xml中使用MotionLayout并定义了一个FrameLayout
    <androidx.constraintlayout.motion.widget.MotionLayout
        android:id="@+id/motionLayout"
        app:layoutDescription="@xml/memory_curve_scene"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:motionDebug="SHOW_ALL">


        <FrameLayout
            android:id="@+id/bottomCard"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="@color/design_default_color_primary">
        </FrameLayout>

    </androidx.constraintlayout.motion.widget.MotionLayout>

注意一下这一行app:motionDebug=”SHOW_ALL”,它可以让我们在屏幕上展示一些当前MotionLayout的一些属性,比如progress等等。通常只会在debug模式下使用,记得app正式发布的时候关闭它。

然后在MotionScene文件,也就是我们在res/xml目录下定义的文件中加入初始的ConstraintSet

<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <ConstraintSet android:id="@+id/rest">
        <Constraint
            android:id="@id/topCard"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginStart="50dp"
            android:layout_marginTop="50dp"
            android:layout_marginEnd="50dp"
            android:layout_marginBottom="50dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"/>
    </ConstraintSet>
</MotionScene>

让我们添加passlike状态的constraintSet,当我们完全滑动到左边或者右边的时候,就处在这个状态。

    <ConstraintSet android:id="@+id/pass"
        app:deriveConstraintsFrom="@id/rest">
        <Constraint
            android:id="@id/topCard"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_marginStart="50dp"
            android:layout_marginTop="20dp"
            android:layout_marginEnd="200dp"
            android:layout_marginBottom="80dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintWidth_percent="0.7"/>

    </ConstraintSet>

    <ConstraintSet android:id="@+id/like"
        app:deriveConstraintsFrom="@id/rest">
        <Constraint
            android:id="@id/topCard"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_marginStart="200dp"
            android:layout_marginTop="20dp"
            android:layout_marginEnd="50dp"
            android:layout_marginBottom="80dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintWidth_percent="0.7"/>
    </ConstraintSet>

passlike状态其实就是镜像的两个状态。

注意一下这一行app:deriveConstraintsFrom="@id/rest",这能让该ConstraintSet自动继承另一个ConstraintSet的属性,当然也可以覆写另一个ConstraintSet的属性。

现在,我们有了三个ConstraintSet,分别是restlikepass。现在我们需要让一个状态以点击或者滑动的方式过渡

我们加入了<OnSwipe>

    <Transition
        app:constraintSetEnd="@+id/pass"
        app:constraintSetStart="@+id/rest"
        app:duration="300" >
        <OnSwipe
            app:dragDirection="dragLeft"
            app:onTouchUp="autoComplete"
            app:touchAnchorId="@id/topCard"
            app:touchAnchorSide="left"
            app:touchRegionId="@id/topCard" />
    </Transition>

like部分是镜像的

现在我们可以这样

使卡片自动飞出屏幕

为了使卡片飞出屏幕,我们还需要添加两个ConstraintSet:offscreenLikeoffscreenPass

    <ConstraintSet android:id="@+id/offscreenLike"
        app:deriveConstraintsFrom="@id/like">
        <Constraint
            android:id="@id/topCard"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_marginBottom="80dp"
            android:layout_marginEnd="50dp"
            android:layout_marginTop="20dp"
            app:layout_constraintStart_toEndOf="parent"
            app:layout_constraintWidth_percent="0.7" />
    </ConstraintSet>

我们想什么时候卡片飞出屏幕呢?当卡片处于like阶段或者pass的时候。

因此,我们加入Transition

    <Transition
        app:autoTransition="animateToEnd"
        app:constraintSetEnd="@+id/offscreenLike"
        app:constraintSetStart="@+id/like"
        app:duration="150" />

底部卡片

我们现在需要在layout中添加我们的底部卡片。

        <FrameLayout
            android:id="@+id/topCard"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="@color/cardview_shadow_start_color">
        </FrameLayout>

res/xml的MotionScene中,我们需要更改每个ConstraintSet

<ConstraintSet android:id="@id/rest">
    <!-- ... -->
    <Constraint android:id="@id/bottomCard">
        <Layout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginBottom="50dp"
            android:layout_marginEnd="50dp"
            android:layout_marginStart="50dp"
            android:layout_marginTop="50dp" />
        <Transform
            android:scaleX="0.90"
            android:scaleY="0.90" />
    </Constraint>
</ConstraintSet>
<ConstraintSet
    android:id="@+id/offScreenLike"
    app:deriveConstraintsFrom="@id/like">
    <!-- ... -->
    <Constraint android:id="@id/bottomCard">
        <Transform
            android:scaleX="1"
            android:scaleY="1" />
    </Constraint>
</ConstraintSet>

使卡片可以无限多

我们需要手动添加代码来让卡片滑动到offscreenLike阶段的时候,重置回rest阶段

    private fun setInfinite() {
        motionLayout.setTransitionListener(object : TransitionAdapter() {
            override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
                when (currentId) {

                    R.id.offscreenLike, R.id.offscreenPass -> {
                        motionLayout?.progress = 0f
                        motionLayout?.setTransition(R.id.rest, R.id.like)
                    }
                }
            }
        })
    }

添加额外的View

我们最后再在xml中添加两个View

        <ImageView
            android:id="@+id/likeIcon"
            android:src="@drawable/ic_baseline_favorite_border"
            android:tint="#fbc02d"
            android:background="@drawable/backround_circle"
            android:layout_height="0dp"
            android:layout_width="0dp" />

,然后更改MotionScene中相应的ConstraintSet,

    <ConstraintSet android:id="@+id/like"
        app:deriveConstraintsFrom="@id/rest">
		..........

        <Constraint android:id="@+id/likeIcon">

            <Layout
                android:layout_width="100dp"
                android:layout_height="100dp"
                app:layout_constraintBottom_toBottomOf="@id/topCard"
                app:layout_constraintEnd_toEndOf="@id/topCard"
                app:layout_constraintStart_toStartOf="@id/topCard"
                app:layout_constraintTop_toTopOf="@id/topCard" />

            <Transform
                android:scaleX="1"
                android:scaleY="1" />

            <PropertySet android:alpha="1" />

        </Constraint>

以及

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

        <Constraint android:id="@+id/likeIcon">

            <Layout
                android:layout_width="40dp"
                android:layout_height="40dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <Transform
                android:scaleX="0.5"
                android:scaleY="0.5" />

            <PropertySet android:alpha="0" />


        </Constraint>

这样,就实现了我们想要的效果

最后附上github项目地址,其中用到了这个动画

参考:Medium