Home » Android » How does the Android L contacts app collapse its toolbar?

How does the Android L contacts app collapse its toolbar?

Posted by: admin April 23, 2020 Leave a comment

Questions:

I’ve been trying to reproduce the way that the Contacts app on version 5.0 collapses the toolbar when the listview is scrolled.

Gallery of screenshots demonstrating the desired interaction
Note the collapse of the toolbar in stages, where it displays search+last contact, fades last contact, collapses last contact, collapses search, leaving only the tabs.

So far, I have a toolbar sitting above a recyclerview in a LinearLayout, and the toolbar is used as an actionbar, not standalone.

I can’t figure out how to intercept the touch event on the recyclerview and make it shrink the toolbar, and then return the scroll event to the recyclerview. I tried putting the entire thing in a scrollview, but then the recyclerview couldn’t calculate it’s height properly and displayed no content. I tried overriding onscroll on the recyclerview, and found that it will only notify me when a scroll event started, and provide me with the first visible card id.

The way that looks right, but I can’t get working for the life of me, is this:

getSupportActionBar().setHideOnContentScrollEnabled(true);

Which returns:

 Caused by: java.lang.UnsupportedOperationException: Hide on content scroll is not supported in this action bar configuration.

Using a traditional actionbar, putting a toolbar below it, and setting hideoncontentscrollenabled also didn’t work, scrolling never triggered the hide method on the actionbar.

— edit —
I was able to get hideOnContentScrollEnabled working on a listview with a traditional actionbar, but the behavior is not the same as the contacts app. This is clearly not the method they used– it simply triggers .hide() on the actionbar when a fling event occurs on a listview, which is notably different from the contacts app, which drags the toolbar along with the scroll event.
— /edit —

So I abandoned that route, and put fill_parent on the cardview height, and animated a collapse on the toolbar. But how do I trigger it so that it follows the touch event and then returns the touch event to the recyclerview?

activity_main.xml

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    >

    <android.support.v7.widget.Toolbar
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="?android:attr/actionBarSize"
        android:background="@color/colorPrimary"
        />

    <fragment android:name="me.myapplication.FragmentTab"
          android:id="@+id/tab_fragment"
          android:layout_width="match_parent"
          android:layout_height="wrap_content" />
</LinearLayout>

fragment_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:padding="8dp"
    android:background="#eeeeee"
    >

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"
        />

</LinearLayout>

styles.xml

...
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
...

MainActivity.java

Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);

// Disable the logo in the actionbar, as per material guidelines
toolbar.getMenu().clear();
toolbar.setTitle("My toolbar");
setSupportActionBar(toolbar);
How to&Answers:

I haven’t investigated the source code yet but this guy seems to have made life easy yet enlightening.

https://github.com/ksoichiro/Android-ObservableScrollView

EDIT

Google has just released Android Design Library. Please take a look as it contains all the effects of collapsing toolbars and much more.

Answer:

Well, I have no idea how they do it, but… why don’t you take a peek to the source code? Luckily for us, the Contacts app is still open-source on Android L (others weren’t as lucky as Contacts, like Mail, which does not work anymore on L; or Keyboard, which they stopped updating anymore since the launch of their propietary Google Keyboard).

Anyway, here’s the source code I think you should look at:
https://github.com/android/platform_packages_apps_contacts/blob/master/src%2Fcom%2Fandroid%2Fcontacts%2Factivities%2FActionBarAdapter.java

Note the method update(boolean skipAnimation) in Line 311, which calls animateTabHeightChange(int start, int end) (Line 437).

My guess is all the magic happens there 😉

Answer:

As of June 2015, your desired effect can be accomplished via the so called CollapsingToolbarLayout of the new design support library.

Based on the sample code here, I am figuring that:

  • the search cardview is child of the toolbar
  • the missed call cardview belongs to the collapsingtoolbar with the collapseMode attribute set to pin
<android.support.design.widget.AppBarLayout
    android:id="@+id/appbar"
    android:layout_width="match_parent"
    android:layout_height="112dp"
    android:fitsSystemWindows="true"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

    <android.support.design.widget.CollapsingToolbarLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:minHeight="?attr/actionBarSize"
        android:fitsSystemWindows="true"
        app:layout_scrollFlags="scroll|enterAlwaysCollapsed|enterAlways">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            android:fitsSystemWindows="false"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
            app:layout_collapseMode="pin"
            app:layout_scrollFlags="scroll|enterAlways">

            <!-- Search layout -->
            <android.support.v7.widget.CardView   
            </android.support.v7.widget.CardView>

        </android.support.v7.widget.Toolbar>

        <!-- Last call card view-->
        <android.support.v7.widget.CardView
            app:layout_collapseMode="pin">               
        </android.support.v7.widget.CardView>

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

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

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <android.support.design.widget.TabLayout
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:background="@color/primary_color"
        app:layout_scrollFlags="scroll"/>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

