Here\'s the scenario: Activity contains fragment A
, which in turn uses getChildFragmentManager()
to add fragments A1
and A2
I was able to come up with a pretty clean solution. IMO its the least hacky, and while this is technically the "draw a bitmap" solution at least its abstracted by the fragment lib.
Make sure your child frags override a parent class with this:
private static final Animation dummyAnimation = new AlphaAnimation(1,1);
static{
dummyAnimation.setDuration(500);
}
@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
if(!enter && getParentFragment() != null){
return dummyAnimation;
}
return super.onCreateAnimation(transit, enter, nextAnim);
}
If we have an exit animation on the child frags, they will be animated instead of blink away. We can exploit this by having an animation that simply draws the child fragments at full alpha for a duration. This way, they'll stay visible in the parent fragment as it animates, giving the desired behavior.
The only issue I can think of is keeping track of that duration. I could maybe set it to a large-ish number but I'm afraid that might have performance issues if its still drawing that animation somewhere.
I understand this may not be able to completely solve your problem, but maybe it will suit someone else's needs, you can add enter
/exit
and popEnter
/popExit
animations to your children Fragment
s that do not actually move/animate the Fragment
s. As long as the animations have the same duration/offset as their parent Fragment
animations, they will appear to move/animate with the parent's animation.
My problem was on parent fragment removal (ft.remove(fragment)), child animations were not happening.
The basic problem is that child fragments are immediately DESTROYED PRIOR to the parents fragment exiting animation.
Child fragments custom animations do not get executed on Parent Fragment removal
As others have eluded to, hiding the PARENT (and not the child) prior to PARENT removal is the way to go.
val ft = fragmentManager?.beginTransaction()
ft?.setCustomAnimations(R.anim.enter_from_right,
R.anim.exit_to_right)
if (parentFragment.isHidden()) {
ft?.show(vehicleModule)
} else {
ft?.hide(vehicleModule)
}
ft?.commit()
If you actually want to remove the parent you should probably set up a listener on you custom animation to know when the animation is ended, so then you can safely do some finalisation on the Parent Fragment (remove). If you don't do this, in a timely fashion, you could end up killing the animation. N.B animation is done on asynchronous queue of its own.
BTW you don't need custom animations on the child fragment, as they will inherit the parent animations.
you can do this in the child fragment.
@Override
public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
if (true) {//condition
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(getView(), "alpha", 1, 1);
objectAnimator.setDuration(333);//time same with parent fragment's animation
return objectAnimator;
}
return super.onCreateAnimator(transit, enter, nextAnim);
}
I recently ran into this problem in my question: Nested fragments transitioning incorrectly
I have a solution that solves this without saving a bitmap, nor using reflection or any other unsatisfying methods.
An example project can be viewed here: https://github.com/zafrani/NestedFragmentTransitions
A GIF of the effect can be viewed here: https://imgur.com/94AvrW4
In my example there are 6 children fragments, split between two parent fragments. I'm able to achieve the transitions for enter, exit, pop and push without any problems. Configuration changes and back presses are also successfully handled.
The bulk of the solution is in my BaseFragment's (the fragment extended by my children and parent fragments) onCreateAnimator function which looks like this:
override fun onCreateAnimator(transit: Int, enter: Boolean, nextAnim: Int): Animator {
if (isConfigChange) {
resetStates()
return nothingAnim()
}
if (parentFragment is ParentFragment) {
if ((parentFragment as BaseFragment).isPopping) {
return nothingAnim()
}
}
if (parentFragment != null && parentFragment.isRemoving) {
return nothingAnim()
}
if (enter) {
if (isPopping) {
resetStates()
return pushAnim()
}
if (isSuppressing) {
resetStates()
return nothingAnim()
}
return enterAnim()
}
if (isPopping) {
resetStates()
return popAnim()
}
if (isSuppressing) {
resetStates()
return nothingAnim()
}
return exitAnim()
}
The activity and parent fragment are responsible for setting the states of these booleans. Its easier to view how and where from my example project.
I am not using support fragments in my example, but the same logic can be used with them and their onCreateAnimation function
So there seem to be a lot of different workarounds for this, but based on @Jayd16's answer, I think I've found a pretty solid catch-all solution that still allows for custom transition animations on child fragments, and doesn't require doing a bitmap cache of the layout.
Have a BaseFragment
class that extends Fragment
, and make all of your fragments extend that class (not just child fragments).
In that BaseFragment
class, add the following:
// Arbitrary value; set it to some reasonable default
private static final int DEFAULT_CHILD_ANIMATION_DURATION = 250;
@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
final Fragment parent = getParentFragment();
// Apply the workaround only if this is a child fragment, and the parent
// is being removed.
if (!enter && parent != null && parent.isRemoving()) {
// This is a workaround for the bug where child fragments disappear when
// the parent is removed (as all children are first removed from the parent)
// See https://code.google.com/p/android/issues/detail?id=55228
Animation doNothingAnim = new AlphaAnimation(1, 1);
doNothingAnim.setDuration(getNextAnimationDuration(parent, DEFAULT_CHILD_ANIMATION_DURATION));
return doNothingAnim;
} else {
return super.onCreateAnimation(transit, enter, nextAnim);
}
}
private static long getNextAnimationDuration(Fragment fragment, long defValue) {
try {
// Attempt to get the resource ID of the next animation that
// will be applied to the given fragment.
Field nextAnimField = Fragment.class.getDeclaredField("mNextAnim");
nextAnimField.setAccessible(true);
int nextAnimResource = nextAnimField.getInt(fragment);
Animation nextAnim = AnimationUtils.loadAnimation(fragment.getActivity(), nextAnimResource);
// ...and if it can be loaded, return that animation's duration
return (nextAnim == null) ? defValue : nextAnim.getDuration();
} catch (NoSuchFieldException|IllegalAccessException|Resources.NotFoundException ex) {
Log.w(TAG, "Unable to load next animation from parent.", ex);
return defValue;
}
}
It does, unfortunately, require reflection; however, since this workaround is for the support library, you don't run the risk of the underlying implementation changing unless you update your support library. If you're building the support library from source, you could add an accessor for the next animation resource ID to Fragment.java
and remove the need for reflection.
This solution removes the need to "guess" the parent's animation duration (so that the "do nothing" animation will have the same duration as the parent's exit animation), and allows you to still do custom animations on child fragments (e.g. if you're swapping child fragments around with different animations).