【问题标题】:Android MotionLayout motionStaggeredAndroid MotionLayout 运动交错
【发布时间】:2020-04-19 14:18:12
【问题描述】:

我试图让我的视图使用 MotionLayout 进行动画处理,但希望某些约束在其他约束之前进行动画处理。我认为这是motion的目的:Transition 的交错属性,但我不明白它是如何工作的,也没有任何地方成功工作的例子。随着 MotionLayout 的更新版本,我们似乎应该为各个约束设置 motion:motionStagger 但我似乎无法让它按需要交错。我能找到的唯一文档是 here 解释增强型交错 API,但我不明白如何使用它。

我在下面添加了我的 MotionLayout 代码。作为参考,我使用的是 2.0.0-beta3' 版本的 ConstraintLayout

<?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="300"
    motion:motionInterpolator="easeInOut"
    motion:staggered="0.4" />

<ConstraintSet android:id="@+id/start">
    <Constraint android:id="@id/translucentOverlay">
        <Layout
            android:layout_width="5dp"
            android:layout_height="5dp"
            motion:layout_constraintBottom_toBottomOf="@id/imageBorder"
            motion:layout_constraintEnd_toEndOf="@id/imageBorder"
            motion:layout_constraintStart_toStartOf="@id/imageBorder"
            motion:layout_constraintTop_toTopOf="@id/imageBorder" />
        <CustomAttribute
            motion:attributeName="alpha"
            motion:customFloatValue="0.0" />
        <Motion motion:motionStagger="2" />
    </Constraint>

    <Constraint android:id="@id/imageBorder">
        <Layout
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginBottom="8dp"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
        <CustomAttribute
            motion:attributeName="crossfade"
            motion:customFloatValue="0" />
        <Motion motion:motionStagger="2" />
    </Constraint>

    <Constraint android:id="@id/imageBackground">
        <Layout
            android:layout_width="32dp"
            android:layout_height="32dp"
            motion:layout_constraintBottom_toBottomOf="@id/imageBorder"
            motion:layout_constraintEnd_toEndOf="@id/imageBorder"
            motion:layout_constraintStart_toStartOf="@id/imageBorder"
            motion:layout_constraintTop_toTopOf="@id/imageBorder" />
        <Motion motion:motionStagger="2" />
    </Constraint>

    <Constraint android:id="@id/profileInitialText">
        <Layout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            motion:layout_constraintBottom_toBottomOf="@id/imageBackground"
            motion:layout_constraintEnd_toEndOf="@id/imageBackground"
            motion:layout_constraintStart_toStartOf="@id/imageBackground"
            motion:layout_constraintTop_toTopOf="@id/imageBackground" />
        <CustomAttribute
            motion:attributeName="alpha"
            motion:customFloatValue="1.0" />
        <Motion motion:motionStagger="2" />
    </Constraint>

    <Constraint android:id="@id/profileImage">
        <Layout
            android:layout_width="32dp"
            android:layout_height="32dp"
            motion:layout_constraintBottom_toBottomOf="@id/imageBackground"
            motion:layout_constraintEnd_toEndOf="@id/imageBackground"
            motion:layout_constraintStart_toStartOf="@id/imageBackground"
            motion:layout_constraintTop_toTopOf="@id/imageBackground" />
        <Motion motion:motionStagger="2" />
    </Constraint>

    <Constraint android:id="@id/name">
        <Layout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="128dp"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
        <CustomAttribute
            motion:attributeName="alpha"
            motion:customFloatValue="0.0" />
        <Motion motion:motionStagger="5" />
    </Constraint>

    <Constraint android:id="@id/description">
        <Layout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toBottomOf="@id/name" />
        <CustomAttribute
            motion:attributeName="alpha"
            motion:customFloatValue="0.0" />
        <Motion motion:motionStagger="5" />
    </Constraint>
</ConstraintSet>

