Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animating Toolbar along with Fragment Transactions

I have MainActivity which is implementing Navigation Drawer using the below xml:

<android.support.v4.widget.DrawerLayout
xmlns:android ="http://schemas.android.com/apk/res/android"
android:id ="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:id="@+id/container_toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >

        <include
            android:id="@+id/toolbar"
            layout="@layout/toolbar" />
    </LinearLayout>

    <FrameLayout
        android:id="@+id/content"
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:background="#FFFFFF"/>


</LinearLayout>

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="240dp"
    android:layout_height="match_parent"
    android:layout_gravity ="start"
    >


    <ListView
        android:id="@+id/drawerList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="5dp"
        android:divider="@color/material_blue_grey_800"
        android:dividerHeight="1dp"
        android:background="#FFFFFF"
        />

   </RelativeLayout>

</android.support.v4.widget.DrawerLayout>

Now i have 3 items in my listview and on clicking on any one of them my code replace Framelayout with that particular fragment like below:

Fragment f1 = new Fragment()

 FragmentTransaction ft = getSupportFragmentManager().beginTransaction()
 ft.setCustomAnimations(R.anim.slide_in,R.anim.hyper_out,R.anim.hyper_in,R.anim.slide_out)


ft.replace(R.id.content, f1).addToBackStack(null).commit();

The above code works fine replacing fragment with Custom animations as desired.However, my question is how to animate the toolbar along with fragment during fragment transactions.

All the fragments have their respective toolbar titles which are changed in onActivityCreated() method of each Fragment Class by following code:

((AppCompatActivity)getActivity()).getSupportActionBar().setTitle("Title");

Should i be applying animations to my layouts to cover up the toolbar?

like image 776
PunitD Avatar asked Jun 13 '15 06:06

PunitD


People also ask

How do you animate fragment transitions?

At a high level, here's how to make a fragment transition with shared elements: Assign a unique transition name to each shared element view. Add shared element views and transition names to the FragmentTransaction . Set a shared element transition animation.

How do you animate a fragment?

To animate the transition between fragments, or to animate the process of showing or hiding a fragment you use the Fragment Manager to create a Fragment Transaction . Within each Fragment Transaction you can specify in and out animations that will be used for show and hide respectively (or both when replace is used).

How do I switch fragments?

Use replace() to replace an existing fragment in a container with an instance of a new fragment class that you provide. Calling replace() is equivalent to calling remove() with a fragment in a container and adding a new fragment to that same container. transaction. commit();


1 Answers

I had this same question - it seems to pop up at some point in the development process of every app, and I never solve it, and revert back to some subpar solution. Now I'm back with a solution!

Answer in Kotlin, but the same principles apply in Java.

To animate the Toolbar alongside the FragmentTransaction animations, you first need a way to inform the Activity/Fragment holding the Toolbar that the animation is starting/ending. I'll call the Activity/Fragment holding the Toolbar the ToolbarHost

First, define an interface which the ToolbarHost can implement, to receive callbacks:

interface FragmentAnimationListener {

    fun onAnimationStart(fragment: Fragment, animation: Animation, enter: Boolean)

    fun onAnimationEnd(fragment: Fragment, animation: Animation, enter: Boolean)
}

I've also added an extension method to help the child Fragment find the parent ToolbarHost interested in receiving the callbacks:

fun Fragment.findParentAnimationListener(): FragmentAnimationListener? {
    return when (parentFragment) {
        is FragmentAnimationListener -> parentFragment as FragmentAnimationListener
        null -> return activity as? FragmentAnimationListener
        else -> parentFragment?.findParentAnimationListener()
    }
}

Now we add the following to the child Fragment which is about to be animated onto the screen. I suggest adding the following to a base Fragment class. This intercepts the animation supplied to the child Fragment from the FragmentTransaction*, adds a listener, finds ToolbarHost, and notifies it of our animation starting/ending.

override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? {
    var animation = super.onCreateAnimation(transit, enter, nextAnim)

    if (animation == null && nextAnim != 0) {
        animation = AnimationUtils.loadAnimation(activity!!, nextAnim)
    }

    val parentAnimationListener = findParentAnimationListener()

    if (animation != null) {    
        animation.setAnimationListener(object : Animation.AnimationListener {

            override fun onAnimationStart(animation: Animation) {
                parentAnimationListener?.onAnimationStart(this@BaseFragment, animation, enter)
            }

            override fun onAnimationEnd(animation: Animation) {
                parentAnimationListener?.onAnimationEnd(this@BaseFragment, animation, enter)
            }

            override fun onAnimationRepeat(animation: Animation?) {
            }
        })
    }

    return animation
}

* This works for animations supplied via FragmentTransaction.setCustomAnimation(int, int, ..). I haven't tested this with different types of FragmentTransaction animations, or no FragmentTransaction animation.

Now that our ToolbarHost knows when the child Fragment is performing its animations, we can think about animating the Toolbar alongside.

Assuming your ToolbarHost is a fragment, we first implement FragmentAnimationListener:

class ParentFragment : Fragment(), FragmentAnimationListener {

    override fun onAnimationStart(fragment: Fragment, animation: Animation, enter: Boolean) {
        ...
    }

    override fun onAnimationEnd(fragment: Fragment, animation: Animation, enter: Boolean) {
        ...
    }
}

Now, one issue we have right off the bat, is since we added our AnimationListener to a base Fragment class, our onAnimationStart and onAnimationEnd are going to be called from both the fragment that is exiting, and the fragment that is entering (assuming your FragmentTransaction is replacing one fragment with another). So before we try and do anything with the Toolbar, we need to filter out only the fragment we're interested in.

I'm going to use the 'entering' Fragment as the trigger for Toolbar animations - and when we pop the stack, I'm going to use the same Fragment (now 'exiting') again. So, the only Fragment I'm interested in is the one being transitioned to:

class ParentFragment : Fragment(), FragmentAnimationListener {

    var newFragment: Fragment? = null 

    fun replaceFragment(newFragment: Fragment) {

        // Store a reference to the fragment we're transitioning to
        this.newFragment = newFragment

        childFragmentManager.beginTransaction()
            .setCustomAnimations(..)
            .replace(container, newFragment, tag)
            .commit()
    }

    override fun onAnimationStart(fragment: Fragment, animation: Animation, enter: Boolean) {
        if (fragment == newFragment) {
            if (enter) {
                // Animate our Toolbar in
            } else {
                // Animate our Toolbar out
            }
        }
    }
}

The last step is to actually perform the Toolbar animation. You can create your own Animation, with your own timing, interpolator, etc - but I prefer to just use the same Animator that's animating the child Fragment. That way, if we're fading in, the Toolbar will fade in. If we're sliding out, the Toolbar will slide out. By using the same Animation we can be certain we have the correct duration and interpolator, so our Animations will run at the same time.

override fun onAnimationStart(fragment: Fragment, animation: Animation, enter: Boolean) {
    if (fragment == newFragment) {
        toolbar.animation = animation
    }
}
like image 79
Tim Malseed Avatar answered Nov 10 '22 01:11

Tim Malseed