FragmentTransaction.replace() fade-in transition shows "ghost" fragment

7.1k views Asked by At

You can download my entire project to try and debug. Here is a repo of my entire code: https://bitbucket.org/lexic92/studio48/


I have a "ghost fragment" appearing in the transition when I try to replace a blank fragment with a blank fragment.

How to recreate the problem: I have a navigation drawer and when I click on an item, it opens a fragment that fills the whole screen.

Navigation Drawer:

  • Button 1 - Fragment with text
  • Button 2 - Fragment that is empty

    1. When I open the app, it starts out with the fragment with text. Then, I open the Navigation Drawer and click Button 2. Transition is bad, and turns into a blank screen for a split second, then switches back to the fading text, until it becomes a blank screen.

    2. If I open the Navigation Drawer and click on Button 2 again, it fades from the full text to the blank screen. So, for one split-second, it shows the fragment with text when it is not supposed to. I ran through my app on debug mode, step by step, to slow it down and verify that this behavior actually happened.

    3. If I open the Navigation Drawer and click on Button 2 again for the third time, it correctly does not show any animations because it is fading to the same screen.

Does anyone know why this happens? Do you think it has anything to do with the navigation drawer?

Here is a repo of my entire code: https://bitbucket.org/lexic92/studio48/

Here are some code excerpts:

SongDetailActivity.java:

      @Override
      public void onNavigationDrawerItemSelected(int position) {
        // update the main content by replacing fragments
        FragmentManager fragmentManager = getSupportFragmentManager();

        //OPEN THE BLANK SCREEN
        if(position == 1) {
            fragmentManager.beginTransaction()
                    //.setCustomAnimations(R.anim.fade_in, R.anim.fade_out)
                    .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
                    .replace(R.id.songlist_detail_container, PlaceholderFragment.newInstance(position + 1, mSongId))
                    .commit();
        }
        //OPEN THE SCREEN WITH TEXT
        else {
            SongDetailFragment sdf = new SongDetailFragment();
            Bundle args = new Bundle();
            args.putString(SongDetailFragment.ARG_ITEM_ID, mSongId);
            sdf.setArguments(args);
            fragmentManager.beginTransaction()
                    //.setCustomAnimations(R.anim.fade_in, R.anim.fade_out)
                    .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
                    .replace(R.id.songlist_detail_container, sdf)
                    .commit();
        }
    }

The same problem happens when I comment out the 2 lines that say ".setTransition" and un-comment the 2 lines that say ".setCustomAnimations". Here are the custom animations:

res/anim/fade_out.xml:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
           android:duration="@android:integer/config_mediumAnimTime" />
</set>

res/anim/fade_in.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
           android:duration="@android:integer/config_mediumAnimTime" />
</set>

How can I get it to transition smoothly without the "ghost" fragment showing up?

Specs: I am running with android.support.v7.app.ActionBarActivity, minSdkVersion == 15, and targetSdkVersion == 21, on a Google Nexus 5 physical device (not an emulator).

My guesses of the answer:

This website seems to give me a hint: http://gotoanswer.com/?q=Android+Animation+within+a+Fragment+not+working

Personally for me this is a bug with the zPosition for Fragment animations.

What you see is the new fragment "appearing" this is because the new fragment is attached underneath the current one.

So when trying to animate a fragment 'Over the top' of an existing fragment, it appears like nothing is happening then the new one just 'appears'.

I have tried many work arounds, playing with zPosition (only affects windows not layouts, so no effect to fragments), onAnimationCreate() bringing the root layout to front or even on animation start and stop... seems to do nothing.

So at this point in time without writing a complete custom animation for fragment transitions, they are a bit buggy at the moment.

You may have to play with. .add(Fragment) wait for it to be added, then call .remove(Fragment) removing the old fragment, that way the new fragment is physically placed on top of the existing fragment.

I did notice that this code was being called upon instantiation of SongDetailActivity.java, even without me clicking on the navigation drawer:

//OPEN THE SCREEN WITH TEXT
            else {
                SongDetailFragment sdf = new SongDetailFragment();
                Bundle args = new Bundle();
                args.putString(SongDetailFragment.ARG_ITEM_ID, mSongId);
                sdf.setArguments(args);
                fragmentManager.beginTransaction()
                        //.setCustomAnimations(R.anim.fade_in, R.anim.fade_out)
                        .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
                        .replace(R.id.songlist_detail_container, sdf)
                        .commit();
            }

Maybe the current fragment is fading out to this "underlying" fragment, as the quotation suggests, before fading in to the new fragment?

What I ended up doing

Note: I am not putting this as an answer because it does not technically answer my question, but it's the best back-up-plan solution I can come up with.

I tried seeing if the problem was solved when there was no animation. The "ghost fragment" didn't show up, but I did notice that it took a split second to load the fragment, which looked jarring and ugly. I wanted a silky smooth app, and if it isn't smooth when there was no animation, then it will probably be hopeless even if I did fix the ghost fragment bug. I learned that fragments are just buggy (see here).

I decided to hide and show views instead. Here is my new code:

SongDetailActivity.java

//---------- NAVIGATION DRAWER -------------
    @Override
    public void onNavigationDrawerItemSelected(int position) {

        //HOME
        if (position == 0) {

        }
        //LYRICS
        else if(position == 1) {
            findViewById(R.id.lyrics).setVisibility(View.VISIBLE);
            findViewById(R.id.info).setVisibility(View.INVISIBLE);

        }
        //INFO
        else if(position == 2) {
            findViewById(R.id.info).setVisibility(View.VISIBLE);
            findViewById(R.id.lyrics).setVisibility(View.INVISIBLE);
        }
    }

I also copied and pasted the layouts for the 2 fragments (the empty one and the one with lots of text) into the same layout, as children of a FrameLayout parent:

fragment_songlist_detail.xml:

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="match_parent"
    android:background="#f8bbd0"
    android:layout_width="match_parent"
    >
    <LinearLayout
        android:id="@+id/lyrics"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        xmlns:android="http://schemas.android.com/apk/res/android">
    ...
    </LinearLayout>
    <LinearLayout
        android:id="@+id/info"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        xmlns:android="http://schemas.android.com/apk/res/android">
    ...
    </LinearLayout>
</FrameLayout>

It works fine, but I just don't know how to make it fade in and fade out. But that is a question for a separate stack overflow question. If anyone solves the ghost fragment mystery, I'm very interested to know the answer. But I decided I'm not going to waste my time trying to figure it out right now, especially when fragments' transitions have proven to be buggy in the past (like in that article).

1

There are 1 answers

0
Elltz On

do this for all your Transaction calls

fragmentManager.beginTransaction().setTransition(FragmentTransaction.
                            TRANSIT_FRAGMENT_FADE).remove(current_fragment).commit();
fragmentManager.beginTransaction().add(R.id.songlist_detail_container, PlaceholderFragment.
                           newInstance(position + 1, mSongId)).commit();

see if it helps. The idea is to first commit a remove,after commit an addition. you can also go for FragmentManager.hide(Fragment);