<ConstraintSet android:id="@+id/end">
    <Constraint android:id="@id/translucentOverlay">
        <Layout
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
        <CustomAttribute
            motion:attributeName="alpha"
            motion:customFloatValue="1.0" />
        <Motion motion:motionStagger="2" />
    </Constraint>

    <Constraint android:id="@id/imageBorder">
        <Layout
            android:layout_width="88dp"
            android:layout_height="88dp"
            motion:layout_constraintBottom_toBottomOf="@id/imageBackground"
            motion:layout_constraintEnd_toEndOf="@id/imageBackground"
            motion:layout_constraintStart_toStartOf="@id/imageBackground"
            motion:layout_constraintTop_toTopOf="@id/imageBackground" />
        <CustomAttribute
            motion:attributeName="crossfade"
            motion:customFloatValue="1" />
        <Motion motion:motionStagger="2" />
    </Constraint>

    <Constraint android:id="@id/imageBackground">
        <Layout
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:layout_marginTop="64dp"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
        <Motion motion:motionStagger="2" />
    </Constraint>

    <Constraint android:id="@id/profileInitialText">
        <Layout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            motion:layout_constraintBottom_toBottomOf="@id/imageBackground"
            motion:layout_constraintEnd_toEndOf="@id/imageBackground"
            motion:layout_constraintStart_toStartOf="@id/imageBackground"
            motion:layout_constraintTop_toTopOf="@id/imageBackground" />
        <Motion motion:motionStagger="2" />
        <CustomAttribute
            motion:attributeName="alpha"
            motion:customFloatValue="0.0" />
    </Constraint>
    <Constraint android:id="@id/profileImage">
        <Layout
            android:layout_width="70dp"
            android:layout_height="70dp"
            motion:layout_constraintBottom_toBottomOf="@id/imageBackground"
            motion:layout_constraintEnd_toEndOf="@id/imageBackground"
            motion:layout_constraintStart_toStartOf="@id/imageBackground"
            motion:layout_constraintTop_toTopOf="@id/imageBackground" />
        <Motion motion:motionStagger="2" />
    </Constraint>

    <Constraint android:id="@id/name">
        <Layout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toBottomOf="@id/profileImage" />
        <CustomAttribute
            motion:attributeName="alpha"
            motion:customFloatValue="1.0" />
        <Motion motion:motionStagger="5" />
    </Constraint>

    <Constraint android:id="@id/description">
        <Layout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="16dp"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toBottomOf="@id/name" />
        <CustomAttribute
            motion:attributeName="alpha"
            motion:customFloatValue="1.0" />
        <Motion motion:motionStagger="5" />
    </Constraint>
</ConstraintSet>

【问题讨论】:

    标签: android animation layout motion


    【解决方案1】:

    交错的实际数学可能有点令人困惑,但在实践中

    交错

    每个动画视图都被赋予一个 Stager 值 (app:motionStagger) 默认情况下,视图的交错值是距视图列表中最顶部视图的曼哈顿距离。您可以通过属性手动设置值

    这会为每个标记有motionStagger的视图分配一个浮点交错值(忽略未标记的视图)。首先启动具有最低浮点值 (V0) 的视图。具有最高浮点值 (Vn) 的视图最后启动。

    • 对于交错值 S(Vi) 的任何视图
    • TS 的过渡交错值(从 0.0 - 1.0)
    • 动画的持续时间是duration
    • 观看动画时长 DS = 时长 * (1 -TS)
    • 称交错分数 SFi = (S(Vi) - S(V0)) / (S(Vn) - S(V0))
    • 视图在以下位置开始动画:(duration-DS) * SFi

    这个数学可能会令人困惑。所以一个实际的例子 如果我有 3 个视图 View1、View2、View3,我将 motionStagger 分别设置为 2、5 和 7,并将动画持续时间设置为 5 秒。 当我将转换交错设置为 0.4 时,进度如下:

    The animation duration is 3.0 sec = 5 * (1- 0.4)
    
    View1 stagger fraction = 0 = (2-2)/(7-2)
    View1 starts at 0.0 sec 
    View1 end    at 3.0 sec (0.0 + 3.0)
    
    View2 stagger fraction = 0.6 = (5-2)/(7-2)
    View2 starts at 1.2 sec (5.0-3.0) * 0.6
    View2 ends   at 4.2 sec 1.2 + 3.0
    
    View3 stagger fraction = 1
    View3 starts at 2.0 sec (5.0 - 3.0) * 1
    View3 ends   at 5.0 sec 
    

    【讨论】:

    • 这对数学分解很有帮助!我正在尝试实现它,但似乎无法在背景/图像之后获得名称/文本以进行动画处理。我将我的 motionScene 代码添加到我的问题中。我错过了什么?
    • 有趣的是,我使用视图上的确切值运行了这个测试,首先输入的是motionStagger设置为7的测试,最后输入的是motionStagger设置为2的测试。似乎你的数学在某个地方有点不稳定。不过感谢您的帮助。分解它让我对如何进行有了更好的了解!
    • 是的,我可能把它弄反了。这将是 -0.4 的行为。对于 0.4,交错分数将是 1- ...
    • 我看到的唯一大问题是你的持续时间是 300。这意味着整个动画需要 300 毫秒。 (约 10 帧)每个动作将持续 180 毫秒。尝试 3000(3 秒)
    【解决方案2】:

    好的,所以在搞砸了很长时间之后,经过大量的试验和错误,并研究了this发布更新中给出的方程式,这就是我想出的。

    上面的链接文章为我们提供了一些有些令人困惑的方程式

    Let The motionStagger value is S(Vi) The overall stagger value is stagger (from 0.0 - 1.0) The duration of the animation is duration The views animation duration = duration * (1 - stagger) The view starts animating at duration * (stagger - stagger * (S(Vi) - S(V0)) / (S(Vn) - S(V0)))

    确定过渡阶段值:

    要确定您希望整体错开的效果,请考虑您尝试错开的观看次数。 我在上面链接的文章指出viewDuration = totalDuration*(1 - stagger),所以我们可以重新排列这个等式成为stagger = 1 - (viewDuration / totalDuration)。就我而言,由于我想在视图进入时有三个不同的时刻,我想让我的viewDuration / totalDuration 大约是1/3。为了简化数学,我选择将我的交错设置为0.6,使每个 viewDuration 为 400。所以我的转换代码如下所示

    <Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@+id/start"
        motion:duration="1000"
        motion:motionInterpolator="easeInOut"
        motion:staggered="0.6" />
    

    您会注意到我将持续时间增加到 1000 以更清楚地查看交错(一旦您确定了交错值,可以更新此处的持续时间,并且交错应适当缩放以适应时间范围)。

    确定个人观点的交错值:

    所以现在我们需要弄清楚将什么作为 ?在&lt;Motion motion:motionStagger="?" /&gt;

    这就是数学变得非常复杂的地方。对于我们要设置交错的每个视图,它们应该按交错值排序。我们给出的等式(经过修改以使其比文章更具可读性)是:

    animationStartTime = totalDuration * (stagger - stagger * ((staggerCurrentView - lowestStaggerValue)/(highestStaggerValue - lowestStaggerValue))
    

    这肯定有点复杂,但我可以用我的例子来分解它。

    因此,对于我的示例,我们已经讨论了如何让三个视图稍微均匀地错开(这就是我们选择 0.6 的错开值的原因)。根据下面等式的逆结构,我知道motionStagger 值最高的视图将首先生成动画。

    假设我们有三个视图,一个我想排在第一位的 ImageView,一个我想排在第二位的 TextView,以及一个我想排在第三位的 Button。所以我会给 ImageView 分配一个motionStagger 值为3,给TextView 分配一个motionStagger 值为2,给TextView 分配一个motionStagger 值为1。让我们在这里进行计算:

    Stagger value = 0.6
    motionStaggerValues = 3 (for ImageView), 2 (for TextView), and 1(for Button) 
    ImageView animationStartTime = 1000 * (0.6 - 0.6 * ((3-1)/(3-1)))
        = 1000 * (0.6 - 0.6 * (1)) = 1000 * 0 = 0
    

    所以 ImageView 从 0 开始动画并动画 400 毫秒(如上一节所示)。 现在让我们计算一下TextView

    Stagger value = 0.6
    motionStaggerValues = 3 (for ImageView), 2 (for TextView), and 1(for Button) 
    TextView animationStartTime = 1000 * (0.6 - 0.6 * ((2-1)/(3-1)))
        = 1000 * (0.6 - 0.6 * (1/2)) = 1000 * 0.3 = 300
    

    所以 TextView 从 300 开始动画并持续 400 毫秒。

    最后,我们来计算一下Button的开始时间:

    Stagger value = 0.6
    motionStaggerValues = 3 (for ImageView), 2 (for TextView), and 1(for Button) 
    TextView animationStartTime = 1000 * (0.6 - 0.6 * ((1-1)/(3-1)))
        = 1000 * (0.6 - 0.6 * (0)) = 1000 * 0.6 = 600
    

    所以 Button 从 600 开始动画并持续 400 毫秒。

    这些值可以根据您选择的 motionStagger 值进行移动和交错。为了解释起见,我试图让它尽可能简单,但根据您要完成的工作,它可能会变得非常复杂。这是我上面概述的示例的最终代码的样子。

    <ConstraintSet android:id="@+id/start">
        <Constraint android:id="@id/imageView">
            ...
            <Motion motion:motionStagger="3" />
        </Constraint>
    
        <Constraint android:id="@id/textView">
            ...
            <Motion motion:motionStagger="2" />
        </Constraint>
    
        <Constraint android:id="@id/button">
            ...
            <Motion motion:motionStagger="1" />
        </Constraint>
    </ConstraintSet>
    

    您需要另一个并行的ConstraintSet 来表示最终状态。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-18
      相关资源
      最近更新 更多