Search code examples
androidandroid-animationandroid-motionlayout

How to set a position of ImageView using Motion Layout


I know how to moving object (imageview, for example) to end from start point,

((MotionLayout)findViewById(R.id.motionLayout)).transitionToEnd();

but how move object to keyPosition, when i clicked a button?

And if its impossible, which tools i must use, to do this?

example screenshot

motion layout code:

motion_06_keyframe.xml

<androidx.constraintlayout.motion.widget.MotionLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/motionLayout"
    app:layoutDescription="@xml/scene_06"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <View
        android:id="@+id/button"
        android:background="@color/colorAccent"
        android:layout_width="64dp"
        android:layout_height="64dp" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="36dp"
        android:text="Button"
        android:onClick="lol"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.47"
        app:layout_constraintStart_toStartOf="parent" />

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

scene_06.xml

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

    <Transition
        motion:constraintSetStart="@+id/start"
        motion:constraintSetEnd="@+id/end"
        motion:duration="1000"
        motion:motionInterpolator="linear">
        <OnSwipe
            motion:touchAnchorId="@+id/button"
            motion:touchAnchorSide="right"
            motion:dragDirection="dragRight" />

        <KeyFrameSet>
            <KeyPosition
                motion:keyPositionType="pathRelative"
                motion:percentY="-0.25"
                motion:framePosition="50"
                motion:motionTarget="@id/button"/>
        </KeyFrameSet>
    </Transition>

    <ConstraintSet android:id="@+id/start">
        <Constraint
            android:id="@id/button"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:layout_marginStart="8dp"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintStart_toStartOf="parent"
            motion:layout_constraintTop_toTopOf="parent">
            <CustomAttribute
                motion:attributeName="BackgroundColor"
                motion:customColorValue="#D81B60"/>
        </Constraint>
    </ConstraintSet>

    <ConstraintSet android:id="@+id/end">
        <Constraint
            android:id="@id/button"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:layout_marginEnd="8dp"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintTop_toTopOf="parent">
            <CustomAttribute
                motion:attributeName="BackgroundColor"
                motion:customColorValue="#9999FF"/>
        </Constraint>
    </ConstraintSet>

</MotionScene>

Solution

  • KeyPosition defines the state the animation should go through, u can't stop the transition there with default tools. The best choice is to divide your transition into two:

    1) start to middle
    2) middle to end

    But the thing is, MotionLayout won't know where you want to transition to if the current state is middle. It takes the first transition in the xml file, so you will have to change it manually to perform the second transition.

    View button; // the button
    boolean weWantTransitionToEnd; // depends on your logic, you can throw it away
    MotionLayout motionLayout; // the layout
    button.setOnClickListener(view -> {
                    switch (motionLayout.getCurrentState()) {
                        case R.id.start:
                            motionLayout.transitionToEnd();
                            break;
                        case R.id.middle:
                            if (weWantTransitionToEnd) {
                                motionLayout.setTransition(R.id.middle, R.id.end);
                                motionLayout.transitionToEnd();
                            } else {
                                motionLayout.setTransition(R.id.start, R.id.middle);
                                motionLayout.transitionToStart();
                            }
                            break;
                        case R.id.end:
                            motionLayout.transitionToStart();
                            break;
                    }
                });
    

    The user won't be able to swipe this correctly though. If you want the user to be able to swipe it in both directions and this square to stick in the middle in some cases, you will have to extend MotionLayout class, leave only one transition (from start to end) and implement your own logic in it.

    The key would be overriding OnTouchEvent(MotionEvent event) and dispatchTouchEvent(MotionEvent event) methods.

        private boolean isThumbDown;
        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    isThumbDown = true;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    isThumbDown = false;
                    break;
            }
            return super.dispatchTouchEvent(ev);
        }
    
        @SuppressLint("ClickableViewAccessibility")
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (getProgress() > 0.47f && getProgress() < 0.53f && !isThumbDown) return false;
            return super.onTouchEvent(event);
        }
    

    That way the transition could stop somewhere in between 47% and 53% in the middle and the user would be able to continue swiping in both directions.