UI 个组件在 TransitionManager 动画中不可见

UI components not visible in TransitionManager animations

这是 Java Android 应用程序中的一个问题,在 MainActivity 中有一个非常简单的自定义可拖动抽屉。

这是它在 Android 10(API 级别 29)模拟器上运行时的行为,这是预期的行为。

但问题是,当它在Android L (API level 21) 模拟器上运行时,它的行为出乎意料如下:

在动画期间,UI 组件不可见。但是当应用程序进入后台并返回时,它们就会变得可见。

应用程序的实现细节:

为了检测滑动/拖动触摸手势,使用了GestureDetectorCompat。 当检测到滑动手势时,将启动自定义抽屉打开动画。 动画是使用 ConstraintSetConstraintLayoutTransitionManager.

实现的

这是触摸手势检测和 TransitionManager 动画的实现。

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private boolean mIsDrawerOpened;
    private ConstraintLayout mRootConstraintLayout;
    private final ConstraintSet mDrawerClosedConstraintSet = new ConstraintSet();
    private final ConstraintSet mDrawerOpenedConstraintSet = new ConstraintSet();
    private GestureDetectorCompat mGestureDetector;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_drawer_closed);

        // Drawer is initially closed
        mIsDrawerOpened = false;

        mRootConstraintLayout = findViewById(R.id.rootConstraintLayout);

        mDrawerClosedConstraintSet.clone(this, R.layout.activity_main_drawer_closed);
        mDrawerOpenedConstraintSet.clone(this, R.layout.activity_main_drawer_opened);

        mGestureDetector = new GestureDetectorCompat(
                getApplicationContext(),
                new GestureDetector.SimpleOnGestureListener() {

                    @Override
                    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {

                        // Drag / Fling gesture detected

                        // TODO: Recongnize unwanted drag / fling gestures and ignore them.

                        TransitionManager.beginDelayedTransition(mRootConstraintLayout);

                        // Drawer is closed?
                        if(!mIsDrawerOpened) {
                            // Open the drawer
                            mDrawerOpenedConstraintSet.applyTo(mRootConstraintLayout);
                            mIsDrawerOpened = true;
                        }

                        return true;
                    }

                    @Override
                    public boolean onSingleTapUp(MotionEvent e) {

                        // Single tap detected

                        // TODO: If user has tapped on the drawer, do not close it.

                        TransitionManager.beginDelayedTransition(mRootConstraintLayout);

                        // Drawer is opened?
                        if(mIsDrawerOpened) {
                            // Close the drawer
                            mDrawerClosedConstraintSet.applyTo(mRootConstraintLayout);
                            mIsDrawerOpened = false;
                        }

                        return true;
                    }

                    @Override
                    public boolean onDown(MotionEvent e) {
                        return true;
                    }
                }
        );
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mGestureDetector.onTouchEvent(event);
        return super.onTouchEvent(event);
    }
}

这是关闭抽屉的布局XML。

res/layout/activity_main_drawer_closed.xml

<ConstraintLayout
    android:id="@+id/rootConstraintLayout">

    <ConstraintLayout
        android:id="@+id/drawerConstraintLayout"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        <!-- Constraint start (left) of drawer to end (right) of parent (drawer is outside the parent) -->
        app:layout_constraintStart_toEndOf="parent"
        ... >

        <Button
            android:id="@+id/button1"
            android:text="1"
            ... />

        <Button
            android:id="@+id/button2"
            android:text="2"
            ... />

    </ConstraintLayout>

    <ImageView
        android:id="@+id/notch"
        android:src="@drawable/drawer_notch"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/drawerConstraintLayout"
        ... />

</ConstraintLayout>

这是打开抽屉的布局XML。

res/layout/activity_main_drawer_opened.xml

<ConstraintLayout
    android:id="@+id/rootConstraintLayout">

    <ConstraintLayout
        android:id="@+id/drawerConstraintLayout"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        <!-- Constraint end (right) of drawer to end (right) of parent (drawer is inside the parent) -->
        app:layout_constraintEnd_toEndOf="parent"
        ... >

        <Button
            android:id="@+id/button1"
            android:text="1"
            ... />

        <Button
            android:id="@+id/button2"
            android:text="2"
            ... />

    </ConstraintLayout>

    <ImageView
        android:id="@+id/notch"
        android:src="@drawable/drawer_notch"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/drawerConstraintLayout"
        ... />

</ConstraintLayout>

来自这 2 个布局的约束集被用作动画的开始和结束关键帧。

最低 SDK 版本设置为 API 级别 19。

build.gradle

android {
    defaultConfig {
        minSdkVersion 19
        ...
    }
    ...
}

可以在 this GitHub gist 中找到完整的实现。

要按预期执行此操作,您需要将 android:clipChildren="false" 添加到根 ViewGroup 中,在您的情况下,在布局 activity_main_drawer_closed.xml 中是 ConstaintLayout。当然,这种解决方案仅适用于您的视图在视口之外的情况。

我不知道为什么这种行为因 Android 的版本而异。理论上,从 Android Marshmallow 开始,当您开始场景转换时,根视图因 TransitionManager 和重绘而变得无效。