片段在动画开始时从视图层次结构中移除
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 中添加了 width
和 height
到 fragment
。这是结果:
我在另外两个视图之间放置了一个片段,我需要顺利地过渡出视图。我已经设置了片段动画以将片段缩放到视图之外。这很好用。但是,一旦动画开始,片段的视图就会立即从视图层次结构中取出,导致片段下方的视图折叠在片段的视图之上。如何让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 中添加了 width
和 height
到 fragment
。这是结果: