Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Cách triển khai Bottom Sheet trong Android

Hướng dẫn này trình bày cách triển khai Bottom Sheet (bảng trượt từ dưới lên) sử dụng Design Support Library và ButterKnife (thư viện view binding cũ). Bottom Sheet là thành phần UI Material Design phổ biến cho menu ngữ cảnh, chi tiết đơn hàng, bộ lọc, v.v.

Lưu ý: ButterKnife đã deprecated (ngừng phát triển). Dự án mới nên dùng View Binding (built-in) hoặc Data Binding. Code dưới đây đã được cập nhật dùng View Binding.

Các bước thực hiện

Bước 1: Tạo dự án mới

Mở Android Studio, chọn File → New Project và điền thông tin cần thiết.

Bước 2: Thêm dependency (build.gradle Module:app)

dependencies {
    // Material Components (bao gồm BottomSheetBehavior)
    implementation 'com.google.android.material:material:1.9.0'

    // View Binding (thay thế ButterKnife) - bật trong android block:
    // buildFeatures { viewBinding true }
}

Bước 3: Layout chính (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
    xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

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

    </com.google.android.material.appbar.AppBarLayout>

    <include layout="@layout/content" />

    <!-- Bottom Sheet đặt SAU content chính -->
    <include layout="@layout/bottom_sheet" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Bước 4: Layout nội dung chính (res/layout/content.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity"
    tools:showIn="@layout/activity_main">

    <Button
        android:id="@+id/btnBottomSheet"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hiện Bottom Sheet"
        android:layout_centerInParent="true"/>

</RelativeLayout>

Bước 5: Layout Bottom Sheet (res/layout/bottom_sheet.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    android:id="@+id/bottomSheet"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="16dp"
    app:behavior_hideable="true"
    app:behavior_peekHeight="60dp"
    app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_gravity="center_vertical"
        android:weightSum="3">

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:text="Chi tiết đơn hàng"
            android:textColor="#444"
            android:textSize="18sp"
            android:textStyle="bold" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="end"
            android:text="₫435.000"
            android:textSize="15sp"
            android:textStyle="bold" />

    </LinearLayout>

    <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Gà nướng BBQ" android:layout_marginTop="8dp"/>
    <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Cơm gà xối mỡ" android:layout_marginTop="4dp"/>

    <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Địa chỉ giao hàng" android:textColor="#444" android:textStyle="bold" android:layout_marginTop="16dp"/>
    <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Số 123, Đường ABC, Quận 1, TP.HCM" android:layout_marginTop="4dp"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:backgroundTint="#000000"
        android:text="THANH TOÁN"
        android:textColor="#FFFFFF" />

</LinearLayout>

Bước 6: MainActivity với View Binding (src/MainActivity.java)

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import com.google.android.material.bottomsheet.BottomSheetBehavior;

// Import class binding tự động sinh ra (tên = tên file layout + Binding)
import com.example.myapp.databinding.ActivityMainBinding;
import com.example.myapp.databinding.BottomSheetBinding;
import com.example.myapp.databinding.ContentBinding;

public class MainActivity extends AppCompatActivity {
    // View Binding instances
    private ActivityMainBinding mainBinding;
    private BottomSheetBinding sheetBinding;
    private ContentBinding contentBinding;

    private BottomSheetBehavior<LinearLayout> sheetBehavior;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Inflate main layout qua View Binding
        mainBinding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(mainBinding.getRoot());

        // Inflate content layout (được include trong main)
        contentBinding = ContentBinding.bind(mainBinding.content.getRoot());

        // Inflate bottom sheet layout
        sheetBinding = BottomSheetBinding.bind(mainBinding.bottomSheet.getRoot());

        // Toolbar
        Toolbar toolbar = mainBinding.toolbar;
        setSupportActionBar(toolbar);

        // Lấy BottomSheetBehavior từ root view của bottom sheet
        LinearLayout bottomSheetView = sheetBinding.bottomSheet;
        sheetBehavior = BottomSheetBehavior.from(bottomSheetView);

        // Callback theo dõi trạng thái
        sheetBehavior.addBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
            @Override
            public void onStateChanged(@NonNull View bottomSheet, int newState) {
                switch (newState) {
                    case BottomSheetBehavior.STATE_EXPANDED:
                        contentBinding.btnBottomSheet.setText("Đóng Sheet");
                        break;
                    case BottomSheetBehavior.STATE_COLLAPSED:
                        contentBinding.btnBottomSheet.setText("Mở rộng Sheet");
                        break;
                    case BottomSheetBehavior.STATE_HIDDEN:
                        // Sheet bị ẩn hoàn toàn (khi behavior_hideable=true và user swipe down)
                        break;
                    case BottomSheetBehavior.STATE_DRAGGING:
                    case BottomSheetBehavior.STATE_SETTLING:
                        // Đang kéo / đang落ち着く
                        break;
                }
            }

            @Override
            public void onSlide(@NonNull View bottomSheet, float slideOffset) {
                // slideOffset: 0.0 (collapsed) -> 1.0 (expanded)
                // Có thể dùng để animate các view bên trong
            }
        });

        // Xử lý click nút trên content
        contentBinding.btnBottomSheet.setOnClickListener(v -> toggleBottomSheet());
    }

    private void toggleBottomSheet() {
        if (sheetBehavior.getState() != BottomSheetBehavior.STATE_EXPANDED) {
            sheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
            contentBinding.btnBottomSheet.setText("Đóng Sheet");
        } else {
            sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
            contentBinding.btnBottomSheet.setText("Mở rộng Sheet");
        }
    }
}

Bước 7: Cấu hình AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="com.example.myapp">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Chạy ứng dụng

Kết nối thiết bị, nhấn Run. Nhấn nút "Hiện Bottom Sheet" - bảng sẽ trượt lên từ dưới. Kéo thả để mở rộng/thu gọn.

Cách triển khai Bottom Sheet trong Android Cách triển khai Bottom Sheet trong Android

Các trạng thái BottomSheetBehavior

Trạng thái Mô tả
STATE_COLLAPSED Thu gọn, chỉ hiển thị peekHeight (mặc định 0)
STATE_EXPANDED Mở rộng đầy đủ nội dung
STATE_DRAGGING Người dùng đang kéo
STATE_SETTLING Đang animation về trạng thái ổn định
STATE_HIDDEN Ẩn hoàn toàn (cần behavior_hideable="true")
STATE_HALF_EXPANDED Nửa mở (API 30+, Material 1.5+)

Thuộc tính XML quan trọng

  • app:layout_behavior: Bắt buộc = com.google.android.material.bottomsheet.BottomSheetBehavior
  • app:behavior_peekHeight: Chiều cao khi collapsed (dp)
  • app:behavior_hideable: true cho phép swipe down để ẩn hẳn
  • app:behavior_skipCollapsed: true bỏ qua trạng thái collapsed khi expand
  • app:behavior_fitToContents: true (mặc định) - height theo nội dung; false - full màn hình

BottomSheetDialogFragment (Dialog modal từ dưới lên)

Cho trường hợp cần dialog modal (chặn tương tác nền):

public class MyBottomSheetDialog extends BottomSheetDialogFragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.bottom_sheet_dialog, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        // Xử lý view
    }
}

// Hiển thị:
new MyBottomSheetDialog().show(getSupportFragmentManager(), "TAG");