Adv

Android GoogleMap-Like BottomSheetBehavior

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"
    
    
    
    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
    
    
    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 
    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
Share
Adv



Share an Example

Share an Example

Browse
What is the capital of Egypt? ( Cairo )