Google Play Music app (GPM) is one of the best visually designed applications in Android’s ecosystem. It’s simple and clean. There are nice animations, transitions, beautiful use of colors/palettes, but most importantly, it follows Material Design spec almost perfectly. It’s a pleasure for your eyes and it offers a really enjoyable user experience.

While working on a personal Android project I wanted to give my app a nice “hide/show Toolbar while scrolling content” feature. After some research and playing with lots of samples found on the Internet, I realize none of them were as visually appealing as the GPM app. Some of them are nice, others not. But none of them follows the same approach.

I finally gave up with the samples and started playing a lot with the GPM app Toolbar. Tons of scrolling ups and downs to figure it out the exact same behavior.

Android Google Music app

I made a sample app that hides/shows the Toolbar in the exact same way as the GPM app does. Believe me, it was really tricky. To understand why, first let’s see GPM app in action:

Scrolling up

As you can see the Toolbar starts at the top with no shadow. If you scroll the content up without releasing your finger, the Toolbar gets scrolled up too. However, if you release your finger a little shadow appears and the “show” animation takes place.

If you continue scrolling up, Toolbar will eventually hide but this happens only when the amount of scrolled pixels is equals to its size. If you continue scrolling up Toolbar will not appear again.

Scrolling down

If you change the direction of the scrolling without releasing your finger you’ll see the Toolbar scrolls down with the content but if you release your finger and the amount of visible pixels of the Toolbar is greater than half its size, it will show again. However, if the amount is less than that, it will hide.

Shadow

One last interesting thing is Toolbar’s shadow. It appears only when there is content scrolled behind it but if you reach the top of your content when scrolling down or if the Toolbar is completely hidden, it will disappear.

At first glance seems pretty straightforward, the reality is that replicating its behavior exactly as in GPM was challenging.

Sample app

Now that we know how it behaves, let’s see the sample app in action.

Pretty cool ah? It has the exact same behavior as in GPM app. So here is how I implemented it.

First you need a Toolbar widget in your layout. Make sure you place the Toolbar on top of your container otherwise you will end up having some weird artifacts when touching option items in the Toolbar.

Then you need to listen for scrolling changes in your container. For my sample app I used a RecyclerView but you can use whatever scrolling container you want as long as you can listen for scrolling changes. Here is a snippet code of MainActivity.java class depicting the listener:

// We need to detect scrolling changes in the RecyclerView
rvCities.setOnScrollListener(new RecyclerView.OnScrollListener() {

  // Keeps track of the overall vertical offset in the list
  int verticalOffset;

  // Determines the scroll UP/DOWN direction
  boolean scrollingUp;

  @Override
  public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
    if (newState == RecyclerView.SCROLL_STATE_IDLE) {
      if (scrollingUp) {
        if (verticalOffset > tToolbar.getHeight()) {
          toolbarAnimateHide();
        } else {
          toolbarAnimateShow(verticalOffset);
        }
      } else {
        if (tToolbar.getTranslationY() < tToolbar.getHeight() * -0.6 && verticalOffset > tToolbar.getHeight()) {
          toolbarAnimateHide();
        } else {
          toolbarAnimateShow(verticalOffset);
        }
      }
    }
  }

  @Override
  public final void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    verticalOffset += dy;
    scrollingUp = dy > 0;
    int toolbarYOffset = (int) (dy - tToolbar.getTranslationY());
    tToolbar.animate().cancel();
    if (scrollingUp) {
      if (toolbarYOffset < tToolbar.getHeight()) {
        if (verticalOffset > tToolbar.getHeight()) {
          toolbarSetElevation(TOOLBAR_ELEVATION);
        }
        tToolbar.setTranslationY(-toolbarYOffset);
      } else {
        toolbarSetElevation(0);
        tToolbar.setTranslationY(-tToolbar.getHeight());
      }
    } else {
      if (toolbarYOffset < 0) {
        if (verticalOffset <= 0) {
          toolbarSetElevation(0);
        }
        tToolbar.setTranslationY(0);
      } else {
        if (verticalOffset > tToolbar.getHeight()) {
          toolbarSetElevation(TOOLBAR_ELEVATION);
        }
        tToolbar.setTranslationY(-toolbarYOffset);
      }
    }
  }
});

Don’t feel intimidated by this code. Basically, we need two variables: verticalOffset to keep track of the overall scrolled vertical space, and scrollingUp to keep track of the scrolling direction. These belongs to a new instance of an inner class RecyclerView.OnScrollListener that listen for scrolling changes on the Recycler and calculate the appropriate hide/show behavior on the Toolbar.

Threshold

As you can see in the code, I’m using a threshold of 60% visible before hiding the Toolbar but feel free to adjust it to whatever suits your needs.

Hide/Show animation

The basic idea behind a nice smooth animation is to detect when the scrolling action is idle to perform the hide/show transition, and calculate Toolbar’s Y-axis offset when is not. Once we do the calculations we can call to one of the helper methods to perform the animation:

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void toolbarSetElevation(float elevation) {
  // setElevation() only works on Lollipop
  if (AndroidUtils.isLollipop()) {
    tToolbar.setElevation(elevation);
  }
}

private void toolbarAnimateShow(final int verticalOffset) {
  tToolbar.animate()
      .translationY(0)
      .setInterpolator(new LinearInterpolator())
      .setDuration(180)
      .setListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationStart(Animator animation) {
          toolbarSetElevation(verticalOffset == 0 ? 0 : TOOLBAR_ELEVATION);
        }
      });
}

private void toolbarAnimateHide() {
  tToolbar.animate()
      .translationY(-tToolbar.getHeight())
      .setInterpolator(new LinearInterpolator())
      .setDuration(180)
      .setListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
          toolbarSetElevation(0);
        }
      });
}

Elevation

The method View.setElevation(float) that makes the little nice shadow appear below the Toolbar, works only on Android Lollipop and newer devices. That means we need to make sure not to call this method on devices running older versions of Android.

With everything in place, we are now ready to enjoy a pretty neat “show/hide Toolbar animation when scrolling” in our apps.

Code

You can find the full source code described here in my Github repo. Feel free to leave any feedback or questions in the comments below.

Thanks to Barak Neeman for proof reading this post. Happy coding!

EDIT: I made a new utility class to simplify hide/show of Toolbar. Check RecyclerViewUtils.ShowHideToolbarOnScrollingListener. Also check the code in MainActivity2.java to see how to use it (much more cleaner).

– Randy S.

Advertisements

33 thoughts on “How to hide/show Android Toolbar when scrolling (Google Play Music’s behavior)

  1. But what happen when, you change orientation on device, and save offset of recycleview, for restoring? The offset value will set on impossible -2000 and the toolbar will show every time you move, without looking on direction.

    Like

  2. First of all – this thing that you did is amazing!

    For the question:
    I have solve the problem with using
    verticalOffset = rvCities.computeVerticalScrollOffset();

    instead of
    verticalOffset += dy;

    And with this you do not need saving and restoring. And with restoring… vertical offest with horizontal and vertical layout is different, so you can’t using straight saving.

    Like

    1. Current Support Library version has RecyclerView.computeVerticalScrollOffset() with protected visibility. Fortunately, Google pull the right move in making it public in latest version of library. I just updated the code to make it work with version 22.2.+ and as a bonus I made a new utility class RecyclerViewUtils.ShowHideToolbarOnScrollingListener to simplify hide/show of Toolbar. Check the code in MainActivity2.java to see how to use it (much more cleaner).

      Thanks for the suggestion.

      Like

  3. Hi , I want to ask can I use in fragment ? my toolbar is on main activity which contain a fragment container , but my recyclerview is at Fragment, is that possible to make it hide toolbar?

    Like

      1. Hi , about Toolbar tToolbar = (Toolbar) findViewById(R.id.tToolbar); should I implement on Fragment also? If implement fragment , will be the layout get null ? Thanks

        Like

      2. It depends on how you want to layout your UI. If you have just one Fragment move your Toolbar inside of it. If you have multiple Fragments and they all share the same Toolbar, leave it in your Activity. Either way, when adding your Fragment (using TransactionManager) call the addOnScrollListener(tToolbar) on your Fragment so you can pass the listener to the RecyclerView.

        Like

  4. Hi , Thanks for your explanation . It was really great animation , but I facing one problem when I scroll to top reach maximum , the toolbar missing but I scroll down a bit , it appear again.

    Like

    1. Hi,

      It shouldn’t be too difficult to refactor ShowHideToolbarOnScrollingListener from https://github.com/rylexr/android-show-hide-toolbar/blob/master/app/src/main/java/com/tinbytes/samples/showhidetoolbar/util/RecyclerViewUtils.java for WebViews. After all, you can see it’s just a listener for scrolling events that extends RecyclerView.OnScrollListener. Check this code http://stackoverflow.com/questions/14752523/how-to-make-a-scroll-listener-for-webview-in-android to see how to make a scroll listener for WebViews. I hope this helps.

      Like

  5. Thank you very much for you work! It works perfect.

    By the way how can I use it with SwipeRefreshLayout? If I simply add it to top level of layout I get toolbar overlapped by update progress animation..

    Like

      1. Take the Toolbar out of the SwipeRefreshLayout. Use a FrameLayout as root and then add both to it, your Toolbar and your SwipeRefreshLayout. I’m sure this solves your problem.

        Like

    1. You could also set the top padding/margin to whatever the Toolbar’s height is. To avoid messing with the autohide feature, you could allow the Swipe gesture to happen only when Toolbar is fully visible. Someway or the other, you’ll have to code a little bit to make this works as you expected. I hope this helps you.

      Like

  6. Thank you for advices!

    I tried to change elevation but without success. Now I’ll try to change SwipeRefreshLayout top margin when toolbar is visible.

    Eventually I can try to make custom refresh animation or redesign UI.

    Like

    1. I found solution! SwipeRefreshLayout have function which can adjust start and end vertical position of the progress view.

      mSwipeRefreshLayout.setProgressViewOffset( false, start, end );

      Like

  7. This looks awesome but will this also work within a Collapsing toolbar layout like how google play music does it when you are inside an Album? Thanks!

    Like

    1. Hi. I’m not sure if it works with it. It should work with whatever View type. I recommend you take a look at Behaviors. It allows you to achieve something similar but in an Android standard way.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s