Creating a custom CoordinatorLayout Behavior
1. Intro
Trust me, it's not as complicated as it might first look like. You either drink too much coffee, or need to take your face off of the screen for a while.
A CoordinatorLayout
is this thing:
public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent {
...
}
from the support library ( compile 'com.android.support:design:25.3.1'
), which seems at first like an innocent old FrameLayout
, until you discover its real powers - Behavior
s!
A Behavior
is an object attached to a child of a CoordinatorLayout
, which defines its interaction with another View
, ( a dependency) within the same layout.
So, a CoordinatorLayout
monitors every movement of its children, and notifies the ones with attached Behavior
s for a dependency change.
Something like this:
The SnackBar
here is called a dependency - which makes sense, because the child depends on its movement in order to behave in a certain way specified by its attached Behavior
.
Following Material Design guidelines, this behavior is the correct interaction between a SnackBar
and a FloatingActionButton
.
2. Let's get started!
Everything should run in a context or under the supervision of a CoordinatorLayout
, so let's create a fresh Activity with a CoordinatorLayout
as its content-view root element.
Create a new project, and add a new Activity by selecting the "Basic Activity" template:
Take a look at activity_main.xml, it should contain the following:
<?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"
tools:context="com.rany.albeg.wein.behaviors.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
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:srcCompat="@android:drawable/ic_dialog_email" />
</android.support.design.widget.CoordinatorLayout>
Since the default behavior of FloatingActionButton
is to run away from the SnackBar
like we wish to implement ourselves, take it off of the XML tree and insert something like an AppCompatButton
:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout>
...
<android.support.v7.widget.AppCompatButton
android:id="@+id/bt_click_me"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:text="Click me"/>
</android.support.design.widget.CoordinatorLayout>
Edit MainActivity
accordingly, and modify onCreate()
:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
AppCompatButton btn = (AppCompatButton) findViewById(R.id.bt_click_me);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Yeah buddy!", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
}
3. Implementation
To create a Behavior
we'll need to create a class that extends CoordinatorLayout.Behavior<V>
where V
is a type of a class which extends View
and represents the type of the child - in our case it'll be AppCompatButton
:
public class CustomMoveUpBehavior extends
CoordinatorLayout.Behavior<AppCompatButton> {
...
}
We're almost done! To complete programming our custom behavior and define the unique interaction between a child and a dependency, we'll need to:
- Override
layoutDependsOn(...)
This method gets called ( at least once in response to a layout request ) by the parent CoordinatorLayout
to determine whether the supplied child view has another specific sibling view as a layout dependency.
In other words, CoordinatorLayout
sees the SnackBar
and asks us :
"Hey, is this AppCompatButton depends on this SnackBar ?" and we should answer "Yes!" in order to further react in accordance to SnackBar
's movement:
@Override
public boolean layoutDependsOn(CoordinatorLayout parent,
AppCompatButton child,
View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
- Override
onDependentViewChanged(...)
This method gets called by the parent CoordinatorLayout
after the relevant layoutDependsOn(...)
returns true
, in which we need to actually move stuff!
In our case, for the button to run away from the SnackBar
, we'll want to translate its Y position to be the difference between SnackBar
's current Y translation and its height!
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent,
AppCompatButton child,
View dependency) {
float tY = dependency.getTranslationY() - dependency.getHeight();
child.setTranslationY(tY);
return true;
}
We return true
to say: "Hey CoordinatorLayout! Our Behavior changed AppCompatButton's position"
CustomMoveUpBehavior
is done and ready to be attached!
There are several ways to attach a Behavior
to View
, and I'll choose to go with the common way - via XML.
In order to attach CustomMoveUpBehavior
to AppCompatButton
via XML, add the following constructor first:
public CustomMoveUpBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
and now jump into activity_main.xml
and just add the following attribute to AppCompatButton
's XML tag:
<android.support.v7.widget.AppCompatButton
...
app:layout_behavior="your.package.name.CustomMoveUpBehavior"
...
/>
replacing your.package.name as necessary.
Play it!