片段在动画开始时从视图层次结构中移除

Fragment Removed from View Hierarchy at Start of Animation

我在另外两个视图之间放置了一个片段,我需要顺利地过渡出视图。我已经设置了片段动画以将片段缩放到视图之外。这很好用。但是,一旦动画开始,片段的视图就会立即从视图层次结构中取出,导致片段下方的视图折叠在片段的视图之上。如何让fragment下方的view随着fragment的缩放平滑上下过渡?

下面是一个例子:

这里是MainActivity.java

public class MainActivity extends AppCompatActivity {

public final String TAG = "MainActivity";

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Button btnHide = findViewById(R.id.btn_hide);
    Button btnShow = findViewById(R.id.btn_show);

    final FragmentManager fm = getSupportFragmentManager();
    final Fragment fragment = fm.findFragmentById(R.id.frag_middle);

    btnHide.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if(fragment != null) {
                FragmentTransaction ft = fm.beginTransaction();
                ft.setCustomAnimations(R.anim.fade_in, R.anim.fade_out);
                ft.hide(fragment);
                ft.commit();
            }else{
                Log.d(TAG, "fragment is null!");
            }
        }
    });

    btnShow.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if(fragment != null){
                FragmentTransaction ft = fm.beginTransaction();
                ft.setCustomAnimations(R.anim.fade_in, R.anim.fade_out);
                ft.show(fragment);
                ft.commit();
            }else{
                Log.d(TAG, "fragment is null!");
            }
        }
    });

}

}

这里是activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="16dp"
tools:context=".MainActivity">

<TextView
    android:id="@+id/tv_top"
    android:layout_height="wrap_content"
    android:layout_width="0dp"
    android:layout_marginStart="20dp"
    android:layout_marginEnd="20dp"
    android:padding="16dp"
    android:text="TextViewTop"
    android:textAlignment="center"
    android:textStyle="bold"
    android:textColor="@android:color/white"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    android:background="@color/colorPrimary"/>

<fragment
    android:id="@+id/frag_middle"
    android:name="my.packagename.mytestapplication.TestFragment"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    app:layout_constraintTop_toBottomOf="@id/tv_top"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"/>

<TextView
    android:id="@+id/tv_bottom"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginStart="20dp"
    android:layout_marginEnd="20dp"
    android:padding="16dp"
    android:text="TextViewBottom"
    android:textAlignment="center"
    android:textStyle="bold"
    android:textColor="@android:color/white"
    app:layout_constraintTop_toBottomOf="@+id/frag_middle"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    android:background="@color/colorPrimary"/>

<Button
    android:id="@+id/btn_hide"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hide Fragment"
    app:layout_constraintTop_toBottomOf="@id/tv_bottom"
    app:layout_constraintStart_toStartOf="parent"
    />

<Button
    android:id="@+id/btn_show"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Show Fragment"
    app:layout_constraintTop_toBottomOf="@id/tv_bottom"
    app:layout_constraintEnd_toEndOf="parent"/>

这里是动画的例子xml:

<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000">

<scale
    android:fromYScale="0"
    android:toYScale="1"
    android:fromXScale="1"
    android:toXScale="1" />

如有任何帮助,我们将不胜感激!谢谢

经过几个小时的努力,我想出了自己的解决方案。它并不完美,我不确定这样做是否正确或最好,但就是这样。

我完全放弃了使用 FragmentTransaction 的 setCustomAnimations 方法的想法,转而使用 ObjectAnimator 为片段的视图设置动画。

我从上面更改的唯一代码是两个按钮 onClick 侦听器。这是更新后的 onClickListener 代码:

 btnHide.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if(fragment != null  && fragment.getView() != null) {

                final ViewGroup.LayoutParams params = fragment.getView().getLayoutParams();
                final int startHeight = fragment.getView().getMeasuredHeight();
                Log.d(TAG, "startHeight is " + startHeight);
                ObjectAnimator animator = ObjectAnimator.ofFloat(fragment.getView(), "scaleY", 0f).setDuration(500);

                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        float yVal = (float)animation.getAnimatedValue();
                        params.height = (int)(yVal * startHeight);
                        fragment.getView().requestLayout();
                    }
                });

                animator.addListener(new Animator.AnimatorListener() {
                    @Override public void onAnimationStart(Animator animation) {}
                    @Override public void onAnimationEnd(Animator animation) {
                        FragmentTransaction ft = fm.beginTransaction();
                        ft.hide(fragment);
                        ft.commit();
                    }
                    @Override public void onAnimationCancel(Animator animation) {}
                    @Override public void onAnimationRepeat(Animator animation) {}
                });

                animator.start();

            }else{
                Log.d(TAG, "fragment is null!");
            }
        }
    });

    btnShow.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if(fragment != null && fragment.getView() != null){

                final ViewGroup.LayoutParams params = fragment.getView().getLayoutParams();
                fragment.getView().measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                final int endHeight = fragment.getView().getMeasuredHeight();
                Log.d(TAG, "endHeight is " + endHeight);
                ObjectAnimator animator = ObjectAnimator.ofFloat(fragment.getView(), "scaleY", 1f).setDuration(500);

                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        float yVal = (float)animation.getAnimatedValue();
                        Log.d(TAG, "animation height is " + yVal);
                        params.height = (int)(yVal * endHeight);
                        Log.d(TAG, "height is " + params.height);
                        fragment.getView().requestLayout();
                    }
                });

                animator.addListener(new Animator.AnimatorListener() {
                    @Override public void onAnimationStart(Animator animation) {
                        FragmentTransaction ft = fm.beginTransaction();
                        ft.show(fragment);
                        ft.commit();
                    }
                    @Override public void onAnimationEnd(Animator animation) {}
                    @Override public void onAnimationCancel(Animator animation) {}
                    @Override public void onAnimationRepeat(Animator animation) {}
                });

                animator.start();

            }else{
                Log.d(TAG, "fragment is null!");
            }
        }
    });

这是现在的样子:

它仍然不完美,但它更接近我要找的东西。

使用 Transition API 可以更简单地实现此动画。 TransitionManager 可以处理和动画化视图或视图层次结构的变化。当您 hide/show 片段时,您只需更改片段视图的可见性。

private void toggle() {
    final FragmentManager fm = getSupportFragmentManager();
    final Fragment fragment = fm.findFragmentById(R.id.frag_middle);
    ViewGroup parentViewGroup = findViewById(R.id.parentViewGroup);

    TransitionManager.beginDelayedTransition(parentViewGroup, new AutoTransition());
    FragmentTransaction ft = fm.beginTransaction();
    if (show) {
        ft.show(fragment);
    } else {
        ft.hide(fragment);
    }
    ft.commitNow();

    show = !show;
}

这里重要的是使用 commitNow 方法而不是 commit。因为 commitNow 立即执行片段事务,这就是 TransitionManager 处理视图更改的原因。使用 commit 它不起作用,因为 commit 异步执行事务。

<android.support.constraint.ConstraintLayout 
    ...
    android:id="@+id/parentViewGroup">

    <fragment
        android:id="@+id/frag_middle"
        android:name="TestFragment"
        android:layout_width="300dp"
        android:layout_height="200dp"
        app:layout_constraintTop_toBottomOf="@id/tv_top"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>

</android.support.constraint.ConstraintLayout>

我在这个 xml 中添加了 widthheightfragment。这是结果: