Android GoogleMap-Like BottomSheetBehavior

| Page Views: 36

In this class we will look at yet another BottomSheetBehavior implementation.

We learn from and re-create an open source example of BottomSheetBehavior mimicking Google Map Behavior created by @miguelhincapie.

It makes use of a library called CustomBottomSheetBehavior and is maintained by @miguelhincapie.

Demo

Here's the project demo.

Android CustomBottomSheetBehavior

1. Installation

We start by installing the library as it is third party. We can easily do this via android studio through the gradle build system.

So proceed over to app level build.gradle and add the following under the dependencies closure:

implementation 'com.mahc.custombottomsheetbehavior:googlemaps-like:0.9.1'

Sync your project after that.

2. Create User Interface

Well we will have three layouts.

(a). activity_main.xml

In our activity_main.xml we will have a CoordinatorLayout. Inside it we include a FrameLayout, a placeholder that can show our dummy map.

Our ToolBar will be placed in our AppBarLayout.

Then followed by a ViewPager for our pages.

We will then have our NestedScrollView, with our BottomSheet content placed inside it.

We then have a FloatingActionButton and lastly a custombottomsheetbehavior.

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    android:id="@+id/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.mahc.custombottomsheet.MainActivity">

    <FrameLayout
        android:id="@+id/dummy_framelayout_replacing_map"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/darker_gray"
        android:fitsSystemWindows="true"/>
    <!--</FrameLayout>-->
    <!--<fragment-->
    <!--android:layout_width="match_parent"-->
    <!--android:layout_height="match_parent"-->
    <!--android:id="@+id/support_map"-->
    <!--android:name="com.google.android.gms.maps.SupportMapFragment"/>-->

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbarlayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay"
        app:layout_behavior="@string/ScrollingAppBarLayoutBehavior">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:popupTheme="@style/AppTheme.PopupOverlay"/>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="@dimen/anchor_point"
        android:background="@color/colorAccent"
        app:layout_behavior="@string/BackDropBottomSheetBehavior"
        android:fitsSystemWindows="true">
    </android.support.v4.view.ViewPager>

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:behavior_peekHeight="@dimen/bottom_sheet_peek_height"
        android:id="@+id/bottom_sheet"
        app:layout_behavior="@string/BottomSheetBehaviorGoogleMapsLike"
        app:anchorPoint="@dimen/anchor_point"
        app:behavior_hideable="true"
        android:fitsSystemWindows="true">

        <include
            layout="@layout/bottom_sheet_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"/>
    </android.support.v4.widget.NestedScrollView>

    <android.support.design.widget.FloatingActionButton
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        app:layout_anchor="@id/bottom_sheet"
        app:layout_anchorGravity="top|right|end"
        android:src="@drawable/ic_action_go"
        android:layout_margin="@dimen/fab_margin"
        app:layout_behavior="@string/ScrollAwareFABBehavior"
        android:clickable="true"
        android:focusable="true"/>

    <com.mahc.custombottomsheetbehavior.MergedAppBarLayout
        android:id="@+id/mergedappbarlayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_behavior="@string/MergedAppBarLayoutBehavior"/>

</android.support.design.widget.CoordinatorLayout>
(b). bottom_sheet_content.xml

Next we have the bottom_sheet_content layout. At the root we have a LinearLayout.

This layout will be displaying our content which will be rendered in TextViews and ImageViews,so we add those widgets as well as ImageButton.

Here are the xml widgets we make use of in this layout.

  1. TextView.
  2. ImageView.
  3. Button.
  4. ImageButton

as well as Viewgroups like:

  1. LinearLayout.
  2. RelativeLayout.
  3. FrameLayout.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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:orientation="vertical"
    android:paddingBottom="16dp"
    android:background="@android:color/white">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="@dimen/bottom_sheet_peek_height"
        android:background="@color/colorPrimary"
        android:paddingTop="8dp"
        android:paddingStart="16dp"
        android:paddingEnd="16dp"
        android:paddingBottom="8dp">

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <TextView
                android:id="@+id/bottom_sheet_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Title dummy"
                android:textColor="@android:color/white"
                android:textSize="19sp"/>

            <TextView
                android:id="@+id/text_dummy1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Text dummy 1234 1234"
                android:textColor="@android:color/white"
                android:textSize="12sp"
                android:layout_marginTop="5dp"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Text dummy qwer asdf zxcv"
                android:textColor="@android:color/white"
                android:textSize="12sp"
                android:layout_marginTop="5dp"/>

        </LinearLayout>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="5 min"
            android:textColor="@android:color/white"
            android:textSize="14sp"
            android:layout_alignParentEnd="true"
            android:layout_centerVertical="true"
            android:paddingTop="8dp"/>

    </RelativeLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center_horizontal"
        android:paddingStart="16dp"
        android:paddingEnd="16dp"
        android:paddingBottom="12dp"
        android:background="@drawable/border_bottom">

        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent">

            <ImageButton
                android:id="@+id/button1"
                android:layout_width="70dp"
                android:layout_height="60dp"
                android:src="@android:drawable/ic_dialog_map"
                android:layout_centerHorizontal="true"
                android:background="?attr/selectableItemBackgroundBorderless"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="BUTTON 1"
                android:textColor="@android:color/black"
                android:layout_alignParentBottom="true"/>
        </RelativeLayout>

        <LinearLayout
            android:id="@+id/establecimiento_layout_favoritos_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_marginEnd="20dp"
            android:layout_marginStart="20dp">

            <ImageButton
                android:id="@+id/button2"
                android:layout_width="70dp"
                android:layout_height="60dp"
                android:src="@android:drawable/ic_dialog_info"
                android:layout_centerHorizontal="true"
                android:background="?attr/selectableItemBackgroundBorderless"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="BUTTON 2"
                android:textColor="@android:color/black"/>
        </LinearLayout>

        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent">

            <ImageButton
                android:id="@+id/establecimiento_share_button"
                android:layout_width="70dp"
                android:layout_height="60dp"
                android:src="@android:drawable/ic_menu_share"
                android:layout_centerHorizontal="true"
                android:background="?attr/selectableItemBackgroundBorderless"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="BUTTON 3"
                android:textColor="@android:color/black"
                android:layout_alignParentBottom="true"/>
        </RelativeLayout>
    </LinearLayout>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/border_bottom">

        <ImageView
            android:id="@+id/establecimiento_icon_sucursales"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:srcCompat="@android:drawable/ic_secure"
            android:layout_marginEnd="16dp"
            android:layout_marginTop="12dp"
            android:layout_marginStart="16dp"
            android:layout_alignParentStart="true"
            android:layout_alignParentTop="true"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_toEndOf="@id/establecimiento_icon_sucursales"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="16dp">

            <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="48dp">

                <Button
                    android:id="@+id/establecimiento_sucursal_row_button"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Text dummy 1"
                    android:layout_gravity="center_vertical"
                    android:background="?attr/selectableItemBackgroundBorderless"
                    android:textAllCaps="false"
                    android:textColor="@android:color/black"/>
            </FrameLayout>

            <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="48dp">

                <Button
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Text dummy 2"
                    android:layout_gravity="center_vertical"
                    android:background="?attr/selectableItemBackgroundBorderless"
                    android:textAllCaps="false"
                    android:textColor="@android:color/black"/>
            </FrameLayout>

            <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="48dp">

                <Button
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="Text dummy 3"
                    android:layout_gravity="center_vertical"
                    android:background="?attr/selectableItemBackgroundBorderless"
                    android:textAllCaps="false"
                    android:textColor="@android:color/black"/>
            </FrameLayout>

        </LinearLayout>

    </RelativeLayout>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="56dp"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="APACHE LICENSE"
            android:layout_gravity="center_vertical"
            android:textSize="18sp"
            android:textColor="@android:color/darker_gray"/>
    </FrameLayout>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"

        android:text="@string/dummy_text"
        android:textSize="12sp"
        android:textColor="@android:color/black"/>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="620dp"
        android:background="@color/colorAccent"
        android:layout_marginTop="40dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="Your remaining content here"
            android:textColor="@android:color/white" />

    </FrameLayout>
</LinearLayout>
(c). pager_item.xml

Then the pager_item.xml.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/imageView"
        android:scaleType="centerCrop"/>
</LinearLayout>

Our Java Code

(a). ItemPagerAdapter.java

First create a new class. Call it a ItemPagerAdapter and let's make it derive from android.support.v4.view.PagerAdapter.

public class ItemPagerAdapter extends android.support.v4.view.PagerAdapter {..}

PagerAdapter acts as the base class for providing the adapter to populate pages inside of that ViewPager.

Add three instance fields in our class:

    Context mContext;
    LayoutInflater mLayoutInflater;
    final int[] mItems;

First the Context is an interface to global information about an application environment. Normally Context allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities, broadcasting and receiving intents, etc.

In this case it will allow us inflate our layout into a view.

Then we have a LayoutInflater, a class that allows us instantiates a layout XML file into its corresponding View objects.

The int array will hold us image ids.

Then our constructor is responsible for first receiving a context object which will be required during inflation of our pager_item layout and secondly the int items which will hold our image ids.

We will also perform the inflation inside that constructor.

    public ItemPagerAdapter(Context context, int[] items) {
        this.mContext = context;
        this.mLayoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        this.mItems = items;
    }

We then override the other methods.

Here's the full code for this class:

package com.mahc.custombottomsheet;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;

import com.mahc.custombottomsheet.R;

public class ItemPagerAdapter extends android.support.v4.view.PagerAdapter {

    Context mContext;
    LayoutInflater mLayoutInflater;
    final int[] mItems;

    public ItemPagerAdapter(Context context, int[] items) {
        this.mContext = context;
        this.mLayoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        this.mItems = items;
    }

    @Override
    public int getCount() {
        return mItems.length;
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == ((LinearLayout) object);
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        View itemView = mLayoutInflater.inflate(R.layout.pager_item, container, false);
        ImageView imageView = (ImageView) itemView.findViewById(R.id.imageView);
        imageView.setImageResource(mItems[position]);
        container.addView(itemView);
        return itemView;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView((LinearLayout) object);
    }
}
(b). MainActivity.java

Finally here's our main activity. Maybe it's been generate to you by android studio.

So go ahead and as instance fields add an int array to hold our Drawables.

Also we'll have a TextView:

    int[] mDrawables = {
            R.drawable.cheese_3,
            R.drawable.cheese_3,
            R.drawable.cheese_3,
            R.drawable.cheese_3,
            R.drawable.cheese_3,
            R.drawable.cheese_3
    };

    TextView bottomSheetTextView;

We start by referencing our widgets CoordinatorLayout as well as View to represent the bottom sheet.

        CoordinatorLayout coordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinatorlayout);
        View bottomSheet = coordinatorLayout.findViewById(R.id.bottom_sheet);

Then we reference the BottomSheetBehaviorGoogleMapsLike. This is the library we are using to create a bottom sheet behavior like that in Google maps.

        final BottomSheetBehaviorGoogleMapsLike behavior = BottomSheetBehaviorGoogleMapsLike.from(bottomSheet);

Then listen to various callbacks:

        behavior.addBottomSheetCallback(new BottomSheetBehaviorGoogleMapsLike.BottomSheetCallback() {
            @Override
            public void onStateChanged(@NonNull View bottomSheet, int newState) {

                }
            }

            @Override
            public void onSlide(@NonNull View bottomSheet, float slideOffset) {
            }
        });

We are interested especially in the state change event where we get various state of our BottomSheetBehaviorGoogleMapsLike.

Here's the code:

package com.mahc.custombottomsheet;

import android.support.annotation.NonNull;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike;
import com.mahc.custombottomsheetbehavior.MergedAppBarLayout;
import com.mahc.custombottomsheetbehavior.MergedAppBarLayoutBehavior;

public class MainActivity extends AppCompatActivity {

    int[] mDrawables = {
            R.drawable.cheese_3,
            R.drawable.cheese_3,
            R.drawable.cheese_3,
            R.drawable.cheese_3,
            R.drawable.cheese_3,
            R.drawable.cheese_3
    };

    TextView bottomSheetTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setDisplayHomeAsUpEnabled(true);
            actionBar.setTitle(" ");
        }

        /**
         * If we want to listen for states callback
         */
        CoordinatorLayout coordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinatorlayout);
        View bottomSheet = coordinatorLayout.findViewById(R.id.bottom_sheet);
        final BottomSheetBehaviorGoogleMapsLike behavior = BottomSheetBehaviorGoogleMapsLike.from(bottomSheet);
        behavior.addBottomSheetCallback(new BottomSheetBehaviorGoogleMapsLike.BottomSheetCallback() {
            @Override
            public void onStateChanged(@NonNull View bottomSheet, int newState) {
                switch (newState) {
                    case BottomSheetBehaviorGoogleMapsLike.STATE_COLLAPSED:
                        Log.d("bottomsheet-", "STATE_COLLAPSED");
                        break;
                    case BottomSheetBehaviorGoogleMapsLike.STATE_DRAGGING:
                        Log.d("bottomsheet-", "STATE_DRAGGING");
                        break;
                    case BottomSheetBehaviorGoogleMapsLike.STATE_EXPANDED:
                        Log.d("bottomsheet-", "STATE_EXPANDED");
                        break;
                    case BottomSheetBehaviorGoogleMapsLike.STATE_ANCHOR_POINT:
                        Log.d("bottomsheet-", "STATE_ANCHOR_POINT");
                        break;
                    case BottomSheetBehaviorGoogleMapsLike.STATE_HIDDEN:
                        Log.d("bottomsheet-", "STATE_HIDDEN");
                        break;
                    default:
                        Log.d("bottomsheet-", "STATE_SETTLING");
                        break;
                }
            }

            @Override
            public void onSlide(@NonNull View bottomSheet, float slideOffset) {
            }
        });

        MergedAppBarLayout mergedAppBarLayout = findViewById(R.id.mergedappbarlayout);
        MergedAppBarLayoutBehavior mergedAppBarLayoutBehavior = MergedAppBarLayoutBehavior.from(mergedAppBarLayout);
        mergedAppBarLayoutBehavior.setToolbarTitle("Title Dummy");
        mergedAppBarLayoutBehavior.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                behavior.setState(BottomSheetBehaviorGoogleMapsLike.STATE_ANCHOR_POINT);
            }
        });

        bottomSheetTextView = (TextView) bottomSheet.findViewById(R.id.bottom_sheet_title);
        ItemPagerAdapter adapter = new ItemPagerAdapter(this,mDrawables);
        ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
        viewPager.setAdapter(adapter);

        behavior.setState(BottomSheetBehaviorGoogleMapsLike.STATE_ANCHOR_POINT);
        //behavior.setCollapsible(false);
    }
}
(d). How to Run.

The download contains both a library as well as code. Just go to the app folder and you find the code for the sample.

Go over to your app level build.gradle and add statement via the implementation statement just as we did in the Installation section.

No. Resource Action
1. GitHub Download
2. GitHub Browse
3. Project Thanks to @miguelhincapie

How do You Feel after reading this?

According to scientists, we humans have 8 primary innate emotions: joy, acceptance, fear, surprise, sadness, disgust, anger, and anticipation. Feel free to tell us how you feel about this article using these emotes or via the comment section. This feedback helps us gauge our progress.

Help me Grow.

I set myself some growth ambitions I desire to achieve by this year's end regarding this website and my youtube channel. Am halfway. Help me reach them by:




Recommendations


What do You Think


Previous Post Next Post