Answer:

No third party library is required now! Android is officially providing library. You can collapse the toolbar and do many other tweaks.

Check this android-developer’s blog

And don’t forget to add this dependency in your build.gradle file.

compile 'com.android.support:design:22.2.0'

Answer:

I found this library that seems to do what you’re looking for: https://github.com/kmshack/Android-ParallaxHeaderViewPager and this https://github.com/flavienlaurent/NotBoringActionBar

You can play the video to see the behavior: https://www.youtube.com/watch?v=sCP-b0a1x5Y

It might not be the ‘new’ standard way of doing it with ToolBar, but it might give you an idea by inspecting the code. It seems to attach a OnScrollListener to the scrolling content and then trigger changes on the size of the bar.

Answer:

For me https://mzgreen.github.io/2015/06/23/How-to-hideshow-Toolbar-when-list-is-scrolling%28part3%29/ has helped. A source code is found here: https://github.com/mzgreen/HideOnScrollExample/tree/master/app/src/main.

A RecycleView in your layout should look like:

<android.support.v7.widget.RecyclerView
    android:id="@+id/recyclerView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="fill_vertical"
    app:layout_behavior="@string/appbar_scrolling_view_behavior" />

Note that after starting an application 2 toolbars appear (actionbar and toolbar). So in your activity.java you should write so:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Hide ActionBar.
    supportRequestWindowFeature(WindowCompat.FEATURE_ACTION_BAR);
    getSupportActionBar().hide();
    setContentView(R.layout.your_activity_layout);
    ...

The toolbar is customized as shown here: https://stackoverflow.com/a/26548766/2914140. I mean, it appears without title and any other elements, so you can add them in a layout.

Answer:

Android’s Contact app doesn’t have an easy plug-and-play solution that you can grab for use in your own app.

It does a full implementation, essentially doing it the same way you would do it if you were implementing it from scratch. For context, before looking at the code, keep in mind how the views are laid out:

https://github.com/android/platform_packages_apps_contacts/blob/lollipop-release/res/layout/quickcontact_activity.xml

The MultiShrinkScroller is a FrameLayout which intermediates the scrolling behavior, but the main stuff is in a LinearLayout, so reducing the height of the higher views will “scroll” the lower views upwards.

The key file for the implementation is this one:

https://github.com/android/platform_packages_apps_contacts/blob/lollipop-release/src/com/android/contacts/widget/MultiShrinkScroller.java

public void scrollTo(int x, int y) {
    final int delta = y - getScroll();
    boolean wasFullscreen = getScrollNeededToBeFullScreen() <= 0;
    if (delta > 0) {
        scrollUp(delta);
    } else {
        scrollDown(delta);
    }
    updatePhotoTintAndDropShadow();
    updateHeaderTextSizeAndMargin();
    //... other stuff
}

private void scrollUp(int delta) {

    // Collapse higher views first
    if (getTransparentViewHeight() != 0) {
        final int originalValue = getTransparentViewHeight();
        setTransparentViewHeight(getTransparentViewHeight() - delta);
        setTransparentViewHeight(Math.max(0, getTransparentViewHeight()));
        delta -= originalValue - getTransparentViewHeight();
    }

    // Shrink toolbar as needed
    final ViewGroup.LayoutParams toolbarLayoutParams
            = mToolbar.getLayoutParams();
    if (toolbarLayoutParams.height > getFullyCompressedHeaderHeight()) {
        final int originalValue = toolbarLayoutParams.height;
        toolbarLayoutParams.height -= delta;
        toolbarLayoutParams.height = Math.max(toolbarLayoutParams.height,
                getFullyCompressedHeaderHeight());
        mToolbar.setLayoutParams(toolbarLayoutParams);
        delta -= originalValue - toolbarLayoutParams.height;
    }

    // Finally, scroll content if nothing left to shrink
    mScrollView.scrollBy(0, delta);
}

updatePhotoTintAndDropShadow(); and updateHeaderTextSizeAndMargin(); handle the change in tint and text as it gets collapsed so that it turns into the look and feel of a regular ActionBar/ToolBar.

You could grab the MultiShrinkScroller file itself and adapt it for your own use, but there are probably easier implementations nowadays (including those from Android’s design library).