简体   繁体   English

MotionLayout OnSwipe spring 物理在 transitionToStart/End() 后停止工作

[英]MotionLayout OnSwipe spring physics stop working after transitionToStart/End()

I have the following MotionLayout:我有以下 MotionLayout:

The Transition XML is as follows (I omitted the keyframes because they don't matter for this example).过渡 XML 如下(我省略了关键帧,因为它们对本示例无关紧要)。

<Transition
    motion:constraintSetStart="@id/collapsed"
    motion:constraintSetEnd="@id/expanded"
    motion:duration="500"
    motion:motionInterpolator="cubic(0.2,0,0,1)">
    <OnSwipe
        motion:springDamping="63.973"
        motion:autoCompleteMode="spring"
        motion:onTouchUp="autoComplete"
        motion:dragDirection="dragUp"
        motion:touchAnchorId="@id/now_playing_touchable_area"
        motion:touchAnchorSide="top"
        motion:springStopThreshold="1.0E-4"
        motion:springMass="2.6"
        motion:springStiffness="389.76" />
    <KeyFrameSet>
        ...
    </KeyFrameSet>
</Transition>

When I start my app for the first time, and I begin a swipe, you can see that the layout tracks my finger correctly.当我第一次启动我的应用程序并开始滑动时,您可以看到布局正确地跟踪我的手指。 It moves at the same speed as my finger, until I release, after which it uses spring physics to autocomplete to a start/end state.它以与我的手指相同的速度移动,直到我松开,然后它使用 spring 物理来自动完成开始/结束 state。

However, I have also bound a click listener to transitionToStart() and transitionToEnd() (depending on whether we're currently expanded/collapsed), and as soon as I trigger those once, the OnSwipe spring physics and tracking stop working.但是,我还绑定了一个点击监听器到transitionToStart()transitionToEnd() (取决于我们当前是否展开/折叠),一旦我触发它们,OnSwipe spring 物理和跟踪停止工作。 The layout no longer tracks my finger, but now uses the cubic motionInterpolator defined for the Transition.布局不再跟踪我的手指,而是使用为过渡定义的三次运动插值器。

How do I combat this?我该如何对抗这个? I would like to be able to use transitionToStart/End() programmatically, while still keeping spring physics for regular swipes.我希望能够以编程方式使用transitionToStart/End() ,同时仍然保持 spring 物理用于常规滑动。

EDIT: I have some more useful information.编辑:我有一些更有用的信息。 It turns out, after the first transitionToStart/End() , the OnSwipe still uses spring physics, except it seems they are being modified by the cubic motionInterpolator.事实证明,在第一次transitionToStart/End()之后,OnSwipe 仍然使用 spring 物理,但似乎它们正在被三次运动插值器修改 How do I know this?我怎么知道这个? If I set my motionInterpolator to linear, I experience no issues.如果我将 motionInterpolator 设置为线性,我不会遇到任何问题。 Before and after a transitionToStart()/End() , the OnSwipe behavior works as intended and tracks my finger like it's supposed to.transitionToStart()/End()之前和之后, OnSwipe 行为按预期工作,并按预期跟踪我的手指。

So a more specific question now is, how can I get the OnSwipe behavior to ignore the motionInterpolator and just always use linear (and why is it being affected by it in the first place)?所以现在一个更具体的问题是,我怎样才能让 OnSwipe 行为忽略 motionInterpolator 并且总是使用线性(为什么它首先会受到它的影响)?

This is a bug but even when it is fixed it would not do what you want.这是一个错误,但即使它被修复,它也不会做你想要的。

The basic problem is you want two transition between the two states.基本问题是您希望在两种状态之间进行两次转换。 There is a work around that is not elegant.有一个不优雅的解决方法。

  1. create to alias states collapsed2 and expanded2创建别名状态collapsed2和expanded2
  2. create transitions collapsed->expanded2 and expanded->collapsed2创建过渡折叠->展开2和展开->折叠2
  3. create auto transitions expanded2->expanded2 and collapsed->collapsed2创建自动过渡 expand2->expanded2 和 collapsed->collapsed2

This some what easy to do in the MotionEditor but it is messy这在 MotionEditor 中很容易做到,但很混乱

    <ConstraintSet
        android:id="@+id/collapsed2"
        motion:deriveConstraintsFrom="@+id/collapsed" />
    <ConstraintSet
        android:id="@+id/expanded2"
        motion:deriveConstraintsFrom="@+id/expanded" />

    <Transition
        android:id="@+id/click_to_expand"
        motion:constraintSetEnd="@+id/expanded2"
        motion:constraintSetStart="@id/collapsed"
        motion:motionInterpolator="cubic(0.2,0,0,1)"
         />

    <Transition
        android:id="@+id/click_to_collapse"
        motion:constraintSetEnd="@+id/collapsed2"
        motion:constraintSetStart="@id/expanded2"
        motion:motionInterpolator="cubic(0.2,0,0,1)"
        />

    <Transition
        motion:autoTransition="jumpToEnd"
        motion:constraintSetEnd="@+id/expanded"
        motion:constraintSetStart="@id/expanded2" />
    <Transition
        motion:autoTransition="jumpToEnd"
        motion:constraintSetEnd="@+id/collapsed"
        motion:constraintSetStart="@id/collapsed" />

To transition过渡


        if (progress < 0.5) {
            mMotionLayout.setTransition(R.id.click_to_expand);
            mMotionLayout.transitionToState(R.id.expanded2);
        }
        else {
            mMotionLayout.setTransition(R.id.click_to_collapse);
            mMotionLayout.transitionToState(R.id.collapsed2);
        }

The power of this approach is you can customize the transitions with there own keyframes这种方法的强大之处在于您可以使用自己的关键帧自定义过渡

  • interpolator插值器
  • arcMode (motion:pathMotionArc="startVertical") arcMode (motion:pathMotionArc="startVertical")
  • keyFrames关键帧
  • stagger错开

Longer term I will be fixing the bug and adding a few features to make the use case simpler.从长远来看,我将修复错误并添加一些功能以使用例更简单。

After a lot of messing around with it, I have found the solution.经过一番折腾,我找到了解决方案。

To restate what my goal is:重申我的目标是:

  • I want to be able use the OnSwipe feature of my MotionLayout's transition, such that it perfectly tracks my finger and uses spring physics when I release to snap to a starting/ending state.我希望能够使用 MotionLayout 过渡的 OnSwipe 功能,以便它完美地跟踪我的手指并在我释放以捕捉开始/结束 state 时使用 spring 物理。
  • I also want to be able to trigger transitionToStart/End programmatically (which in my case happens to be hooked up to a setOnClickListener on the collapsed bar, see the GIF in the original question).我还希望能够以编程方式触发transitionToStart/End (在我的情况下,它恰好连接到折叠栏上的 setOnClickListener,请参阅原始问题中的 GIF)。

For OnSwipe to track my finger correctly, it needs the motionInterpolator to be linear , but I don't want to make the programmatic triggers to be linear as well, because it doesn't look very natural.为了让 OnSwipe 正确跟踪我的手指,它需要 motionInterpolator 是线性的,但我不想让程序触发器也成为线性的,因为它看起来不太自然。 I want them to be cubic instead, so this means I will need to switch between two transitions depending on whether it was triggered programmatically or by OnSwipe .我希望它们是立方的,所以这意味着我需要在两个转换之间切换,具体取决于它是由编程触发还是由 OnSwipe 触发

So let's create two Transitions for this:所以让我们为此创建两个转换:

<MotionScene
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:motion="http://schemas.android.com/apk/res-auto">

    <Transition
        android:id="@+id/programmatic_transition"
        motion:constraintSetStart="@id/collapsed"
        motion:constraintSetEnd="@id/expanded"
        motion:duration="500"
        motion:motionInterpolator="cubic(0.2,0,0,1)">
        <KeyFrameSet>
            ...
        </KeyFrameSet>
    </Transition>

    <Transition
        android:id="@+id/onswipe_transition"
        motion:constraintSetStart="@id/collapsed"
        motion:constraintSetEnd="@id/expanded"
        motion:duration="500"
        motion:motionInterpolator="linear">
        <OnSwipe
            motion:springDamping="60"
            motion:autoCompleteMode="spring"
            motion:onTouchUp="autoComplete"
            motion:dragDirection="dragUp"
            motion:touchAnchorId="@id/now_playing_anchor"
            motion:touchAnchorSide="top"
            motion:springStopThreshold="0"
            motion:springMass="2.6"
            motion:springStiffness="389.76" />
        <KeyFrameSet>
            ...
        </KeyFrameSet>
    </Transition>
...
</MotionScene>

Notice that both of them are identical transitions, both transitioning from the "collapsed" state to the "expanded" state, both with the same KeyFrameSet.请注意,它们都是相同的过渡,都从“折叠”state 过渡到“展开”state,都具有相同的 KeyFrameSet。 The only differences are that one of them has a cubic motionInterpolator (I called this one "programmatic_transition"), and the other has a linear motionInterpolator paired with an OnSwipe tag with my desired spring physics (called "onswipe_transition").唯一的区别是其中一个有一个三次运动插值器(我称这个为“programmatic_transition”),另一个有一个线性运动插值器,与我想要的 spring 物理(称为“onswipe_transition”)配对的 OnSwipe 标签。

Now that we have our two transitions, we need to programmatically switch between them, depending on whether we're currently swiping, or just using transitionToStart/End() .现在我们有了两个过渡,我们需要以编程方式在它们之间切换,这取决于我们当前是在滑动还是仅使用transitionToStart/End()

The latter is easy: just set the transition right before calling transitionToStart/End() .后者很简单:只需在调用transitionToStart/End()之前设置转换。

fun collapse() {
    motionLayout.setTransition(R.id.programmatic_transition)
    motionLayout.transitionToStart()
}
fun expand() {
    motionLayout.setTransition(R.id.programmatic_transition)
    motionLayout.transitionToEnd()
}

For the swipes, we'll use a transition listener to detect when a swipe is started, so that we can set the "onswipe_transition" instead.对于滑动,我们将使用过渡侦听器来检测何时开始滑动,以便我们可以设置“onswipe_transition”。

motionLayout.setTransitionListener(object : TransitionListener {
    override fun onTransitionTrigger(p0: MotionLayout?, p1: Int, p2: Boolean, p3: Float) {}
    override fun onTransitionStarted(p0: MotionLayout?, p1: Int, p2: Int) {
        if (dragging) {
            motionLayout.setTransition(R.id.onswipe_transition)
        }
    }
    override fun onTransitionChange(p0: MotionLayout, p1: Int, p2: Int, p3: Float) {}
    override fun onTransitionCompleted(p0: MotionLayout, p1: Int) {}
})

Notice how we only set the transition when our finger is dragging across the screen (this magical variable dragging ).注意我们如何在手指在屏幕上拖动时设置过渡(这个神奇的变量dragging )。 The last step now is to make sure dragging is true when our finger is dragging across the screen, and false when our finger has released the screen.现在的最后一步是确保当我们的手指在屏幕上拖动时draggingtrue ,当我们的手指松开屏幕时为false

For that, we'll override onInterceptTouchEvent on our MotionLayout so we can keep track of this.为此,我们将在 MotionLayout 上覆盖onInterceptTouchEvent ,以便我们可以跟踪它。

override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
    val isInTarget = // check if the position of the event is within the view that we want to allow to be dragged from

    return if (isInTarget || dragging) {
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                dragging = true
            }
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                dragging = false
            }
        }
        super.onInterceptTouchEvent(ev)
    } else {
        true
    }
}

This isInTarget value will depend on your own design.isInTarget值将取决于您自己的设计。

So there we have it.因此,我们有它。 I have gone nearly crazy by figuring all of this out over the past two days, but I'm pleased to say that this solution has fixed the problem flawlessly.在过去的两天里,我几乎把所有这些都弄清楚了,但我很高兴地说,这个解决方案完美地解决了这个问题。 Hopefully I'll have helped someone else out there trying to something similar!希望我会帮助其他人尝试类似的事情!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM