• Handling Scrolls with CoordinatorLayout
  • Original article by CODEPATH
  • Translation from: The Gold Project
  • This article is permalink: github.com/xitu/gold-m…
  • Translator: Feximin

The overview

CoordinatorLayout extends the ability to create a variety of scrolling effects in Google’s Material Design. Currently, the framework provides several ways to work without having to write any custom animation code. These effects include:

  • Slide the Floating Action Button up and down to make room for the Snackbar.

  • Expand or collapse the Toolbar or header to provide space for the main content area.

  • Controls which view expands and collapses at what rate, including animations for parallax scrolling effects.

Code sample

Chris Banes from Google made a cool demo of CoordinatorLayout with other features in the Design Support Library.

The full source code is available on Github. This project is one of the easiest ways to understand CoordinatorLayout.

Set up the

First, make sure you follow the Design Support Library instructions.

Floating Action Button and Snackbar

CoordinatorLayout can create the hover effect by using the layout_Anchor and layout_GRAVITY properties. For more information, see the Floating Action Buttons guide.

When rendering a Snackbar, it usually appears at the bottom of the visible screen. The Floating Action button must be moved up to make room.

The animation effect automatically appears whenever the CoordinatorLayout is used as the main layout. The Float Action button has a default behavior that moves the button up the height of the Snackbar when it detects that the Snackbar has been added.

 <android.support.design.widget.CoordinatorLayout
        android:id="@+id/main_content"
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

   <android.support.v7.widget.RecyclerView
         android:id="@+id/rvToDoList"
         android:layout_width="match_parent"
         android:layout_height="match_parent"/>

   <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|right"
        android:layout_margin="16dp"
        android:src="@mipmap/ic_launcher"
        app:layout_anchor="@id/rvToDoList"
        app:layout_anchorGravity="bottom|right|end"/>
 </android.support.design.widget.CoordinatorLayout>
Copy the code

Expand and unpack the Toolbar

First, make sure you’re not using the outdated ActionBar. And make sure you follow the guidelines for using the ToolBar as an ActionBar. Also make sure that oordinatorLayout is the main layout container.

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

      <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="? attr/actionBarSize"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

</android.support.design.widget.CoordinatorLayout>
Copy the code

Responding to rolling events

Next, we must use a container layout called AppBarLayout to make the ToolBar respond to scrolling events:

<android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/detail_backdrop_height"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        android:fitsSystemWindows="true">

  <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="? attr/actionBarSize"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

 </android.support.design.widget.AppBarLayout>
Copy the code

Note: According to the official Google Documentation, AppBarLayout currently needs to be embedded in CoordinatorLayout as a direct child element.

Then, we need to define an association between the AppBarLayout and the View that we want to scroll to. Add app:layout_behavior to RecyclerView or other scrollable views like NestedScrollView that can be nested. Support in the library have a mapping to AppBarLayout. The special @ string string resources/appbar_scrolling_view_behavior ScrollingViewBehavior, It notifies AppBarLayout when a scrolling event occurs on a particular view. The Behavior must be built on the view that triggers the (scroll) event.

 <android.support.v7.widget.RecyclerView
        android:id="@+id/rvToDoList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">
Copy the code

When the CoordinatorLayout finds that this property has been declared in RecyclerView, it searches the other views contained under it to see if there are any related views associated with the behavior. In this particular case AppBarLayout. ScrollingViewBehavior describes RecyclerView and AppBarLayout dependencies between. Any scrolling event on RecyclerView will trigger a change in the layout of AppBarLayout or any view contained within it.

The RecyclerView scrolling event triggers a change in the view declared in AppBarLayout using the app:layout_scrollFlags property:

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="? attr/actionBarSize"
                app:layout_scrollFlags="scroll|enterAlways"/>

 </android.support.design.widget.AppBarLayout>
Copy the code

For any scroll effect to take effect, you must enable the Scroll flag in the app:layout_scrollFlags property. This sign must be used in conjunction with enterAlways, enter into decay, enter into decay or snap:

  • EnterAlways: View becomes visible when scrolling up. This flag is useful in cases where you are sliding from the bottom of a list and want the Toolbar to show as soon as you slide up.

    Scrolling up refers to the list scrolling up, not up.

    In general, the Toolbar will only be displayed when the list slides to the top, as shown below:

  • EnterAlwaysCollapsed: Usually only when enterAlways is used, the Toolbar will continue to unfold when you are falling:

    If you have declared enterAlways and you have set a minHeight, you can also use enterAlwaysCollapsed. If this is set, your view will only show this minimum height. The view expands to its full height only when it slides to the head:

  • 13. When the scroll sign is set, descent usually causes movement of the entire content:

    By specifying minHeight and exitUntilCollapsed, the rest of the content will first reach the minimum height of the Toolbar and then exit the screen before starting to scroll:

  • snap: Using this option will determine what function to perform if the view is only partially subtracted. If the view’s height is reduced by less than 50% at the end of the slide, it will return to its original position. If this value is greater than 50% of it, it will disappear completely.

Note: In your mind’s eye, put the View that uses the Scroll bit first. This way, the collapsed view will exit first, leaving the elements fixed at the top.

At this point, you should realize that the ToolBar responds to scrolling events.

Create a collapse

To create the collapsible ToolBar effect, we must include the ToolBar in the CollapsingToolbarLayout:

<android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
    <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="? attr/colorPrimary"
            app:expandedTitleMarginEnd="64dp"
            app:expandedTitleMarginStart="48dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="? attr/actionBarSize"
                app:layout_scrollFlags="scroll|enterAlways"></android.support.v7.widget.Toolbar>

    </android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
Copy the code

The result should now appear as:

Normally, we set the Toolbar title. Now we need to collapse the title in the toolbarlayout, not in the Toolbar.

 CollapsingToolbarLayout collapsingToolbar =
              (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
 collapsingToolbar.setTitle("Title");
Copy the code

Note that when CollapsingToolbarLayout is used, make the status bar transparent (API 19) or transparent (API 21) as described in the document. In particular, the following styles should be set in res/values-xx/styles.xml:

<! -- res/values-v19/styles.xml --> <style name="AppTheme" parent="Base.AppTheme">
    <item name="android:windowTranslucentStatus">true</item> </style> <! -- res/values-v21/styles.xml --> <style name="AppTheme" parent="Base.AppTheme">
    <item name="android:windowDrawsSystemBarBackgrounds">true</item>
    <item name="android:statusBarColor">@android:color/transparent</item>
</style>
Copy the code

By enabling the system bar transtransparency effect like above, your layout will fill the content behind the system bar, so you’ll also have to use Android :fitsSystemWindow on layouts that don’t want to be overwritten by the system bar. Another way to add padding to API 19 to prevent the system bar from overwriting the View can be seen here.

Create parallax animation

CollapsingToolbarLayout allows you to create more advanced animations in the CollapsingToolbarLayout, such as using an ImageView that collapses as you collapse it. The height of the title can also be changed as the user swipes.

To create this effect, add an ImageView and declare the app:layout_collapseMode=”parallax” attribute in the ImageView TAB.

<android.support.design.widget.CollapsingToolbarLayout
    android:id="@+id/collapsing_toolbar"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    app:contentScrim="? attr/colorPrimary"
    app:expandedTitleMarginEnd="64dp"
    app:expandedTitleMarginStart="48dp"
    app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="? attr/actionBarSize"
                app:layout_scrollFlags="scroll|enterAlways" />
            <ImageView
                android:src="@drawable/cheese_1"
                app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:scaleType="centerCrop"
                app:layout_collapseMode="parallax"
                android:minHeight="100dp" />

</android.support.design.widget.CollapsingToolbarLayout>
Copy the code

At the bottom of the table

The bottom table is already supported in the Support Design Library v23.2. There are two types of bottom tables supported: persistent and modal. The bottom table of the Persistent type shows the contents of the application, while the modal type shows a menu or simple dialog box.

Bottom table in the form of Persistent

There are two ways to create a bottom table in the Persistent form. The first is to use a NestedScrollView, and then simply embed the content into it. The second is to create an additional RecyclerView embedded in the CoordinatorLayout. If the layout_behavior is predefined @string/bottom_sheet_behavior, then the RecyclerView is hidden by default. Also note that RecyclerView should use wrap_content instead of match_parent, which is a new modification so that the bottom bar only takes up necessary space and not all of it:

<CoordinatorLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/design_bottom_sheet"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_behavior="@string/bottom_sheet_behavior">
</CoordinatorLayout>
Copy the code

The next step is to create RecyclerView. We can create a simple Item that contains only a picture and text, and an adapter that can populate those items.

public class Item {

    private int mDrawableRes;

    private String mTitle;

    public Item(@DrawableRes int drawable, String title) {
        mDrawableRes = drawable;
        mTitle = title;
    }

    public int getDrawableResource() {
        return mDrawableRes;
    }

    public String getTitle() {
        returnmTitle; }}Copy the code

Next, create the adapter:

public class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ViewHolder> {

    private List<Item> mItems;

    public ItemAdapter(List<Item> items, ItemListener listener) {
        mItems = items;
        mListener = listener;
    }

    public void setListener(ItemListener listener) {
        mListener = listener;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new ViewHolder(LayoutInflater.from(parent.getContext())
                .inflate(R.layout.adapter, parent, false));
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.setData(mItems.get(position));
    }

    @Override
    public int getItemCount() {
        return mItems.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {

        public ImageView imageView;
        public TextView textView;
        public Item item;

        public ViewHolder(View itemView) {
            super(itemView);
            itemView.setOnClickListener(this);
            imageView = (ImageView) itemView.findViewById(R.id.imageView);
            textView = (TextView) itemView.findViewById(R.id.textView);
        }

        public void setData(Item item) {
            this.item = item;
            imageView.setImageResource(item.getDrawableResource());
            textView.setText(item.getTitle());
        }

        @Override
        public void onClick(View v) {
            if (mListener != null) {
                mListener.onItemClick(item);
            }
        }
    }

    public interface ItemListener {
        void onItemClick(Item item);
    }
}
Copy the code

The bottom table is hidden by default. We need a click event to trigger the show and hide. Note: Because of this known issue, do not attempt to expand the bottom table in the OnCreate() method.

RecyclerView recyclerView = (RecyclerView) findViewById(R.id.design_bottom_sheet); 

// Create your items
ArrayList<Item> items = new ArrayList<>();
items.add(new Item(R.drawable.cheese_1, "Cheese 1"));
items.add(new Item(R.drawable.cheese_2, "Cheese 2"));

// Instantiate adapter
ItemAdapter itemAdapter = new ItemAdapter(items, null);
recyclerView.setAdapter(itemAdapter);

// Set the layout manager
recyclerView.setLayoutManager(new LinearLayoutManager(this));

CoordinatorLayout coordinatorLayout = (CoordinatorLayout) findViewById(R.id.main_content);
final BottomSheetBehavior behavior = BottomSheetBehavior.from(recyclerView);

fab.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
       if(behavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
         behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
       } else{ behavior.setState(BottomSheetBehavior.STATE_COLLAPSED); }}});Copy the code

You can set the layout property app: Behavior_hideable =true to allow the user to also hide the bottom table by sliding. There are other attributes, including STATE_DRAGGING, STATE_SETTLING, and STATE_HIDDEN. For more, see another tutorial in the table at the bottom.

The bottom table in Modal form

The bottom table in Modal form is basically a Dialog fragment that slides in from the bottom. See this article for more information on how to create this type of fragment. Instead you should inherit BottomSheetDialogFragment DialogFragment.

High-level bottom representation example

There are many complex examples of bottom tables that use the Floating Action Button, which expands or shrinks or changes the state of the table as the user slides. The most famous example is Google Maps with multi-order tables:

The following tutorials and code examples will help you achieve these more complex effects:

  • CustomBottomSheetBehavior Sample – describes the three kinds of states in the table at the bottom of the slide switch back and forth. Refer to the related StackOverflow blog.

  • Grafixartist Bottom Sheet Tutorial – a Tutorial on how to position and animate a Floating Action Button when sliding a Bottom table.

  • You can read this article to further discuss how to simulate the effect of state changes during a Google Map swipe.

It may take quite a bit of experimentation to get the desired results. For certain use cases, you may find the third-party libraries listed below to be an easier option.

Optional third party bottom table

In addition to the official bottom table available in the Design Support Library, there are several optional and very popular third-party libraries that are easier to configure and use for certain uses:

Here are the most common choices and related examples:

  • AndroidSlidingUpPanel – a popular method that implements the bottom table, this should be considered an official alternative.
  • Flipboard/ Bottomsheet – Another alternative that was very popular before the official solution was released.
  • ThreePhasesBottomSheet – Example code to create a multi-order bottom table using a third-party library.
  • Foursquare BottomSheet Tutorial – Describes how to use third-party bottom tables to achieve the same results used in older versions of Foursquare.

Between the official persistent modal and these third-party alternatives, you should be able to run enough experiments to achieve any desired effect.

The CoordinatorLayout fault is rectified

CoordinatorLayout is very powerful but error-prone. If you’re having problems with behaviors, check out these tips:

  • For an example of how to use CoordinatorLayout efficiently, consult the Cheesesquare source code carefully. This repository is an example repository that is continuously updated by Google and reflects the behavior’s best practices. Especially layout for a tabbed ViewPager list and this for a layout for a detail view. Carefully compare your code with cheesesquare’s source code.
  • Ensure that theCoordinatorLayoutDirect subview ofUse of theapp:layout_behavior="@string/appbar_scrolling_view_behavior"Properties. For example, in the case of a pull-down refresh, this property would be includedRecyclerViewSwipeRefreshLayoutNot in the descendants below the second tier.
  • In an application that uses an internal list of itemsViewPagerYou can imagine using coordination between a fragment and a parent activityDescribed hereAs in theViewPagerTo addapp:layout_behaviorProperty, so that the pager scroll event can be passed up and then can beCoordinatorLayoutManagement. But remember, youShould not bewillapp:layout_behaviorProperties are placed anywhere on the fragment or its internal list.
  • Keep in mind thatScrollViewCan’t withCoordinatorLayoutUse them together. You’re going to need something likeThis exampleAs shown in theNestedScrollViewInstead. Include your content inNestedScrollView, and then add to itapp:layout_behaviorThis will make your rolling behavior expected to work.
  • Make sure the root layout of your activity or fragment isCoordinatorLayout. The scroll event does not respond to any other layout.

There are many ways that you can make an error using CoordinatorLayout, and you can add a prompt here when you see an error.

Custom behaviors

An example of a custom behavior is discussed in this article on CoordinatorLayout with Floating Action Buttons.

CoordinatorLayout works by searching for all the definitions that are decorated statically using the APP: Layout_Behavior tag in the XML or programmatically using the @DefaultBehavior annotation in the View class The child View of the CoordinatorLayout Behavior. When a scrolling event occurs, CoorinatorLayout tries to trigger the child views that are declared as dependencies.

To define your own CoordinatorLayout Behavior, you should implement the layoutDependsOn() and onDependentViewChanged() methods. For example, appBarLayout. Behavior defines these two key methods. This behavior is used to trigger changes in the AppBarLayout when a scrolling event occurs.

public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
          return dependency instanceof AppBarLayout;
      }

 public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
          // check the behavior triggered
          android.support.design.widget.CoordinatorLayout.Behavior behavior = ((android.support.design.widget.CoordinatorLayout.LayoutParams)dependency.getLayoutParams()).getBehavior();
          if(behavior instanceof AppBarLayout.Behavior) {
          // do stuff here
          }
 }       
Copy the code

Understand how to implement these custom behaviors. Best method is to study AppBarLayout behaviors and FloatingActionButtion behaviors of these two samples.

Third party scroll and parallax effects library

In addition to using the CoordinatorLayout described above, you can view these popular third party libraries to achieve scrolling and parallax effects between ScrollView, ListView, ViewPager and RecyclerView.

Embed Google Map into AppBarLayout

Because of this confirmed issue, Google Map is not currently supported in AppBarLayout. An update to the Support Design Library in V23.1.0 provides a setOnDragListener() method, which can be useful if drag-and-drop effects are needed in this layout. However, it doesn’t seem to affect scrolling, as this blog post explains.

reference

  • Android-developers.blogspot.com/2015/05/and…
  • Android-developers.blogspot.com/2016/02/and…
  • Code.tutsplus.com/articles/ho…

Diggings translation project is a community for translating quality Internet technical articles from diggings English sharing articles. The content covers Android, iOS, React, front end, back end, product, design and other fields. If you want to see more high-quality translations, please continue to pay attention to The Jingjin Translation Project, official weibo, zhihu column.