Creating the Flexible Space with Image Pattern on Android
The Design support library for Android makes it easy to create great animations. Let’s look at how to create an app with a collapsing Toolbar
animation. It’s know as Flexible Space with Image pattern, a popular scrolling technique.
I didn’t know this pattern actually had a name until Ian Lake happened to point it out for me.
Flexible Space with Image Pattern
A scrolling technique that supports an image header with a scrollable view below it. Upon scrolling, the ‘Flexible Space’ (image header) gets tinted with a color. At the same time, it collapses into a Toolbar — Material Design guidelines
This pattern is a popular scrolling technique. In familiar terms, you can see this in WhatsApp’s contact detail screen.
Here’s the step-by-step screenshots of the Flexible Space animation. Should give you a clear picture of what’s going on.
Notice the Toolbar dynamically changes color, depending on the image. It takes on the most dominant color present in the image.
We can do this with the Palette API, but more on that later.
You might immediately start to freak out with the amount of code it might take. But rest assured, there’s no scary Java code to write. Most of it is XML, so cheers to that!
Getting Started
Start by adding the Design Support Library to your app/build.gradle file.
dependencies {
…
compile 'com.android.support:design:25.0.2'
}
Layout Structure
As always, we’ll get started with the XML first. Open your activity.xml layout.
Here’s the layout skeleton.
<android.support.design.widget.CoordinatorLayout > <android.support.design.widget.AppBarLayout > <android.support.design.widget.CollapsingToolbarLayout > <ImageView /> <android.support.v7.widget.Toolbar /> </android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<!-- Your scrollable content here --></android.support.design.widget.CoordinatorLayout>
Design Support UI Widgets
I understand if all these layouts might appear new to you. But you won’t have to worry. I’ll do my best to explain them, in the easiest way.
A powerful FrameLayout
that specifies behavior for child views for various interactions. It also allows anchoring of floating views in your layout.
It is a special kind of vertical LinearLayout
. It helps respond to its children’s scroll events (scroll gestures). Additionally, it’s responsible for implementing many features of Material Design’s AppBarLayout
.
But, there’s one thing to note. Its usage relies on being a direct child within CoordinatorLayout
. The layout skeleton above demonstrates this.
It is a Toolbar
wrapper which makes the ‘Flexible Space’ pattern possible. It collapses the image header while decreasing the expanded title to a Toolbar
title.
What’s left is the ImageView
which holds our actual header’s image and Toolbar
which we’re familiar with.
4. FloatingActionButton
I’m sure you’re familiar with what a Floating Action Button is, aren’t you? Android gave it a thumbs up by giving us an official UI widget. It’s a part of the Design Support Library.
Defining the XML Layout
Alright, with that out of the way lets get to the actual XML.
A word of caution. The layout below might look threatening in size!
This is not complex code. XML just tends to be a little verbose. But you’re welcome to try replicating the Flexible Space scroll animation in Java. Then I’m sure you’d truly appreciate how easy the Design Support library is.
However, I have highlighted the essential lines you need to focus on.
What you need to know from this?
- line 24:
layout_scrollFlags
Tells theCollapsingToolbarLayout
and its children, how to behave on a scroll.
Here’s what the flags mean, straight from the developer’s blog:
- scroll:
This flag should be set for all views that want to scroll off-screen. For views that do not use this flag, they’ll remain pinned to the top of the screen. - exitUntilCollapsed:
Causes the view to scroll off until it is ‘collapsed’ before exiting - snap:
Enables the expanded view to snap to either a collapsed state, or expanded state. There is no in-between state. If theView
has been dragged more towards expanding, it expands completely. If its dragged more towards collapsing, theView
collapses completely.
- line 33:
layout_collapseMode
Indicates how theImageView
reacts while collapsing on-scroll.
There are 2 collapse modes:
COLLAPSE_MODE_PARALLAX
(use forImageView
)COLLAPSE_MODE_PIN
(use forToolbar
)
- line 63:
layout_behavior
TheCoordinatorLayout
performs most of its magic usingBehavior
. Behaviors tell how its child Views must interact with each other.
A Behavior implements one or more interactions that may include drags, swipes, flings, or any other gestures.
- line 73:
layout_anchor
Remember we spoke about anchoring Views earlier? This attribute tellsFloatingActionButton
to anchor’ itself toAppBarLayout
. - line 74:
layout_anchorGravity
This attribute tells ourView
where to position itself, with respect to its ‘anchored’View
. In this case, this attribute tells our FAB, to position itself to the bottom right of theAppBarLayout
.
Here’s what the Android Studio’s Preview pane shows us.
But wait. Does the expanded title look clear enough to you? I’m sure it’s not.
Whenever you display a label (TextView
) against a background image, use a ‘scrim‘. It will help make the text more readable.
TIP: Use a Scrim for clear, readable text
A Scrim is a semi-transparent gradient layer that helps text appear more readable against backgrounds.
Just below your ImageView
, add a simple View with a gradient Drawable
as background.
<View
android:layout_width="match_parent"
android:layout_height="160dp"
android:layout_gravity="bottom"
android:background="@drawable/scrim"/>
Here’s the Drawable
scrim.
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="90"
android:endColor="@android:color/transparent"
android:startColor="#66000000"/>
</shape>
That’s a gradient with 40% opacity black from the bottom, to transparent at the top. 66 is the HEX code for 40%.
If you want to play around transparency in hex codes, this wonderful StackOverflow answer helps.
So with the gradient applied, see the difference for yourself.
While the difference is subtle, the right one’s much better right? Additionally, you can apply another scrim for the Toolbar. It can help the back and overflow icons be more visible. I leave this as an exercise for you.
I’ve already mentioned a hint above on parallax scroll. But there’s more to it.
Getting the Parallax scroll right
We can achieve parallax scrolling with ImageView
by setting its layout_collapseMode
to parallax
.
Parallax Scrolling involves the background moving at a slower rate to the foreground. — Creativebloq.com
Did you notice an extra collapse mode attribute for the Toolbar
?
Yes, you must use this as well. Why? Because, while the ImageView
should collapse, the Toolbar
must persist on scrolling.
Toolbar
must use collapseMode:pin
, because we want it to persist and remain on top, as the user scrolls down.
Note that I haven’t set any color for the Toolbar
. The CollapsingToolbarLayout
takes care of this. It dynamically picks up a color from our image and sets it to the Toolbar
.
If you’re interested in Parallax scrolling with Tabs instead, this one’s for you.
Floating Action Button
Floating action buttons are used for a promoted action
I’ll stick to calling it FAB from now on. If you tried to create a FAB earlier, you’d realize how hard it was.
But with the Design Support Library, that’s not the case anymore. The layout above shows, how easy it is!
Notice that I haven’t explicitly defined a size for the FAB. By default, it takes its regular width and height of 56dp. But, you can define this by using the following attribute.
app:fabSize="regular"
Optionally, you can set the fabSize
as mini
, which is a miniature version of the FAB at 40dp in size.
Next in the layout, we align the FAB with AppBarLayout
. We do this with the layout_anchor
attribute.
<android.support.design.widget.FloatingActionButton
...
app:layout_anchor="@+id/appbar"
app:layout_anchorGravity="bottom|right|end" />
That’s a wrap for the XML part. The good news is, this XML is all that’s required to trigger the Flexible Space scroll animation.
But wait! There’s still one thing that seems a bit off. What about the FAB’s action?
After scrolling, the ‘Add’ action goes hidden. It becomes available, only when you scroll all the way to the top. This is a bit inconvenient isn’t it? So let’s fix that.
The Hidden Action
Once the AppBar collapses, we need to show the FAB’s action somewhere.
Now I’m not saying this is the recommended approach. But my suggestion is that once the FAB vanishes, we add the action to the Toolbar’s menu.
To do this we’ll need a listener first. We have to listen to AppBarLayout
expand and collapse states. To be precise, we need AppBarLayout.OffsetChangedListener.
If the AppBarLayout
’s ‘verticalOffset’ is zero, then its fully expanded. So when the verticalOffset is almost equal to the fully expanded height, add the action to Toolbar
’s menu.
First, let’s look at how to set the listener.
appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
// Vertical offset == 0 indicates appBar is fully expanded.
if (Math.abs(verticalOffset) > 200) {
appBarExpanded = false;
invalidateOptionsMenu();
} else {
appBarExpanded = true;
invalidateOptionsMenu();
}
}
});
Some key takeaway points from the above code snippet.
verticalOffset
returns negative values, so we wrap it withMath.abs(verticalOffset)
.appBarExpanded
a boolean that tells us whenAppBarLayout
is expanded or collapsed.invalidateOptionsMenu()
called every timeAppBarLayout
’s height crosses a threshold (200 in our case).
Updating the Toolbar Menu
invalidateOptionsMenu()
helps update our Toolbar
Menu
. But we need to tell our Menu
when to add and remove the extra Action.
First, we need a reference for the existing Menu
. You can get this from the onCreateOptionsMenu()
method.
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
collapsedMenu = menu;
return true;
}
collapsedMenu
is a global variable of type Menu
. It allows us to keep a copy of the original Menu
.
Next, we need to update our Menu
. We’re already calling invalidateOptionsMenu()
in the scroll listener. This will trigger the onPrepareOptionsMenu()
method. Hence, we’ll add our dynamic Menu
logic here.
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (collapsedMenu != null
&& (!appBarExpanded || collapsedMenu.size() != 1)) {
//collapsed
collapsedMenu.add("Add")
.setIcon(R.drawable.ic_action_add)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
} else {
//expanded
}
return super.onPrepareOptionsMenu(collapsedMenu);
}
You’ll notice that I haven’t done anything for the expanded state. We don’t need to either. I’ll tell you why.
After onPrepareOptionsMenu()
, the onCreateOptionsMenu()
is called. What do we need to do in the expanded state? The FAB becomes visible, which means we should hide the ‘Add’ action from Toolbar
menu.
onCreateOptionsMenu()
inflates the original Menu
again. So we don’t have to worry about removing the action ourselves.
So now, when the FAB hides, its Action is added to Toolbar
Menu
. When the AppBarLayout
collapses, the ‘Add’ action becomes visible in the Toolbar
Menu
.
Now all that remains is to do the usual UI initialization in Java. So let’s setup our Toolbar first. Then we’ll call in the Palette API.
Setting up the basic UI
We need to initialize our Toolbar
first and then the CollapsingToolbarLayout
. So open your Activity.java and type away. Or you can copy-paste this instead.
toolbar = (Toolbar) findViewById(R.id.anim_toolbar);
setSupportActionBar(toolbar);collapsingToolbar = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
collapsingToolbar.setTitle("Suleiman Ali Shakir");ImageView header = (ImageView) findViewById(R.id.header);
Now you might ask me, why I have set a title for CollapsingToolbarLayout
, instead of Toolbar
? That’s a valid question.
Take a look at the Flexible Space with Image scroll animation. Yes, the GIF which I shared at the beginning of this post. Notice there’s an expanded title, which on scrolling, collapses into the Toolbar
title.
The CollapsingToolbarLayout
handles this for us. Hence we set the title for that, instead of a Toolbar
.
Additionally, the CollapsingToolbarLayout
also handles tinting our Toolbar
using the Palette API. So let’s look at that next.
Dynamic Colors with Palette API
The palette API is a support library that extracts prominent colors from images.
The Palette API offers several color options (variations). Here are the basic four:
- Light
- Dark
- Vibrant
- Muted
To use the Palette library, we first need to add it to your project. So open up your app/build.gradle and add the following line.
dependencies {
…
compile 'com.android.support:palette-v7:25.0.2'
}
Using the Palette API
First, pass the ‘header’ ImageView
’s bitmap to the Palette API. Then the API will generate colors based on the header image, in an AsyncTask.
Once it completes, we can fetch a color we want and set it to CollapsingToolbarLayout
. This, in turn, tints our Toolbar
to our chosen color, when we scroll.
Let’s look at some code.
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.header);
Palette.from(bitmap).generate(new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(Palette palette) {
mutedColor = palette.getMutedColor(R.attr.colorPrimary);
collapsingToolbar.setContentScrimColor(mutedColor);
}
});
I am fetching my bitmap from my resources directory.
But, in a real-world scenario, you would be downloading the image from an URL. Then saving it as a bitmap and passing that to the Palette API.
You can also increase the number of colors generated from the image. Or you can even retrieve the entire color swatch! If you’re interested in playing around with the Palette API, Chris Banes’ post is a great article.
Toolbar Animation in Action
Finally, we’ve completed what’s needed for the Toolbar
animation. In fact, much more than what’s required! So go ahead, run your app and watch the magic.
Notice the blue color the Toolbar takes after collapsing? That’s the ‘mutedColor’ generated from the image, by the Palette API.
To be honest, it was surprising to see such a smooth Toolbar
animation. Even the FAB beautifully reacts upon touch with a higher elevation.
Source Code available on GitHub
Material Design is a powerful visual language that can help you design a brilliant app.
In Android, the Design Support Library makes it easy to create powerful animations, like this one. It allows us to create rich app experiences our users can enjoy.
So how are you using the Design Support Library? Do you have your own take on the ‘Flexible Space with Image’ animation pattern? Let me know in the comments below.
Suleiman is a UX UI Designer & App developer who loves creating simple, usable, yet beautiful experiences for people. He’s currently pursuing his Masters in UX Design at Rutgers, NJ. Check out his portfolio.
Originally published at blog.iamsuleiman.com on June 2, 2015.