Android CoordinatorLayout

Filed Under: Android

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

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.

android coordinatorlayout default behaviour

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.

CoordinatorLayout Behaviors

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.

  • The Behaviors only work on the direct child of the CoordinatorLayout.
  • It’s necessary for the CoordinatorLayout to be the root layout of the activity

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="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://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?
android coordinator layout button default behaviour

To define the custom Behavior we need to be aware of two important elements:

  • child : It’s the view on which the behavior would be performed.
  • dependency : It’s the view which will trigger the behavior on the child

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.

Attaching the Behavior to android CoordinatorLayout

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:

android coordinatorlayout

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.

Android CoordinatorLayout Example Project Structure

android coordinatorlayout example

Android CoordinatorLayout Example Code

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="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://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.

android coordinatorlayout example

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.

Reference: Official Doc

Comments

  1. pranay sawant says:

    In ‘layoutDependsOn’ what is dependecy view. It is not clear, can you explain in more detail?

Leave a Reply

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

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages