Fragment transition animations while hiding the previous fragment and using backstack

Print Friendly

This article will explain how to create fragments inside an Activity, how to dynamically add a fragment to an Activity, how to create transition animations between fragments, how to hide the previous fragment and finally how to use the fragment backstack.

You can download the code here

So first lets get started. We need to create three layout files, so create main.xml, fragment1.xml, fragment2.xml.
main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="#FFFFFF" >
    <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:text="Learning Fragments swap" android:textColor="#800080"
        android:textStyle="bold"
        android:textSize="26sp"> </TextView>
    <FrameLayout
        android:id="@+id/fragment_swap"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
    </FrameLayout>

</RelativeLayout>

We will be using the FrameLayout to do the fragment swaping.
fragment1.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center" >

    <ImageView
        android:id="@+id/adblogcat"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/adblogcat" >
    </ImageView>

    <TextView
        android:id="@+id/fragment1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/adblogcat"
        android:layout_centerInParent="true"
        android:clickable="true"
        android:gravity="center"
        android:text="THIS IS FRAGMENT 1"
        android:textColor="#FF0000"
        android:textSize="30sp"
        android:textStyle="bold" >
    </TextView>

</RelativeLayout>

fragment2.xml:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:clickable="true"
    android:gravity="center"
    android:layout_gravity="center"
    android:text="THIS IS FRAGMENT 2"
    android:textColor="#000000"
    android:textSize="30sp" >
</TextView>

With fragment2, I want to show that you do not need a Layout View in an xml, you can inflate any View you want. In this case, a TextView.

Now we need to create a class Fragment1.java which extends Fragment. I have put comments in the code so its understandable, the code:

public class Fragment1 extends Fragment {

	// my custom listener
	private OnFragment1ClickedListener mOnFragment1ClickedListener;

	// Must always call onCreateView and inflate your layout.
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {

		super.onCreateView(inflater, container, savedInstanceState);

		View view = inflater.inflate(R.layout.fragment1, container, false);

		// I want the whole view to be clickable, but I could have called the textView or ImageView only.
		view.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
                                // call the listener's abstract method, the implementation of the method happens in the Activity.
				mOnFragment1ClickedListener.onFragment1Clicked();
			}
		});

		return view;
	}

	// in onAttach we must instantiate the listener 
	@Override
	public void onAttach(Activity activity) {
		super.onAttach(activity);
		try {
			mOnFragment1ClickedListener = (OnFragment1ClickedListener) activity;
		} catch (ClassCastException e) {
			e.printStackTrace();
		}
	}
	// This is my own custom listener so that the Activity and fragment can communicate.
	// If you are not familiar with this, google "observer design pattern".
	public interface OnFragment1ClickedListener{
		public void onFragment1Clicked();
	}

}

This fragment has the click listener to call my custom listener’s abstract method onFragment1Clicked().

We now create another class Fragment2.java in which we do the exact same thing. However, this fragment will not call another fragment, so we do not have the interface and abstract method. Instead we just call a toast. Code:

public class Fragment2 extends Fragment {

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {

		super.onCreateView(inflater, container, savedInstanceState);

		View view = inflater.inflate(R.layout.fragment2, container, false);
		view.setOnClickListener(new View.OnClickListener() {
			
			@Override
			public void onClick(View v) {
				Toast.makeText(getActivity(), "THIS IS FRAGMENT2 BEING CLICKED!!! PRESS BACK TO GO TO FRAGMENT1", Toast.LENGTH_SHORT).show();
			}
		});
		return view;
	}

}

Now, we need three animations. I created one animation that fades away and another one that appears as the other fades and finally one that just appears so when users press the back button, they don’t have to wait for the fading animation; it doesn’t make sense. We need to create a folder inside the res folder “anim” and inside create three xml files: fragment_animation_fade_in.xml, fragment_animation_fade_out.xml, fragment_animation_no_fade_in.xml.
Notice that with fragments we now have to use objectAnimator.

fragment_animation_fade_in.xml:

<?xml version="1.0" encoding="utf-8"?> 
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/linear_interpolator"
    android:valueFrom="0"
    android:valueTo="1"
    android:propertyName="alpha"
    android:duration="500" />

fragment_animation_fade_out.xml:

<?xml version="1.0" encoding="utf-8"?> 
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/linear_interpolator"
    android:valueFrom="1"
    android:valueTo="0"
    android:propertyName="alpha"
    android:duration="500" />

fragment_animation_no_fade_in.xml

<?xml version="1.0" encoding="utf-8"?> 
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/linear_interpolator"
    android:valueFrom="1"
    android:valueTo="1"
    android:propertyName="alpha"
    android:duration="1" />

So this was the easy part. Now we need to put everything together. We do this by creating an activity, Main.java, which will do all the work. Here is the code with comments and then I will explain further:

public class Main extends Activity implements OnFragment1ClickedListener {
	// This is needed in order to manage the fragments.
	public Stack<String> mFragmentStack;

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

		mFragmentStack = new Stack<String>();

		// Dynamically add a fragment to an Activity
		FragmentManager fragmentManager = getFragmentManager();
		FragmentTransaction transaction = fragmentManager.beginTransaction();
		Fragment fragment = new Fragment1();
		String tag = fragment.toString();
		mFragmentStack.add(tag);
		transaction.add(R.id.fragment_swap, fragment,tag);
		transaction.addToBackStack(tag);
		transaction.commit();


	}

	// Notice how this class implements OnFragment1ClickkedListener? Well this causes us to 
	// implement the abstract method onFragment1Clicked() from Fragment1.java. 
	@Override
	public void onFragment1Clicked() {
		Fragment newFragment = new Fragment2();
		Bundle args = new Bundle();
		// by doing newFragment.toString(), I am taking a unique identified of that object.
		String tag = newFragment.toString();
		newFragment.setArguments(args);

		FragmentTransaction transaction = getFragmentManager().beginTransaction();
		// setting the animations here. the fade_in animation for fragment1 and fade_out for fragment2
		transaction.setCustomAnimations(R.anim.fragment_animation_fade_in, R.anim.fragment_animation_fade_out);
		// I find what the current fragment from the stack is, take it and hide it
		// using transaction.hide(currentFragment);
		Fragment currentFragment = getFragmentManager().findFragmentByTag(mFragmentStack.peek());
		transaction.hide(currentFragment);
		
		transaction.add(R.id.fragment_swap, newFragment,newFragment.toString());
		// This is important, we must addToBackStack so we can pull it out later.
		transaction.addToBackStack(tag);
		// Instead of using replace we use add. Why? If we use replace, then the previous
		// fragment will always have to be re-created. What if you don't want to do that. In
		// my case, I didn't want it to be re-created all the time because I had a position
		// set and by re-creating it, I would have lost the position or had to use static flag.
		mFragmentStack.add(tag);

		transaction.commit();
	}

	@Override
	public void onBackPressed(){
		// from the stack we can get the latest fragment
		Fragment fragment = getFragmentManager().findFragmentByTag(mFragmentStack.peek());
		// If its an instance of Fragment1 I don't want to finish my activity, so I launch a Toast instead.
		if (fragment instanceof Fragment1){
			Toast.makeText(getApplicationContext(), "YOU ARE AT THE TOP FRAGMENT", Toast.LENGTH_SHORT).show();
		}
		else{
			// Remove the framg
			removeFragment();
			super.onBackPressed();
		}
	}
	private void removeFragment(){
		// remove the current fragment from the stack.
		mFragmentStack.pop();
		FragmentTransaction transaction = getFragmentManager().beginTransaction();
		// get fragment that is to be shown (in our case fragment1).
		Fragment fragment = getFragmentManager().findFragmentByTag(mFragmentStack.peek());
		// This time I set an animation with no fade in, so the user doesn't wait for the animation in back press
		transaction.setCustomAnimations(R.anim.fragment_animation_no_fade_in, R.anim.fragment_animation_fade_out);
		// We must use the show() method.
		transaction.show(fragment);
		transaction.commit();
	}

}

So basically I keep a stack of fragments that I am using so I can use as many fragments as I want. I am only using two right now, but this will work with 3 or more as well as long as the code in onFragment1Clicked() is used (slightly modified for other fragments of course). Also I am using the FragmentTransaction method hide() to hide my previous fragments and instead of replace() I am using add(). That is all, happy fragmenting?

Comments are closed.