BottomNavigation: โครงสร้างและการตั้งค่าใน Jetpack Compose

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-06-29 เวลาอ่าน: 8 นาที

BottomNavigation (NavigationBar ใน Material3) เป็นคอมโพเนนต์ Material Design ที่ให้การนำทางระหว่างหน้าจอหลักสามถึงห้าหน้าจอของแอปพลิเคชัน ใน Jetpack Compose แถบด้านล่างถูกใช้งานผ่าน NavigationBar หรือ BottomNavigation โดยแต่ละรายการจะแสดงด้วยไอคอนและป้ายข้อความ ตาม Google Material Design (2025) BottomNavigation ได้รับการแนะนำสำหรับ iOS และ Android เป็นรูปแบบการนำทางหลัก แผงจะปรับความสูงของระบบนำทาง (Gesture Navigation) โดยอัตโนมัติผ่าน WindowInsets NavigationBar ใน Material3 รองรับสีไดนามิก ป้าย และระยะขอบที่ปรับเปลี่ยนได้

ประเด็นสำคัญ

  • BottomNavigation — แถบนำทางด้านล่างสำหรับ 3–5 หน้าจอ ใช้งานผ่าน NavigationBar ใน Material3
  • NavigationBarItem — รายการแผงที่ประกอบด้วยไอคอน ป้าย และป้ายแจ้งเตือน
  • การเชื่อมต่อกับ NavHost — การเลือกรายการจะเรียก navigate() ใน NavController เพื่อเปลี่ยนกราฟ
  • Material3 เพิ่มสีไดนามิกและการจัดการระบบนำทางอัตโนมัติผ่าน WindowInsets
  • Badge — ป้ายแจ้งเตือนบนไอคอน ใช้งานผ่าน BadgedBox พร้อมจำนวน

BottomNavigation ใน Jetpack Compose คืออะไร?

BottomNavigation เป็นคอมโพเนนต์ Material Design สำหรับแถบนำทางด้านล่างที่แสดงรายการเมนูสามถึงห้ารายการ ใน Jetpack Compose จะใช้งานผ่านฟังก์ชัน Composable BottomNavigation (Material2) และ NavigationBar (Material3) แถบจะวางอยู่ที่ด้านล่างของหน้าจอและให้การเข้าถึงส่วนหลักของแอปพลิเคชันได้อย่างรวดเร็ว

วัตถุประสงค์หลักของ BottomNavigation คือการจัดระเบียบการนำทางหลักระหว่างปลายทางระดับหน้าจอ แต่ละรายการในแผงแทนหน้าจอแยกต่างหาก (หน้าแรก, ค้นหา, โปรไฟล์) การแตะจะเปลี่ยนเนื้อหาในพื้นที่หลักของหน้าจอ BottomNavigation ไม่ได้ออกแบบมาสำหรับการนำทางแบบซ้อน — สำหรับสิ่งนั้นจะใช้แท็บที่ด้านบน

ตามแนวทางการออกแบบ Material Design (2025) แถบด้านล่างควรมีเฉพาะส่วนที่เข้าถึงได้จากทุกจุดของแอปพลิเคชัน การซ่อนแถบบนหน้าจอย่อย (เช่น เมื่อเปิดหน้ารายละเอียด) เป็นแนวปฏิบัติมาตรฐาน ใน Compose การมองเห็นของ BottomNavigation ถูกควบคุมผ่านสถานะ Scaffold หรือการเรนเดอร์แบบมีเงื่อนไขตามเส้นทางปัจจุบันของ NavController

ใน Material3 แนะนำให้ใช้ NavigationBar แทน BottomNavigation แบบเก่า NavigationBar ประกอบด้วยสีไดนามิก การรองรับ WindowInsets และแอนิเมชันการกดที่ดีขึ้น ตาม Google NavigationBar เป็นคอมโพเนนต์ที่มองไปข้างหน้า ในขณะที่ BottomNavigation จาก M2 ยังคงอยู่เพื่อความเข้ากันได้ย้อนหลังเท่านั้น

แต่ละรายการของ NavigationBar ถูกใช้งานผ่าน Composable NavigationBarItem พารามิเตอร์: icon (ไอคอน), label (ป้ายข้อความ), selected (สถานะการเลือก) และ onClick (การเรียกกลับ) NavigationBarItem ใช้สีที่เลือก (primary) โดยอัตโนมัติและสร้างแอนิเมชันการเปลี่ยนขนาดไอคอนเมื่อเปิดใช้งาน

kotlin
@Composable
fun BottomNavBar() {
    val items = listOf(
        Screen.Home,
        Screen.Search,
        Screen.Profile
    )
    var selectedItem by remember { mutableIntStateOf(0) }
    NavigationBar {
        items.forEachIndexed { index, item ->
            NavigationBarItem(
                icon = { Icon(item.icon, contentDescription = item.label) },
                label = { Text(item.label) },
                selected = selectedItem == index,
                onClick = { selectedItem = index }
            )
        }
    }
}

ไอคอนสำหรับ NavigationBarItem ควรเป็น แบบเติม (Filled) สำหรับสถานะที่เลือกและ แบบโครงร่าง (Outlined) สำหรับสถานะที่ไม่ได้เลือก Material Icons มีทั้งสองชุด: Icons.Filled.Home และ Icons.Outlined.Home โดยค่าเริ่มต้น NavigationBarItem จะแสดงแบบเติมสำหรับสถานะที่เลือก หากต้องการแสดงผลต่างกัน ให้ส่งไอคอนแบบมีเงื่อนไข: if (selected) Filled.X else Outlined.X

ความยาวของป้ายข้อความไม่ควรเกิน 12 ตัวอักษร (ละติน) หรือ 8 ตัวอักษร (ซีริลลิก) ป้ายยาวจะถูกตัดด้วยจุดไข่ปลา สำหรับป้าย ให้ใช้ maxLines = 1 และ ellipsis = TextTruncation.Ellipsis หากต้องแสดงป้ายเสมอ ให้ลบพารามิเตอร์ alwaysShowLabel = true (ค่าเริ่มต้นคือ true)

พารามิเตอร์ชนิดคำอธิบาย
icon@Composable () -> Unitไอคอนของรายการแผง
label@Composable (() -> Unit)?ป้ายข้อความใต้ไอคอน
selectedBooleanแฟลกรายการที่เลือก
onClick() -> Unitการเรียกกลับเมื่อคลิก
badge@Composable (() -> Unit)?ป้ายแจ้งเตือนพร้อมจำนวน

การเชื่อมต่อ BottomNavigation กับ NavHost

การอัปเดต selectedItem โดยตรงไม่ได้เรียกการนำทาง — การเปลี่ยนหน้าจอจำเป็นต้องรวมเข้ากับ NavController รูปแบบทั่วไป: onClick ใน NavigationBarItem เรียก navController.navigate(route) และ selectedItem ถูกกำหนดจากเส้นทางปัจจุบันของ NavController

NavController เก็บสแต็กย้อนกลับปัจจุบัน เพื่อตรวจสอบว่ารายการแผงใดถูกเลือก ให้เปรียบเทียบเส้นทางปัจจุบัน (navController.currentBackStackEntryAsState().value?.destination?.route) กับเส้นทางของรายการ หากเส้นทางตรงกัน รายการนั้นจะถือว่าถูกเลือก ซึ่งจะกำจัดการซ้ำซ้อนของสถานะ

kotlin
@Composable
fun MainScreen(navController: NavController) {
    val navBackStackEntry by navController.currentBackStackEntryAsState()
    val currentRoute = navBackStackEntry?.destination?.route
    Scaffold(
        bottomBar = {
            NavigationBar {
                items.forEach { item ->
                    NavigationBarItem(
                        icon = { Icon(item.icon, contentDescription = item.label) },
                        label = { Text(item.label) },
                        selected = currentRoute == item.route,
                        onClick = {
                            navController.navigate(item.route) {
                                popUpTo(navController.graph.findStartDestination().id) {
                                    saveState = true
                                }
                                launchSingleTop = true
                                restoreState = true
                            }
                        }
                    )
                }
            }
        }
    ) { /* content */ }
}

การตั้งค่าหลักใน navigate(): popUpTo(startDestination) ป้องกันการสะสมของสแต็กย้อนกลับเมื่อเปลี่ยนแท็บ launchSingleTop ป้องกันการซ้ำซ้อนของเส้นทางในสแต็ก และ restoreState กู้คืนสถานะของแท็บที่เยี่ยมชมก่อนหน้านี้ หากไม่มีพารามิเตอร์เหล่านี้ แต่ละคลิกจะสร้างรายการใหม่ในสแต็ก ซึ่งนำไปสู่พฤติกรรมที่ไม่ถูกต้องของปุ่มย้อนกลับ

NavigationBar ใน Material3: สีไดนามิกและการปรับตัว

Material3 (M3) มี NavigationBar และ NavigationBarItem แทน BottomNavigation จาก Material2 ข้อดีหลักของ M3: สีไดนามิก ความสูงที่ปรับเปลี่ยนได้ แอนิเมชันริปเปิลเต็มความกว้าง และการรองรับ WindowInsets สำหรับระบบนำทาง

สีไดนามิกเลือกสี NavigationBar โดยอัตโนมัติตามวอลเปเปอร์ของอุปกรณ์ (Android 12+) รายการที่ใช้งานจะถูกระบายสีด้วย primary รายการที่ไม่ใช้งานด้วย onSurfaceVariant โดยค่าเริ่มต้น NavigationBar จะยกขึ้นเหนือระบบนำทางผ่าน Modifier.navigationBarsPadding() สำหรับการควบคุมด้วยตนเอง ให้ใช้พารามิเตอร์ windowInsets

NavigationBar ใน M3 จะสร้างแอนิเมชันความกว้างของรายการที่ใช้งานโดยอัตโนมัติ — ไอคอนเลื่อนไปทางซ้ายและป้ายขยายออก แอนิเมชันใช้เวลา 300 ms และใช้ easing มาตรฐานของ Compose หากไม่ต้องการพฤติกรรมนี้ ให้แสดงป้ายเสมอผ่าน alwaysShowLabel = true ใน NavigationBarItem

การปรับแต่งสี NavigationBar

เพื่อแทนที่สี ให้ส่ง NavigationBarColors ในพารามิเตอร์ colors โดยค่าเริ่มต้นจะใช้ NavigationBarDefaults.colors() แต่สามารถตั้งค่าที่กำหนดเองได้: containerColor (พื้นหลังแถบ), contentColor (สีไอคอน), indicatorColor (พื้นหลังรายการที่ใช้งาน) แตกต่างจาก M2 ที่ตั้งสีผ่าน BottomNavigationDefaults M3 ใช้อินเทอร์เฟซ NavColors แบบรวม

หากโปรเจกต์ใช้ BottomNavigation จาก M2 (androidx.compose.material) Google แนะนำให้ย้ายไปใช้ NavigationBar จาก M3 การย้ายรวมถึงการเปลี่ยน imports การอัปเดตพารามิเตอร์ และการเพิ่ม @OptIn(ExperimentalMaterial3Api::class) BottomNavigation จาก M2 ถือว่าล้าสมัยและไม่ได้รับคุณสมบัติใหม่

สถานะของ NavigationBar เก็บดัชนีของรายการที่เลือกอยู่ในปัจจุบัน ในกรณีง่าย ๆ จะใช้ remember { mutableIntStateOf(0) } อย่างไรก็ตาม สำหรับแอปพลิเคชันในระดับโปรดักชัน ดัชนีควรซิงโครไนซ์กับกราฟนำทาง ไม่ใช่สถานะ UI ในเครื่อง

วิธีการหลักในการจัดการสถานะ: แบบ NavController (แนะนำ) และ แบบ ViewModel ในกรณีแรก selectedItem คำนวณจากเส้นทางปัจจุบันของ NavController ในกรณีที่สอง ViewModel เก็บหน้าจอปัจจุบัน และ NavigationBarItem อ่าน StateFlow จาก ViewModel แบบ NavController ง่ายกว่า แบบ ViewModel ให้การควบคุมมากกว่าสำหรับตรรกะที่ซับซ้อน (การยืนยันตัวตน การทดสอบ A/B)

เมื่อใช้ saveState และ restoreState NavController จะบันทึกและกู้คืนสถานะของ LazyColumn และคอมโพเนนต์อื่น ๆ ภายในแต่ละแท็บโดยอัตโนมัติ ซึ่งช่วยลดความจำเป็นในการใช้ ViewModel เพื่อเก็บตำแหน่งการเลื่อน อย่างไรก็ตาม การบันทึกใช้ไม่ได้กับช่องป้อนข้อมูล — จำเป็นต้องใช้ SavedStateHandle หรือ ViewModel

ป้ายแจ้งเตือนและตัวนับบนรายการ

Badge เป็นตัวบ่งชี้การแจ้งเตือนขนาดกะทัดรัดที่แสดงบนไอคอน NavigationBarItem ใน Material3 ป้ายแจ้งเตือนถูกใช้งานผ่าน BadgedBox ซึ่งห่อหุ้มไอคอนและวางป้ายไว้ที่มุมบนขวา ป้ายสามารถประกอบด้วยตัวเลข (จำนวนการแจ้งเตือน) หรือจุด (ตัวบ่งชี้การมีอยู่แบบง่าย)

kotlin
@Composable
fun HomeItem(unreadCount: Int) {
    NavigationBarItem(
        icon = {
            BadgedBox(badge = {
                if (unreadCount > 0) {
                    Badge { Text(unreadCount.toString()) }
                }
            }) {
                Icon(Icons.Filled.Home, contentDescription = "หน้าแรก")
            }
        },
        label = { Text("Home") },
        selected = false,
        onClick = { }
    )
}

