I've mostly worked with iOS and have become accustomed to very smooth and fluent screen change animations. I am now working on an Android app and can't for the life of me get a fragment transaction to smoothly add/replace a fragment.
My set up is as follows: MainActivity
has a FrameLayout
for its xml which I immediately load FragmentA
into on the MainActivity
's OnCreate
. My app then proceeds to replace the MainActivity
's FrameLayout
with FragmentB
, FragmentC
, ect. So my entire app has 1 activity.
On button clicks I call the following to add/replace the current fragment:
getFragmentManager()
.beginTransaction()
.setCustomAnimations(R.animator.slide_in, android.R.animator.fade_out, android.R.animator.fade_in, R.animator.slide_out)
.addToBackStack(null)
.add(R.id.fragment_1, fragment)
.commit();
slide_in.xml looks like (slide_out.xml is obviously the opposite):
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:interpolator="@android:interpolator/linear"
android:propertyName="xFraction"
android:valueType="floatType"
android:valueFrom="1.0"
android:valueTo="0"
android:duration="@android:integer/config_shortAnimTime"
/>
</set>
Sliding in and out I'm animating xFraction which I've subclassed LinearLayout
to do like so:
public class SlidingLinearLayout extends LinearLayout {
private float yFraction = 0;
private float xFraction = 0;
public SlidingLinearLayout(Context context) {
super(context);
}
public SlidingLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SlidingLinearLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private ViewTreeObserver.OnPreDrawListener preDrawListener = null;
public void setYFraction(float fraction) {
this.yFraction = fraction;
if (getHeight() == 0) {
if (preDrawListener == null) {
preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(preDrawListener);
setYFraction(yFraction);
return true;
}
};
getViewTreeObserver().addOnPreDrawListener(preDrawListener);
}
return;
}
float translationY = getHeight() * fraction;
setTranslationY(translationY);
}
public float getYFraction() {
return this.yFraction;
}
public void setXFraction(float fraction) {
this.xFraction = fraction;
if (getWidth() == 0) {
if (preDrawListener == null) {
preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(preDrawListener);
setXFraction(xFraction);
return true;
}
};
getViewTreeObserver().addOnPreDrawListener(preDrawListener);
}
return;
}
float translationX = getWidth() * fraction;
setTranslationX(translationX);
}
public float getxFraction() {
return xFraction;
}
}
So my issue is most of the time when I initially click a button to add/replace a fragment, I don't get an animation it just sort of pops into place. However more times than not when I press the back button and the fragment pops from the backstack the animation executes as expected. I feel like it is an issue with initialization while trying to animate. When the fragment is in memory and animated off screen it does so much better than when a new fragment is created then animated on to the screen. I'm curious if others have experienced this and if so how they resolved it.
This is an annoyance that has plagued my Android development experience that I'd really like to put to rest! Any help would be greatly appreciated. Thanks!
In my experience, if the fragment is doing too much work on load, then the system will basically skip the enter animation and just display the fragment. In my case the solution was to create an animation listener in onCreateAnimation to wait until the enter animation had finished before doing the process intensive work that was causing the animation to be skipped.
@Override
public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
if (nextAnim == 0) {
return super.onCreateAnimation(transit, enter, nextAnim);
}
Animation anim = android.view.animation.AnimationUtils.loadAnimation(getContext(), nextAnim);
anim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
// Do any process intensive work that can wait until after fragment has loaded
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
return anim;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With