如何使用 Motion Layout 设置 ImageView 的位置

How to set a position of ImageView using Motion Layout

我知道如何将对象(例如图像视图)从起点移动到终点,

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

但是如何在我单击按钮时将对象移动到 keyPosition

如果不可能,我必须使用哪些工具才能做到这一点?

example screenshot

运动布局代码:

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>

KeyPosition 定义动画应该经历的状态,你不能用默认工具在那里停止过渡。 最好的选择是将你的过渡分成两部分:

1) 从头到尾
2) 中到尾

但问题是,如果当前状态为中间状态,MotionLayout 将不知道您想转换到哪里。它采用 xml 文件中的第一个转换,因此您必须手动更改它才能执行第二个转换。

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;
                }
            });

不过,用户将无法正确刷卡。 如果您希望用户能够在两个方向上滑动它并且在某些情况下这个方块会粘在中间,您将不得不扩展 MotionLayout class,只留下一个过渡(从开始到end) 并在其中实现自己的逻辑。

关键是覆盖 OnTouchEvent(MotionEvent event)dispatchTouchEvent(MotionEvent event) 方法。

    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);
    }

这样过渡可以在中间的 47% 和 53% 之间的某个地方停止,用户将能够继续在两个方向上滑动。