Implement Floating Action Button – Part 2

floating action button material design

I’d recommend going through Part 1 of the implementation first. It covers how you can quickly setup a Floating Action Button. Though if you feel you’ve already got that covered, feel free to proceed.

In this part, I’ll show you how we can add some neat animations to our Floating Action Button (FAB) such as the initial grow animation, as well as a Quick Return pattern (famously seen on the Google+ app).

Floating Action Button Animations

As per the design guidelines, the FAB must:

  • hide upon scrolling down and reappear when scrolled up (known as the Quick Return Pattern)
  • initially greet users with a ‘grow’ animation upon entering app screen

The quick return pattern can most commonly be found in the current Google+ app. Screen real estate is important especially on smaller devices. Upon scrolling, the toolbar and FAB translate to being hidden. This allows you to view the main scrollable content, distraction-free. When you scroll up, both views translate back up.

For a better idea on what it is, watch it in action below:

 Quick Return

Custom ScrollListener

So first off, let’s create the Quick Return pattern. It’s better to sort and organize your project from the very beginning, so create a new package utils under your main package com.yourname.app.

Now under the utils package, create a new abstract class MyRecyclerScroll. This is going to extend RecyclerView.OnScrollListener. Now its pretty obvious what extends class will do (judging from the name). Yep, you’re right, listen to scroll events!

Now the OnScrollListener provides us with 2 handy methods that we can use, such as the onScrolled() and the onScrollStateChanged() method.

However, the method that we’re interested in is the onScrolled(). So go ahead and implement that method.

public abstract class MyRecyclerScroll extends RecyclerView.OnScrollListener {
   
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        ...
    }
}

Now why we made it an abstract class is because, we’re going to create a custom scroll listener for our recycler view that detects the direction of scroll. Depending on that, we’ll hide or show our FAB. We’ll then hook it up to the fab in our MainActivity.java and implement its methods to handle them.

The Approach

So before we dive into the all the messy code, let’s analyse how the Quick Return Pattern works.

  1. When the user scrolls down, the FAB animates downward and gets hidden.
  2. The FAB remains hidden while the user scrolls down.
  3. If the FAB is not visible and user scrolls up, the FAB animates up and becomes visible.
  4. FAB continues to remain visible while the user scrolls up.

With that said, in order to achieve this we need to track just two things:

  1. The distance & direction of scroll
  2. Whether the FAB is visible

Let’s start off by declaring those 2 variables that we need.

int scrollDist = 0;
boolean isVisible = true;

Initialy scroll distance will obviously be zero, hence its set to that. By default upon entrance the FAB will be visible and so the boolean is set to true.

onScrolled Method

Now in the onScrolled() method, if we take a look at the last 2 parameters: int dx, int dy. These are the horizontal and vertical scrolls respectively.

We’re only concerned with dy, which gives us a positive value on scrolling down, and negative value scrolling up. We need to record the scroll distance first, then do appropriate checks whether the FAB is visible and accordingly show or hide it.

Also, in order to check if the scroll distance will suffice, add a new variable  static final float MINIMUM = 25. This is the value against which we’ll check our scroll distance. Unless the scroll distance not going to be greater than that, we won’t bother to hide or show the FAB. It helps avoid weird stuttering with the FAB, when we scroll slightly in either direction.

@Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        if (isVisible && scrollDist > MINIMUM) {
            hide();
            scrollDist = 0;
            isVisible = false;
        }
        else if (!isVisible && scrollDist < -MINIMUM) {
            show();
            scrollDist = 0;
            isVisible = true;
        }

        if ((isVisible && dy > 0) || (!isVisible && dy < 0)) {
            scrollDist += dy;
        }

    }

Algorithm

Nothing scary up there. We’re just handling 2 if conditions; hide FAB if visible when scrolling down, and show FAB if not visible when scrolling up.

  • We’re first checking if FAB is visible and if the scroll(down) is above the minimum.
  • If that’s true then, hide FAB. Since it’s not visible anymore, set its boolean and also reset scroll distance.
  • We only need to record scroll distance upto which the FAB hides/ shows. Need not bother recording the ENTIRE scroll distance.
  • Similarly, if the FAB is hidden, we’ll call the show() method.
  • We’re checking scroll distance against -MINIMUM because remember I said dy gives a negative value on scrolling up. Hence we must compare it against negativ MINIMUM.
  • Finally, the two OR conditions are  whether the user scrolls up or down, we’ll just count the scroll distance.

Below this method, add 2 abstract methods.

public abstract void show();
public abstract void hide();

We’ll be using these methods in our MainActivity to handle our FAB.

Attaching to the Activity

Our custom ScrollListener is now complete. All we need to do is attach it to our MainActivity and implement its abstract methods show()hide().

...
recyclerView.setOnScrollListener(new MyRecyclerScroll() {
           @Override
           public void show() {
               fab.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2)).start();
           }

           @Override
           public void hide() {
               fab.animate().translationY(fab.getHeight() + fabMargin).setInterpolator(new AccelerateInterpolator(2)).start();
           }
       });
...

We’re simply attaching our custom ScrollListener ‘MyRecyclerScroll’ as the ScrollListener for our RecyclerView. Upon doing so, we implement the 2 abstract methods.

For the show method, we handle the show animation for the FAB. The translationY distance is 0 because we want the FAB to come back to its original position. For hide method, we obviously hide the FAB. It’s translation distance will be the FAB height (56dp) + its margin (16dp). This is the distance the FAB is required to move off screen.

You could play around with different Interpolators and their timings no issues, but I’ve found these to work just fine for me.

Now go ahead and run your app. You can see our FAB animate quite nicely.fab quick return animation


Grow Animation

Next, we’ll implement a grow animation for our Floating Action Button.

The floating action button animates onto the screen as an expanding piece of material, by default. The icon within it may be animated. – source

This one’s relatively easy to implement. All that we’re going to do is create an XML scaling animation that grows our FAB from 0 to its original dimensions. We will then reference that animation in our Activity and attach it to our FAB.

The XML

Start by creating a new folder called anim under your res folder. Create a new XML file for a scale animation and name it simple_grow.xml.

So our path should be like res/anim/simple_grow.xml.

<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXScale="0.0"
    android:fromYScale="0.0"
    android:toXScale="1.0"
    android:toYScale="1.0"
    android:duration="700"
    android:pivotX="50%"
    android:pivotY="50%"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator">

</scale>

The X & Y to and from scale parameters are essential. We tell the animation that we need to start from 0 scale, which is no dimensions ( so you cant see it) and then scale the X & Y to 1.0 (maximum scale) so that we can see the FAB. 700 is a nice duration for the animation (which I’ve fixed at after a lot of trial). We need to give the animation some time to run and for the user to admire it.The pivot X & Y say that we want to scale it from the center. Without defining these two, the animation will scale from the top left by default. Lastly, we set a Decelerate Interpolator so that it looks nice. It lets the scale ease the animation towards the end.

If you’re interested in knowing more about Interpolators, you can give this a read, or watch it live below. Have fun experimenting with different Interpolators to achieve different effects!

Add animation to the FAB

Open the MainActivity.xml and define the animation before we set our content view using  Animation animation = AnimationUtils.loadAnimation(this, R.anim.simple_grow); Then after we’ve referenced our fab from the XML, we simply set the animation to the FAB with  fab.startAnimation(animation);

 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Animation animation = AnimationUtils.loadAnimation(this, R.anim.simple_grow);
        ...
        fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.startAnimation(animation);
       
}

Whatever you’ve got running in your onCreate() method, make sure you start the FAB animation at the end. Making it start too early would cause to animation to skip by the time the user gets to see. So make it run towards the end of your onCreate().

Watch the video below to see how it finally looks like.

With this, we’re done. Cheers! We’ve got a nice little growing animation that greets the user upon entering the app, and then the FAB translates nicely when the user scrolls up and down the list.

Download the project or head over to my GitHub repo to check out the animation code sample.

Suleiman

Product Designer who occasionally writes code.

You may also like...

