In my previous post I described CoordinatorLayout
and how to write custom behaviour for views. There were a few comments asking how to write custom scrolling view that plays well with CoordinatorLayout
and AppBarLayout
. Some readers were also curious why AppBarLayout
is working with RecyclerView
and not with ListView
.
Let’s take a brief look at the magic that shows/hides AppBarLayout
on scrolls coming from a RecyclerView
.
Inspecting the source code of AppBarLayout
we can see that the default behavior annotation points to @DefaultBehavior(AppBarLayout.Behavior.class)
The AppBarLayout.Behavior implements methods that react to the scroll events:
void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed)
void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)
void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes)
boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes)
void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target)
This explains why AppBarLayout
is changing its location when placed inside a CoordinatorLayout
. Now the question is how CoordinatorLayout
gets notified by its child when scrolling occurs. If we take a look at RecyclerView
class signature we might find our answer.
Say hello to NestedScrollingChild and NestedScrollingParent interfaces.
We can see that RecyclerView
implements NestedScrollingChild
interface. Let’s see what documentation says about it:
This interface should be implemented by View subclasses that wish to support dispatching nested scrolling operations to a cooperating parent ViewGroup.
To receive dispatched nested scrolling operations CoordinatorLayout
needs to implement NestedScrollingParent
interface.
This interface should be implemented by ViewGroup subclasses that wish to support scrolling operations delegated by a nested child view.
Let’s dive into NestedScrollingChild
and NestedScrollingParent
communication process.
When the child is about to begin scroll (that corresponds to onTouch action down event) it calls onScrollStarted method. With each scroll step child is calling two methods dispatchPreScroll
and dispatchScroll
. First method offers an opportunity for the parent view to consume some or all of the scroll operation before the child view consumes it. If the parent returns true the child should reduce its scroll by the value consumed by the parent. Method dispatchNestedScroll
is called to report scroll progress to the parent. It contains both the consumed and the unconsumed scroll values. The parent may treat these values differently:
An implementation may choose to use the consumed portion to match or chase scroll position of multiple child elements, for example. The unconsumed portion may be used to allow continuous dragging of multiple scrolling or draggable elements, such as scrolling a list within a vertical drawer where the drawer begins dragging once the edge of inner scrolling content is reached.
CoordinatorLayout
acts as a proxy. It receives callbacks from the child and forwards it to its children behavior classes.
When scroll process finishes the child calls onScrollStoped
callback.
Implementing custom NestedScrollingChild view.
Now that we know how it works, we can implement a sample application with a custom view that detects scroll and delegates it to CoordinatorLayout
.
Let’s start with the View
public class NestedScrollingChildView extends View implements NestedScrollingChild, OnGestureListener
Good guys from the Android Team wanted to make the task a little bit easier for us and introduced the NestedScrollingChildHelper
.
Helper class for implementing nested scrolling child views compatible with Android platform versions earlier than Android 5.0 Lollipop (API 21).
All we need to do is create helper instance and delegate appropriate methods.
By default nested scroll is disabled. We can enable it in the constructor.
setNestedScrollingEnabled(true);
Now we need to detect the scroll. Let’s use an instance of GestureDetectorCompat
to reduce boilerplate.
@Override
public boolean onTouchEvent(MotionEvent event){
return mDetector.onTouchEvent(event);
}
In the onDown
method we should call onStartScrolling
and pass vertical axis flag as parameter.
@Override
public boolean onDown(MotionEvent e) {
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
return true;
}
From the onScroll
method we call dispatchNestedPreScroll
with calculated distanceY followed by the dispatchNestedScroll
call. Since our view doesn’t scroll at all let’s pass 0 as the consumed and unconsumed scroll value.
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
dispatchNestedPreScroll(0, (int) distanceY, null, null);
dispatchNestedScroll(0, 0, 0, 0, null);
return true;
}
The last part is to call stopNestedScroll
when the scroll process is over. Since GestureDetectorCompat
doesn’t provide appropriate callback we need to do it in a custom implementation of our view’s onTouchEvent
method.
@Override
public boolean onTouchEvent(MotionEvent event){
final boolean handled = mDetector.onTouchEvent(event);
if (!handled && event.getAction() == MotionEvent.ACTION_UP) {
stopNestedScroll();
}
return true;
}
For the simplicity we won’t handle the fling in this example.
And voilà

Full source code can be found here: https://github.com/ggajews/nestedscrollingchildviewdemo. If you still want to make ListView
work with the CoordinatorLayout
you need to implement your own NestedScrollingChild ListView. Now you know how to do it.