【Material Design】Android Bottom Sheets 底部面板 範例
Bottom Sheets 是 Android 界面設計中一種常見元素,它是一個可滑動出現在畫面底部的面板。它可以用來顯示額外的內容或作為互動面板,例如在地圖應用中顯示點擊地點的詳細信息,或在音樂播放器中顯示歌單。
Bottom Sheets 可以有兩種不同的類型:模態和非模態。 模態 Bottom Sheets 需要用戶進行互動才能關閉,而非模態 Bottom Sheets 可以被用戶輕鬆關閉。

文章目錄
- 非模態 Bottom Sheets 樣式UI
- 非模態 Bottom Sheets 控制狀態
- 非模態 Bottom Sheets 動態設置
- 非模態 Bottom Sheets 滑動監聽
- 模態 Bottom Sheets Dialog 樣式UI
- 模態 Bottom Sheets Dialog
- Developer Documents Bottom Sheets
1.非模態 Bottom Sheets 樣式UI

drawable/rounded_dialog.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@android:color/black" />
    <corners
        android:topLeftRadius="15dp"
        android:topRightRadius="15dp" />
</shape>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?><!-- Use DrawerLayout as root container for activity -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/change"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Change"
            android:textAllCaps="false"
            android:layout_marginTop="20dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
        <androidx.coordinatorlayout.widget.CoordinatorLayout
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_marginTop="20dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/change">
            <FrameLayout
                android:id="@+id/bottomSheetLayout"
                android:background="@drawable/rounded_dialog"
                android:layout_width="match_parent"
                android:layout_height="300dp"
                app:behavior_peekHeight="100dp"
                app:layout_behavior="@string/bottom_sheet_behavior">
                <androidx.appcompat.widget.AppCompatButton
                    android:id="@+id/hello"
                    android:textAllCaps="false"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:text="Hello" />
            </FrameLayout>
        </androidx.coordinatorlayout.widget.CoordinatorLayout>
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
2.非模態 Bottom Sheets 控制狀態
狀態
STATE_COLLAPSED:坍塌(默認)
STATE_EXPANDED:展開
MainActivity.kt
@SuppressLint("RestrictedApi", "VisibleForTests")
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private lateinit var bottomSheetBehavior: BottomSheetBehavior<View>
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        bottomSheetBehavior = BottomSheetBehavior.from(binding.bottomSheetLayout)
        bottomSheetBehavior.disableShapeAnimations()
        init()
    }
    private fun init() {
        binding.change.setOnClickListener {
            if (bottomSheetBehavior.state != BottomSheetBehavior.STATE_EXPANDED) {
                bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
            } else {
                bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
            }
        }
        binding.hello.setOnClickListener {
            Toast.makeText(this, "Hello", Toast.LENGTH_SHORT).show()
        }
    }
}
3.非模態 Bottom Sheets 動態設置
Extensions.kt
val Float.dp get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics)
MainActivity.kt
@SuppressLint("RestrictedApi", "VisibleForTests")
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private lateinit var bottomSheetBehavior: BottomSheetBehavior<View>
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        bottomSheetBehavior = BottomSheetBehavior.from(binding.bottomSheetLayout)
        bottomSheetBehavior.disableShapeAnimations()
        val bottomSheetBehavior = BottomSheetBehavior.from(binding.bottomSheetLayout)
        bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
        bottomSheetBehavior.peekHeight = 90f.dp.toInt()
    }
}
4.非模態 Bottom Sheets 滑動監聽
MainActivity.kt
@SuppressLint("RestrictedApi", "VisibleForTests")
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private lateinit var bottomSheetBehavior: BottomSheetBehavior<View>
    private lateinit var bottomSheetCallback: BottomSheetBehavior.BottomSheetCallback
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        bottomSheetBehavior = BottomSheetBehavior.from(binding.bottomSheetLayout)
        bottomSheetBehavior.disableShapeAnimations()
        init()
    }
    private fun init() {
        bottomSheetCallback = object : BottomSheetBehavior.BottomSheetCallback() {
            override fun onStateChanged(bottomSheet: View, newState: Int) {
                Log.e("bottomSheet", "onStateChanged")
            }
            override fun onSlide(bottomSheet: View, slideOffset: Float) {
                Log.e("bottomSheet", "onSlide")
            }
        }
    }
    override fun onStart() {
        super.onStart()
        bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallback)
    }
    override fun onDestroy() {
        bottomSheetBehavior.removeBottomSheetCallback(bottomSheetCallback)
        super.onDestroy()
    }
}
5.模態 Bottom Sheets Dialog 樣式UI

drawable/rounded_dailog.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@android:color/black" />
    <corners
        android:topLeftRadius="15dp"
        android:topRightRadius="15dp" />
</shape>
values/themes.xml
<resources>
    <style name="Theme.JetpackDemo" parent="Theme.MaterialComponents.Light.NoActionBar">
        <item name="bottomSheetDialogTheme">@style/AppBottomSheetDialogTheme</item>
    </style>
    <style name="AppBottomSheetDialogTheme" parent="ThemeOverlay.Material3.BottomSheetDialog">
        <item name="bottomSheetStyle">@style/AppModalStyle</item>
    </style>
    <style name="AppModalStyle" parent="Widget.Design.BottomSheet.Modal">
        <item name="android:background">@drawable/rounded_dialog</item>
    </style>
</resources>
bottom_sheet_dialog.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
    </data>
    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/hello"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="20dp"
            android:text="hello"
            android:textAllCaps="false" />
        <androidx.appcompat.widget.AppCompatTextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="20dp"
            android:text="A" />
        <androidx.appcompat.widget.AppCompatTextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="20dp"
            android:text="B" />
        <androidx.appcompat.widget.AppCompatTextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="20dp"
            android:layout_marginBottom="20dp"
            android:text="C" />
    </androidx.appcompat.widget.LinearLayoutCompat>
</layout>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?><!-- Use DrawerLayout as root container for activity -->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/open"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="open"
            android:textAllCaps="false"
            android:layout_marginTop="20dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
6.模態 Bottom Sheets Dialog
BottomSheetDialog.kt
class BottomSheetDialog : BottomSheetDialogFragment() {
    private var mBinding: BottomSheetDialogBinding? = null
    private val binding get() = requireNotNull(mBinding)
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View {
        if (mBinding == null) {
            mBinding = BottomSheetDialogBinding.inflate(inflater, container, false)
        }
        init()
        return binding.root
    }
    private fun init() {
        binding.hello.setOnClickListener {
            Toast.makeText(context, "Hello", Toast.LENGTH_SHORT).show()
        }
    }
    override fun onDestroy() {
        mBinding = null
        super.onDestroy()
    }
}
MainActivity.kt
@SuppressLint("RestrictedApi", "VisibleForTests")
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        init()
    }
    private fun init() {
        binding.open.setOnClickListener {
            BottomSheetDialog().show(supportFragmentManager, "Dialog")
        }
    }
}
7.Developer Documents Bottom Sheets
Open in Documents Bottom Sheets