androidx / constraintlayout

ConstraintLayout is an Android layout component which allows you to position and size widgets in a flexible way
Apache License 2.0
1.06k stars 177 forks source link

MotionLayout TransitionListener - events don't fire for OnSwipe autocomplete #678

Open Barrio17 opened 1 year ago

Barrio17 commented 1 year ago

I am trying to perform an action when a Transition contained in my fragment's MotionLayout has completed.

To do so, I am using a TransitionListener and invoke the required action in onTransactionCompleted(). The transition uses an OnSwipe action and has autoCompleteMode set to spring.

What I'm seeing is that if I complete the swipe gesture with my finger down the whole time, I can see events firing for onTransitionChange() throughout and then eventually an onTransitionCompleted() call. However.... if I allow the 'spring' to autocomplete the animation for me, I see the onTransitionChange() events stop firing at the progress point where i lifted my finger and onTransitionCompleted() is never called. The animation itself is completing fine - it just seems that the TransitionListener events do not fire during the autocomplete phase of the transition. Once the animation has completed, if i drag the target view a little further, the events kick in again and I see onTransitionCompleted() call fire.

I found the below issue which sounds like this may have been tracked and resolved previously but has since regressed: https://issuetracker.google.com/issues/149423772

Some code snippets:

Listener in fragment:

motionLayout.setTransitionListener(object: MotionLayout.TransitionListener {
    override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
       // invoke required action
       ...
    }
    override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float) {
        ...
    }
    override fun onTransitionStarted(motionLayout: MotionLayout?, startId: Int, endId: Int) {}
    override fun onTransitionTrigger(motionLayout: MotionLayout?, triggerId: Int, positive: Boolean, progress: Float) {}
  })

Relevant Transition from my motion scene:

<Transition
      android:id="@+id/unlock_transition"
      app:constraintSetStart="@+id/swipe_start"
      app:constraintSetEnd="@+id/swipe_end"
      app:motionInterpolator="easeInOut"
      app:duration="1400">
      <OnSwipe
          app:limitBoundsTo="@id/unlock_cover_card"
          app:touchAnchorSide="bottom"
          app:dragDirection="dragUp"
          app:autoCompleteMode="spring"
          app:springStiffness="25" />
      <KeyFrameSet>
          <KeyAttribute
              app:framePosition="85"
              app:motionTarget="@id/unlock_cover_card"
              android:translationY="-220dp"
              android:rotation="-8" />
          <KeyAttribute
              app:framePosition="85"
              app:motionTarget="@id/key_card"
              android:translationY="220dp"
              android:rotation="4" />
      </KeyFrameSet>
</Transition>

As it stands I can't find a way to trigger the action when my transition has completed without the user having dragged 100% of the way through the swipe. Any help much appreciated!

Extra info:

Barrio17 commented 1 year ago

Update: I've just noticed that the events also seem to fire ok when using autoCompleteMode="continuousVelocity" instead of autoCompleteMode="spring" in the <OnSwipe>.

This will let me crack on for now at least, though would be nice to be able to use the spring mode as it definitely better matches the animation design I'm currently implementing.

jafu888 commented 1 year ago

Completion was judged by touch driver reaching 0 or 1 with spring it over shoots so that does not work very well.

We trying to improve this for the next release.

GuessWh0o commented 1 year ago

Same here. I have debugged MotionLayoutState class, and when I have touchUp: 'autocomplete' mode enabled - I continuously receive MotionAnimationCommand.Animate with newProgress set to 1.0, and because of that I can not trigger manually animateTo anymore. My event is immediately overwritten by one of those events from stuck animation.

I was able to somewhat fix it by having touchUp: 'stop' for the cost of having my screen in unwanted state, but I would really like my animation to autocomplete properly. 🌵

Here is my Transitions block if it helps.

Transitions: {
    default: {
      from: 'start',
      to: 'end',
      duration: 800,
      onSwipe: {
        anchor: 'myAnchor',
        side: 'middle',
        touchUp: 'autocomplete',
        mode: 'velocity'
      },
    }