AppBarLayout / CoordinatorLayout / DesignLibrary / NestedScrollingChild / NestedScrollView / TabLayout

Design Library – Part 3

At Google I/O 2015 the Material Design Support Library was announced, and with it creating material apps targeting API levels below API 19 suddenly got a lot easier. In this series we’ll look at taking the RSS Reader app that we used as a basis for the Material series, and re-write it to make full use of the new Design Support Library. Previously we looked at getting a basic tab bar working and in this article we’ll look at getting some nice toolbar hiding and showing on scroll behaviour working.

Before we begin I think that it is worth pointing out that adapting the existing code to include some really nice behaviours will require absolutely no Java code. Please read the previous sentence again! NO. JAVA.

This is where some of the real power built in to the design support library really comes to prominence, as we get a lot of behaviour built in to the new controls – we just have to hook it up which can, in many cases, be done entirely within our layouts.

So the first thing to look at is out main layout which we’re going to need to modify once again:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:id="@+id/main_layout"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <android.support.design.widget.AppBarLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

    <android.support.v7.widget.Toolbar
      android:id="@+id/toolbar"
      android:layout_width="match_parent"
      android:layout_height="?attr/actionBarSize"
      app:layout_scrollFlags="scroll|enterAlways" />

    <android.support.design.widget.TabLayout
      android:id="@+id/tab_layout"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:clipToPadding="false"
      android:paddingLeft="@dimen/home_offset"
      app:tabMode="scrollable" />

  </android.support.design.widget.AppBarLayout>

  <android.support.v4.view.ViewPager
    android:id="@+id/viewpager"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior" />
  
</android.support.design.widget.CoordinatorLayout>

We have a couple of new Layout types which we’ve added in here. The outer one is CoordinatorLayout. This is the main workhorse which does an awful lot for us yet requires very little in the way of configuration. It is essentially a marshaller which applies externally defined behaviours to the child Views within CoordinatorLayout. This is a bit of an abstract concept, so let’s actually try and explain it a little better by examining what is going in the rest of this layout.

The next new layout is AppBarLayout which now wraps our Toolbar and TabLayout. AppBarLayout defines some common scrolling behaviours which are common to app bars and is what defines the scrolling behaviour which we’re looking to achieve. If we look at the additional layout parameter we’ve added to the Toolbar we can see that we’ve specified a couple of scroll flags which are the behaviours which will be applied to that specific control. scroll means that the toolbar will scroll in direct relation to other scroll events, and enterAlways means that it will scroll on any downwards scroll event. In other words scroll means that this view will move as we scroll and enterAlways will provide a quick return when we scroll downwards.

So we’ve spoken about scroll events, but not actually discussed where these scroll events come from, but that should be fairly obvious if we think about it: The behaviour that we’re after is to hide the toolbar when the user scrolls the contents of the current ViewPager page. So we need the toolbar to respond to scroll events originating from the ViewPager. If we look at the line we added to the ViewPager it is a layout parameter (meaning that it will be handled by the parent layout – the CoordinatorLayout and defines the behaviour that we wish to implement.

The behaviour is the contract between two distinct Views and CoordinatorLayout is responsible for connecting the two Views together. In our case AppBarLayout defines a behaviour named ScrollingViewBehavior which collects scroll events from the View it gets attached to and passes them back to AppBarLayout. AppBarLayout then applies these scrolling events to its child Views according to the scroll flags that each one has declared.

So as scroll events occur within the ViewPager, these get passed to the AppBarLayout which then applies them accordingly to the Toolbar and we get the scrolling behaviour that we require.

However, if we try that it doesn’t work. This caused me a lot of head scratching because everything looked as it should. I referred to Chris Banes’ excellent Cheesesquare code to confirm how this should work and I couldn’t see any difference in this area of the code. The only difference I noticed was that within each ViewPager fragment Chris was using a RecyclerView whereas I was using a WebView. It was after looking at the source of RecyclerView v22.2.0 that I noticed that it implements an interface named NestedScrollingChild which is part of the v4 support library.

Next I looked at this and tried to implement my own subclass of WebView which implemented NestedScrollingChild and implemented its methods using NestedScrollingChildHelper as the javadocs suggest. However, I didn’t manage to get this working.

Next I looked for other classes which implement NestedScrollingChild and found another View in the v4 support library named NestedScrollView which behaves much like ScrollView whilst implementing NestedScrollingChild.

I tried wrapping my standard WebView inside this:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <WebView
    android:id="@+id/web_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

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

This now started generating the required scroll events and I got the behaviour I was after:

Although there’s quite a lot of new information here, the actual changes we needed to make to get some pretty nice behaviour are actually quite minor. We get an awful lot from a few small changes thanks to CoordinatorLayout and AppBarLayout (and the behaviour that it defines).

In the next article we’ll move on to the Floating Action Button.

The source code for this article is available here.

© 2015, Mark Allison. All rights reserved.

Copyright © 2015 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

1 Comment

  1. Hi mark,

    I was wondering how is this going to work when you will have to select another fragment from the navigationDrawer and hide the viewPager and the tabs ?

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.