BadgedBox จะวาง Badge โดยอัตโนมัติที่มุมบนขวาขององค์ประกอบย่อย หากจำนวนเกิน 99 Badge จะแสดง "99+" เพื่อซ่อนป้ายเมื่อค่าเป็นศูนย์ ให้ใช้ if (count > 0) Badge จาก Material3 มีค่าเริ่มต้นเป็นสี error (แดง) พร้อมข้อความสีขาว ปรับแต่งผ่าน BadgeDefaults.colors(containerColor, contentColor)

ใน Material2 ไม่มีป้ายแจ้งเตือน — ต้องวาดด้วยตนเองผ่าน Canvas หรือใช้ไลบรารีของบุคคลที่สาม Material3 แก้ปัญหานี้ด้วยคอมโพเนนต์พื้นเมือง ตาม Android Developers (2025) BadgedBox ถูกใช้ใน 40% ของแอปที่มี NavigationBar โดย 60% ใช้แสดงข้อความที่ยังไม่ได้อ่าน

คำถามที่พบบ่อย

BottomNavigation ควรมีกี่รายการ?

Material Design แนะนำ 3 ถึง 5 รายการ น้อยกว่าสามไม่เหมาะสม (ให้ใช้แท็บแทน) มากกว่าห้าทำให้ความสามารถในการอ่านป้ายและขนาดเป้าหมายสัมผัสลดลง หากต้องการมากกว่า 5 ส่วน ให้ใช้ Navigation Rail (แท็บเล็ต) หรือ Drawer

จะซ่อน BottomNavigation บนหน้าจอย่อยได้อย่างไร?

กำหนดรายการเส้นทางที่แถบมองเห็นได้ (โดยปกติคือเส้นทางราก) ใน Scaffold bottomBar ให้ส่ง NavigationBar เฉพาะเมื่อ currentRoute อยู่ใน bottomNavRoutes อีกทางเลือกหนึ่งคือ NavHost ที่มีกราฟแยกต่างหากสำหรับหน้าจอแบบซ้อนโดยไม่มี bottomBar

ความแตกต่างระหว่าง NavigationBar (M3) และ BottomNavigation (M2) คืออะไร?

NavigationBar จาก Material3 รองรับสีไดนามิก BadgedBox WindowInsets และความกว้างรายการที่ใช้งานแบบมีแอนิเมชัน BottomNavigation จาก Material2 เป็น API ดั้งเดิมที่ไม่มีคุณสมบัติเหล่านี้ Google แนะนำให้ใช้ NavigationBar สำหรับโปรเจกต์ใหม่และย้ายโปรเจกต์ที่มีอยู่

ทำไมหน้าจอถึงถูกสร้างใหม่เมื่อคลิกรายการ BottomNavigation?

ปัญหาคือการขาด saveState และ restoreState ใน navigate() หากไม่มี NavController จะไม่บันทึกสถานะของแท็บก่อนหน้า เพิ่ม popUpTo(startDestination) { saveState = true } และ restoreState = true ใน NavOptions เพื่อรักษา LazyColumn ตำแหน่งการเลื่อน และช่องป้อนข้อมูล

จะปรับแต่งแอนิเมชันการสลับระหว่างแท็บได้อย่างไร?

แอนิเมชันการสลับถูกควบคุมที่ระดับ NavHost ไม่ใช่ BottomNavigation เพิ่ม composable(route, enterTransition, exitTransition) พร้อมแอนิเมชันที่กำหนดเอง สำหรับ BottomNavigation แอนิเมชันแบบค่อย ๆ จาง (fadeIn + fadeOut) ด้วยระยะเวลา 300 มิลลิวินาทีเป็นเรื่องปกติ

สรุป

  • BottomNavigation (NavigationBar ใน M3) — แถบด้านล่างสำหรับสลับระหว่าง 3–5 หน้าจอหลักของแอปพลิเคชัน
  • NavigationBarItem — รายการแผงพร้อมไอคอน ป้าย ป้ายแจ้งเตือน และสถานะการเลือก
  • การรวมกับ NavController ต้องการ popUpTo launchSingleTop และ restoreState สำหรับการจัดการสแต็กที่ถูกต้อง
  • NavigationBar ของ Material3 รองรับสีไดนามิก BadgedBox WindowInsets และแอนิเมชันรายการที่ใช้งาน
  • BadgedBox — คอมโพเนนต์สำหรับป้ายแจ้งเตือนพร้อมตัวเลขหรือจุดบนไอคอนรายการ
  • สถานะการเลือก คำนวณจาก currentBackStackEntryAsState เพื่อซิงโครไนซ์กับ NavController
  • เพื่อซ่อนแถบบนหน้าจอย่อย ให้ใช้การเรนเดอร์แบบมีเงื่อนไขใน Scaffold bottomBar ตามรายการเส้นทาง

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม