Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing Stretchable Bottom Sheets and Collapsing Toolbars in Android

Tech May 12 2

In Android development, creating interactive bottom panels and collapsible interfaces enhances user experience significantly. This article explores two powerful components: BottomSheetBehavior for stretchable bottom sheets and AppBarLayout for collapsing toolbar patterns.

BottomSheetBehavior Implementation

The BottomSheetBehavior from Google's Material Design library enables views to function as draggable bottom panels within a CoordinatorLayout. This is particularly useful when combined with scrolling content like RecyclerView.

XML Layout Configuration

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

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:id="@+id/btn_back"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/icon_back"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            android:layout_marginTop="12dp"
            android:layout_marginStart="15dp"
            android:padding="5dp"/>

        <TextView
            android:id="@+id/txt_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Content Details"
            android:textColor="#ff000000"
            android:textSize="16sp"
            app:layout_constraintTop_toTopOf="@+id/btn_back"
            app:layout_constraintBottom_toBottomOf="@+id/btn_back"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

        <ImageView
            android:id="@+id/img_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toBottomOf="@+id/btn_back"
            android:layout_marginTop="20dp"
            android:layout_marginHorizontal="45dp"/>

    </androidx.constraintlayout.widget.ConstraintLayout>

    <LinearLayout
        android:id="@+id/sliding_panel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
        app:behavior_peekHeight="200dp"
        android:orientation="vertical">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="73dp"
            android:elevation="0.5dp"
            android:background="@drawable/bg_action_container"
            android:layout_marginHorizontal="20dp">

            <TextView
                android:id="@+id/btn_favorite"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Favorite"
                android:textSize="10sp"
                android:drawableTop="@drawable/selector_star"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toStartOf="@+id/btn_preview"
                app:layout_constraintHorizontal_chainStyle="spread"/>

            <TextView
                android:id="@+id/btn_preview"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Preview"
                android:textSize="10sp"
                android:drawableTop="@mipmap/icon_preview"
                app:layout_constraintTop_toTopOf="@+id/btn_favorite"
                app:layout_constraintStart_toEndOf="@+id/btn_favorite"
                app:layout_constraintEnd_toStartOf="@+id/btn_share" />

            <TextView
                android:id="@+id/btn_share"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Share"
                android:textSize="10sp"
                android:drawableTop="@mipmap/icon_share"
                app:layout_constraintTop_toTopOf="@+id/btn_favorite"
                app:layout_constraintStart_toEndOf="@+id/btn_preview"
                app:layout_constraintEnd_toStartOf="@+id/btn_download"/>

            <TextView
                android:id="@+id/btn_download"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Save"
                android:textSize="10sp"
                android:drawableTop="@mipmap/icon_download"
                app:layout_constraintTop_toTopOf="@+id/btn_favorite"
                app:layout_constraintStart_toEndOf="@+id/btn_share"
                app:layout_constraintEnd_toEndOf="parent"/>

        </androidx.constraintlayout.widget.ConstraintLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@drawable/bg_panel_background"
            android:elevation="0.5dp"
            android:layout_marginTop="20dp"
            android:orientation="vertical">
            
            <View
                android:layout_width="105dp"
                android:layout_height="5dp"
                android:layout_gravity="center"
                android:background="#333333"
                android:layout_marginTop="10dp"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="More Recommendations"
                android:textColor="#ff333333"
                android:textSize="16sp"
                android:textStyle="bold"
                android:layout_marginStart="20dp"
                android:layout_marginTop="15dp"/>

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recycler_similar"
                android:layout_width="match_parent"
                android:layout_height="300dp"
                app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
                app:spanCount="3"
                android:layout_marginTop="5dp">

            </androidx.recyclerview.widget.RecyclerView>

        </LinearLayout>

    </LinearLayout>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Key Configuration Attributes

  • behavior_peekHeight: Sets the collapsed height (default state). Use setPeekHeight() in code.
  • behavior_hideable: Enables complete hiding when swiping down. Use setHideable(true/false).
  • behavior_skipCollapsed: Skips the collapsed state when expanding/collapsing. Use setSkipCollapsed(true/false).

Java Implementation with State Callbacks

View slidingPanel = findViewById(R.id.sliding_panel);
BottomSheetBehavior panelBehavior = BottomSheetBehavior.from(slidingPanel);

panelBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
    @Override
    public void onStateChanged(@NonNull View bottomSheet, @BottomSheetBehavior.State int newState) {
        String stateDescription;
        switch (newState) {
            case BottomSheetBehavior.STATE_DRAGGING:
                stateDescription = "STATE_DRAGGING";
                break;
            case BottomSheetBehavior.STATE_SETTLING:
                stateDescription = "STATE_SETTLING";
                break;
            case BottomSheetBehavior.STATE_EXPANDED:
                stateDescription = "STATE_EXPANDED";
                break;
            case BottomSheetBehavior.STATE_COLLAPSED:
                stateDescription = "STATE_COLLAPSED";
                break;
            case BottomSheetBehavior.STATE_HIDDEN:
                stateDescription = "STATE_HIDDEN";
                break;
            default:
                stateDescription = "STATE_UNKNOWN";
                break;
        }
        Log.d("PanelDemo", "Current state: " + stateDescription);
    }

    @Override
    public void onSlide(@NonNull View bottomSheet, float slideOffset) {
        Log.d("PanelDemo", "Slide offset: " + slideOffset);
    }
});

Collapsing Toolbar Layout

The collapsing toolbar pattern uses AppBarLayout combined with CoordinatorLayout to create smooth collapsing effects as users scroll content.

<?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">
    
    <com.dokiwei.lib_base.widget.TitleBar
        android:id="@+id/custom_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:tb_left_icon="@mipmap/icon_return"
        app:tb_title="Invitation Script"
        app:tb_title_size="16sp"
        app:tb_title_style="bold" />

<androidx.coordinatorlayout.widget.CoordinatorLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#00000000"
        app:elevation="0dp">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:minHeight="10dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <FrameLayout
                android:id="@+id/advertisement_slot"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@color/white"
                android:layout_marginTop="10dp"
                app:layout_constraintTop_toTopOf="parent" />

        </androidx.constraintlayout.widget.ConstraintLayout>
    </com.google.android.material.appbar.AppBarLayout>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <androidx.core.widget.NestedScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fillViewport="true">

            <com.allen.library.shape.ShapeConstraintLayout
                android:id="@+id/content_container"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_marginTop="10dp"
                android:layout_marginHorizontal="16dp"
                android:paddingHorizontal="12dp"
                android:paddingTop="20dp"
                app:layout_constraintTop_toBottomOf="@+id/v"
                app:layout_constraintBottom_toBottomOf="parent"
                app:shapeCornersTopLeftRadius="10dp"
                app:shapeCornersTopRightRadius="10dp"
                app:shapeSolidColor="#ffffff">

                <androidx.core.widget.NestedScrollView
                    android:layout_width="match_parent"
                    android:layout_height="0dp"
                    app:layout_constraintHeight_percent="0.98"
                    app:layout_constraintBottom_toBottomOf="parent">

                    <com.dokiwei.lib_base.widget.ProgressWebview
                        android:id="@+id/web_content"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content" />

                </androidx.core.widget.NestedScrollView>

            </com.allen.library.shape.ShapeConstraintLayout>
        </androidx.core.widget.NestedScrollView>

    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

</LinearLayout>

Scroll Flags Explained

The layout_scrollFlags attribute controls how the AppBarLayout responds to scrolling:

  • scroll: Enables scrolling behavior; required for any colalpsing functionality
  • exitUntilCollapsed: Maintains minimum height (minHeight) even when fully collapsed
  • enterAlways: Displays the toolbar immediately when scrolling up
  • snap: Snaps to either fully collapsed or fully expanded states based on scroll distance

These components work together seamlessly within the CoordinatorLayout to create polished, interactive UI patterns that adapt fluidly to user interactions.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.