I have the following MotionLayout:
The Transition XML is as follows (I omitted the keyframes because they don’t matter for this example).
<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.
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. 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.
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. How do I know this? If I set my motionInterpolator to linear, I experience no issues. Before and after a transitionToStart()/End()
, the OnSwipe behavior works as intended and tracks my finger like it’s supposed to.
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)?
2
Answers
After a lot of messing around with it, I have found the solution.
To restate what my goal is:
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).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. 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.
So let's create two Transitions for this:
Notice that both of them are identical transitions, both transitioning from the "collapsed" state to the "expanded" state, both with the same 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").
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()
.The latter is easy: just set the transition right before calling
transitionToStart/End()
.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.
Notice how we only set the transition when our finger is dragging across the screen (this magical variable
dragging
). The last step now is to make suredragging
istrue
when our finger is dragging across the screen, andfalse
when our finger has released the screen.For that, we'll override
onInterceptTouchEvent
on our MotionLayout so we can keep track of this.This
isInTarget
value will depend on your own design.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!
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.
This some what easy to do in the MotionEditor but it is messy
To transition
The power of this approach is you can customize the transitions with there own
keyframes
Longer term I will be fixing the bug and adding a few features to make the use case simpler.