Up until now we’ve used android CoordinatorLayout in plenty of our tutorials. Yet we haven’t gone into it’s details. In this tutorial we’ll discuss and customise the CoordinatorLayout in android app.
Android CoordinatorLayout is a super-powered FrameLayout. It has a lot more to offer than it seems. It has additional level of control over it’s child views. It coordinates the animations and transitions of child views with one another. Let’s create a new Android Studio project and choose the Basic Activity template that has CoordinatorLayout
by default. The layout consists of a Floating Action Button. Clicking it displays a SnackBar as shown below. Did you notice that the Floating Action Button animates up to make way for the SnackBar and comes back when the SnackBar disappear? This is no magic. It’s how the Floating Action Button behaves inside a CoordinatorLayout. Note : CoordinatorLayout can also expand the ToolBar to show more content or collapse it while scrolling, something that is commonly seen when you scroll a WhatsApp User’s profile screen. Don’t worry, we’ll look into this in a later tutorial. A question that would be popping up in our heads now - How does the CoordinatorLayout know what to do with the child view? The answer lies in the next section.
The FAB within a CoordinatorLayout has been specified a default Behavior that causes it to animate accordingly when another view interacts with it. Do a Ctrl/CMD+ Click on the FloatingActionButton in the layout/activity and you shall see a Behavior has been defined on the class with an annotation. It should look like this:
@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class)
FloatingActionButton.Behavior is the default Behavior class used on the FAB. We can define our own Behaviors by extending the class CoordinatorLayout.Behavior. Here T is the class whose Behavior we wish to define. In the above case it is CoordinatorLayout.Behavior.
Now let’s add a Button widget at the bottom of our screen. activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.journaldev.coordinatorlayoutbehaviours.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_main" />
<!--<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_dialog_email" />-->
<android.support.v7.widget.AppCompatButton
android:layout_height="wrap_content"
android:layout_gravity="bottom|start"
android:text="CLICK ME"
android:id="@+id/button"
android:layout_margin="@dimen/fab_margin"
android:layout_width="match_parent"/>
</android.support.design.widget.CoordinatorLayout>
We’ve commented out the FAB from the above layout. Now replace the FloatingActionButton listener with the AppCompatButton in the MainActivity.java as shown below.
AppCompatButton fab = (AppCompatButton) findViewById(R.id.button);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Hey Button. Define a Custom Behaviour. Else I'll take your space", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
This is how the application looks now. Any guesses? To define the custom Behavior we need to be aware of two important elements:
In the above case the AppCompatButton is the child and the SnackBar is the dependency. Note: For the FloatingActionButton default behavior, the dependency is not just the SnackBar. There are other View elements too that trigger a behavior on the FloatingActionButton. Let’s start by creating our own custom Behavior class that moves up the AppCompatButton. Let’s name it CustomMoveUpBehavior.java.
public class CustomMoveUpBehavior extends CoordinatorLayout.Behavior {
public CustomMoveUpBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
The two neccessary methods that should be overridden in the above class are layoutDependsOn and onDependentViewChanged. Let’s add override them in our class.
package com.journaldev.coordinatorlayoutbehaviours;
import android.os.Build;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.view.View;
public class CustomMoveUpBehavior extends CoordinatorLayout.Behavior {
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight());
child.setTranslationY(translationY);
return true;
}
}
The layoutDependsOn checks whether the dependency that’ll trigger the behavior is an instanceof SnackBar. The onDependentViewChanged is used to move up the child view(AppCompatbutton) based on a basic math calculation.
To attach the CustomMoveUpBehavior.java we’ll create a Custom AppCompatButton and add the annotations as shown below.
package com.journaldev.coordinatorlayoutbehaviours;
import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.support.v7.widget.AppCompatButton;
import android.util.AttributeSet;
@CoordinatorLayout.DefaultBehavior(CustomMoveUpBehavior.class)
public class CustomButton extends AppCompatButton {
public CustomButton(Context context) {
super(context);
}
public CustomButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
Do the following changes in the activity_main.xml and MainActivity.java Replace the AppCompatButton
xml tag with the following one.
<com.journaldev.coordinatorlayoutbehaviours.CustomButton
android:layout_height="wrap_content"
android:layout_gravity="bottom|start"
android:text="CLICK ME"
android:id="@+id/button"
android:layout_margin="@dimen/fab_margin"
android:layout_width="match_parent"/>
Replace the respective Button onClickListener
in the MainActivity.java.
CustomButton fab = (CustomButton) findViewById(R.id.button);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Hey Button. Define a Custom Behaviour. Else I'll take your space", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
The application when run now should look like this: Wasn’t it cool? Now let’s try to implement a Custom Behavior for the FAB. We’ll trigger it to rotate and move up when the SnackBar is displayed. We’ve implemented a CustomRotateBehavior.java class. It’s given below.
public class CustomRotateBehavior extends CoordinatorLayout.Behavior {
public CustomRotateBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
float translationY = getFabTranslationYForSnackbar(parent, child);
float percentComplete = -translationY / dependency.getHeight();
child.setRotation(180 * percentComplete);
child.setTranslationY(translationY);
return false;
}
private float getFabTranslationYForSnackbar(CoordinatorLayout parent,
FloatingActionButton fab) {
float minOffset = 0;
final List dependencies = parent.getDependencies(fab);
for (int i = 0, z = dependencies.size(); i < z; i++) {
final View view = dependencies.get(i);
if (view instanceof Snackbar.SnackbarLayout && parent.doViewsOverlap(fab, view)) {
minOffset = Math.min(minOffset,
ViewCompat.getTranslationY(view) - view.getHeight());
}
}
return minOffset;
}
}
The method getFabTranslationYForSnackbar(parent,child)
calculates how far up the screen should the SnackBar come up for the FAB to start changing. Before we do the relevant changes, let’s browse through our project structure.
Now instead of extending the FloatingActionButton we can just define the app:layout_behavior in the FloatingActionButton view and point it to our subclass. This is how our activity_main.xml looks now.
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:app="https://schemas.android.com/apk/res-auto"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.journaldev.coordinatorlayoutbehaviours.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_main" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:layout_behavior="com.journaldev.coordinatorlayoutbehaviours.CustomRotateBehavior"
android:src="@android:drawable/arrow_down_float" />
<!--<com.journaldev.coordinatorlayoutbehaviours.CustomButton
android:layout_height="wrap_content"
android:layout_gravity="bottom|start"
android:text="CLICK ME"
android:id="@+id/button"
android:layout_margin="@dimen/fab_margin"
android:layout_width="match_parent"/>-->
</android.support.design.widget.CoordinatorLayout>
The MainActivity.java looks like this now.
package com.journaldev.coordinatorlayoutbehaviours;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.AppCompatButton;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
/*CustomButton fab = (CustomButton) findViewById(R.id.button);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "You've added the CustomMoveUpBehavior. Now I'll let you move", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});*/
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Hey FAB. Please Rotate 180 degrees when I'm up.", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
Let’s run our application one last time to see the new Behavior. This brings an end to Android CoordinatorLayout Example. We started with browsing through the default Behavior of the FAB widget and have ended up with overriding it with our own Rotation Behavior. Not to miss out the Behavior on a Button too. It’s a long way. You can download the Android CoordinatorLayoutBehaviours Project from the below link.
Download Android CoordinatorLayout Example Project
Reference: Official Doc
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
Now this what I call an excellent tutorial. Man you don’t know how hard I have been toiling with CoordinatorLayout and could not figure out how it achieves its behaviour. Thanks lots
- Tonnnie
How to fix this error?-----> error: non-static method convertStreamToString(InputStream) cannot be referenced from a static context
- Habtie Destaw
In ‘layoutDependsOn’ what is dependecy view. It is not clear, can you explain in more detail?
- pranay sawant