15 Responses

  1. Gabriel Novakovski Nunes says:

    Hello! Nice job, man. But i’m having some trouble with the quick return pattern, can you help me? when i run the app, returns a crash. I didn’t find the right project to see if i’m making something wrong, can you show what is the right project on github?

  2. Luís Cunha says:

    How would you achieve this behaviour on a layout with a collapsing toolbar, meaning the bottom alignment get’s out of the screen bounds when the collapsing toolbar is fully extended and the FAB is basically always hidden because of it.

    • Suleiman19 says:

      How are you implementing the collapsing Toolbar?
      The increased Toolbar height would be causing the FAB to appear off the screen. You can use a RelativeLayout to absolute position the FAB.

      If you’re using the Design Support Library, you can use their native FAB as a better alternative.

      • Luís Cunha says:

        I have a collapsing toolbar just like the one in your example, but with a viewpager with a few tabs, and each child fragment has a different layout, one of which requires a fab. From what I could find setting a fixed position for a view seems to only be possible in code, or is there a way to do this on the xml layout directly?

      • Suleiman19 says:

        Could you post your issue on StackOverflow so I can get a better understanding of your issue?

      • Luís Cunha says:

        I have noticed that you include the viewpager layout in your main_activity.xml()
        Is this necessary for proper viewpager behaviour? What’s happening to my app is that the tabs of the viewpager are only present when the activity is 1st created or when I rotate the screen and it is reconstructed. Whenever I navigate away from the viewpager containing fragment and then return to it, in place of the tabs I just get a solid color bar with nothing on it. I have posted a question about it on
        http://stackoverflow.com/questions/31674241/android-viewpager-child-fragments-load-but-tabs-dont
        but got no solutions so far.

      • Luís Cunha says:

        I have noticed that you include the viewpager layout in your main_activity.xml
        ()
        Is this necessary for proper viewpager behaviour? What’s happening to my app is that the tabs of the viewpager are only present when the activity is 1st created or when I rotate the screen and it is reconstructed. Whenever I navigate away from the viewpager containing fragment and then return to it, in place of the tabs I just get a solid color bar with nothing on it. I have posted a question about it on http://stackoverflow.com/questions/31674241/android-viewpager-child-fragments-load-but-tabs-dont
        but got no solutions so far.

      • Suleiman19 says:

        You say when you navigate away and return to it, then there’s an issue. This points to the onResume() method.

        Could you try storing the instance, or reinflating your tab layout on resume?

      • Luís Cunha says:

        I’m not sure how I’d be able to reinflate onResume(), how can I access findViewById or what would be the best way to achieve this? This is not the 1st screen of my app, and I must navigate through a recyclerview and tap an item to reach the fragment with the viewpager, and in both ways, navigating into it or away and back, the tabs do not appear on the viewpager. They only appear if the fragment is the 1st one opened, which happens when I click a notification to go directly into it. So there would be no instance to save since if I navigate into it, it already doesn’t display correctly. Do viewpagers only work when they are part of the activity instead of a fragment?

        PS.: Sorry for the double post, and I fixed the stackoverflow link to my question that for some reason wasn’t posted correctly.

      • Suleiman19 says:

        Is there an absolute necessity that your RecyclerView onClick() must lead to a Fragment? If its an Activity instead, half your problems are solved.

      • Luís Cunha says:

        Unfortunately yes, the app receives a list of stores from a webservice and they are listed on the screen, the user must then choose which one he wishes to view the details of, as it will contain different images, sales, etc. depending on the store chosen. The solution we’re implementing now is to just remove the fragments from the app and use activities for each screen, but this is a terrible solution since we’re repeating a lot of UI code.
        Also of note, the fragments themselves work perfectly and if I log the tab names to the console on side swipe, they are properly reported, so all the information is present, the viewpager is just not actually displaying the click-able labels.
        Can viewpagers only be used in activities?

      • Suleiman19 says:

        Your ViewPager is just another adapter that holds the Views. Your Tabs is a separate layout. Make sure the ViewPager is well below the Tab Layout.

      • Luís Cunha says:

        We ended up just implementing it in a separate activity instead of a fragment and is now working as intended just with that change. Thank you very much for your time and helpful comments.

      • Suleiman19 says:

        It is not necessary to include the ViewPager that way, you can directly add it to your activity/ fragment’s layout if you wish.

Leave a Reply

Your email address will not be published. Required fields are marked *