NavHost คือคอนเทนเนอร์ composable ที่ทำหน้าที่เป็นจุดเริ่มต้นสำหรับกราฟนำทางใน Jetpack Compose โดยเชื่อมโยง NavController กับชุดของเส้นทางและเรนเดอร์หน้าจอปัจจุบันตามสถานะของแบ็กสแต็ก ตามข้อมูลของ Android Developers (2025) NavHost เป็นส่วนประกอบที่จำเป็นสำหรับแอปพลิเคชัน Compose ใดๆ ที่มีการนำทาง ภายใน NavHost เส้นทาง composable จะถูกลงทะเบียนพร้อมกับอาร์กิวเมนต์เสริม ดีพลิงก์ และแอนิเมชัน แต่ละเส้นทางคือฟังก์ชัน composable ทั่วไปที่รับ NavBackStackEntry พร้อมข้อมูลการเปลี่ยนภาพ NavHost จะจัดการปุ่มย้อนกลับ การบันทึกสถานะ และการกู้คืนเมื่อกำหนดค่าใหม่โดยอัตโนมัติ
ประเด็นสำคัญ
NavHost คือฟังก์ชัน composable ที่ให้คอนเทนเนอร์สำหรับแสดงหน้าจอนำทางปัจจุบัน NavHost รับ NavController, startDestination และกราฟเส้นทางที่สร้างขึ้นผ่าน Kotlin DSL เมื่อเส้นทางปัจจุบันเปลี่ยนไป NavHost จะสลับ composable ที่แสดงด้วยแอนิเมชันที่ระบุ
NavHost ทำหน้าที่เป็น ตัวสลับหน้าจอ: มันติดตาม NavBackStackEntry ปัจจุบันจาก NavController และเรนเดอร์บล็อก composable ที่เกี่ยวข้อง แต่ละหน้าจอคือฟังก์ชัน composable อิสระที่รับ NavBackStackEntry พร้อมอาร์กิวเมนต์เส้นทาง หน้าจอทั้งหมดมีอยู่ในแผนผังการประกอบเดียว แต่ NavHost จะแสดงเพียงครั้งละหนึ่งหน้า โดยซ่อนหน้าอื่นๆ ผ่านแอนิเมชัน
ต่างจาก FragmentManager ตรงที่ NavHost ไม่ได้สร้าง Fragment สำหรับแต่ละหน้าจอ วงจรชีวิตทั้งหมดถูกจัดการผ่าน CompositionLifecycle — ฟังก์ชัน composable ไม่มี onStart/onResume ดังนั้น LaunchedEffect และ DisposableEffect จึงถูกใช้สำหรับเอฟเฟกต์ข้างเคียง NavHost สมัครสมาชิก NavController โดยอัตโนมัติและประกอบ UI ใหม่เมื่อเส้นทางเปลี่ยนแปลง
ตามข้อมูลของ Google NavHost เป็น API ที่เสถียรตั้งแต่ Navigation 2.4.0 ตั้งแต่เวอร์ชัน 2.8.0 NavHost รองรับการนำทางแบบ Type-Safe ผ่าน Kotlin Serialization ซึ่งแทนที่เส้นทางสตริงด้วยคลาสข้อมูล NavHost ยังรองรับกราฟแบบซ้อน ซึ่งช่วยให้จัดระเบียบการนำทางตามโมดูลได้
NavHost ถูกสร้างขึ้นด้วยพารามิเตอร์ที่จำเป็นสองตัว: navController (อินสแตนซ์ของ NavHostController) และ startDestination (สตริงเส้นทางของหน้าจอแรก) พารามิเตอร์ที่สามคือบล็อกตัวสร้างที่ลงทะเบียนเส้นทางทั้งหมดผ่าน composable(), navigation() และ dialog()
@Composable
fun AppNavHost(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") { HomeScreen(navController) }
composable("settings") { SettingsScreen(navController) }
}
}
startDestination คือเส้นทางที่เปิดเมื่อ NavHost เปิดตัวครั้งแรก หากแบ็กสแต็กว่างเปล่า NavHost จะเพิ่ม startDestination ลงในสแตกโดยอัตโนมัติ เมื่อกำหนดค่าใหม่ (หมุนหน้าจอ) NavHost จะกู้คืนเส้นทางล่าสุดจาก savedState ไม่ใช่ startDestination
สำหรับ BottomNavigation startDestination คือหนึ่งในเส้นทางของแผงด้านล่าง เส้นทางแผงที่เหลือจะถูกเพิ่มเป็นรายการ composable แยกต่างหาก NavHost ควรวางไว้ภายใน Scaffold.content — ที่ซึ่งเนื้อหาหลักของแอปพลิเคชันแสดงอยู่ NavHost ใช้พื้นที่ความสูงที่มีอยู่ทั้งหมดลบด้วย TopAppBar และ BottomNavigation
ฟังก์ชัน composable(route, arguments, deepLinks, enterTransition, exitTransition, content) ลงทะเบียนเส้นทางในกราฟ NavHost พารามิเตอร์ route คือสตริงที่อธิบายเส้นทางด้วยตัวยึดตำแหน่งเสริมในรูปแบบ {paramName} ตัวยึดตำแหน่งจะถูกแทนที่ด้วยค่าจริงระหว่างการนำทาง
บล็อก content ของ composable รับ NavBackStackEntry ซึ่งใช้ดึงอาร์กิวเมนต์ ฟังก์ชัน composable ของหน้าจอจะถูกเรนเดอร์เฉพาะเมื่อเส้นทาง NavController ปัจจุบันตรงกับเส้นทางเท่านั้น หากไม่ตรงกัน composable จะถูกลบออกจากการประกอบ แต่สถานะของมันสามารถรักษาไว้ได้ผ่าน rememberSaveable หรือ ViewModel กับ SavedStateHandle
composable(
route = "article/{articleId}",
arguments = listOf(navArgument("articleId") {
type = NavType.IntType
defaultValue = 0
}),
deepLinks = listOf(navDeepLink { uriPattern = "https://app.example/article/{articleId}" })
) { backStackEntry ->
val articleId = backStackEntry.arguments?.getInt("articleId") ?: 0
ArticleScreen(articleId = articleId)
}
จำนวนรายการ composable ภายใน NavHost อาจมีตั้งแต่ไม่กี่รายการไปจนถึงหลายร้อยรายการ สำหรับแอปพลิเคชันขนาดใหญ่ เส้นทางจะถูกแบ่งตามโมดูลและเชื่อมต่อผ่านกราฟแบบซ้อน แต่ละ composable สามารถมีการตั้งค่าแอนิเมชัน ดีพลิงก์ และอาร์กิวเมนต์ของตัวเอง
อาร์กิวเมนต์เส้นทางถูกกำหนดผ่านพารามิเตอร์ arguments: List<NamedNavArgument> ใน composable() แต่ละอาร์กิวเมนต์ถูกกำหนดผ่าน navArgument(name) { type; defaultValue } NavType กำหนดชนิดของอาร์กิวเมนต์: StringType, IntType, LongType, FloatType, BoolType, ParcelableType และ ReferenceType
| พารามิเตอร์เส้นทาง | ตัวอย่างเส้นทาง | NavType |
|---|---|---|
| เส้นทาง (path) | "user/{id}" | NavType.IntType |
| คิวรี (query) | "search?q={query}" | NavType.StringType |
| ตัวเลือก | "details/{id}?tab={tab}" | StringType + defaultValue="" |
| Parcelable | "checkout/{order}" | NavType.ParcelableType |
อาร์กิวเมนต์ถูกดึงจาก NavBackStackEntry ผ่าน arguments?.getInt("id") สำหรับอาร์กิวเมนต์บังคับ สามารถละ defaultValue ได้ — NavType จะใช้ null สำหรับอาร์กิวเมนต์ตัวเลือก ต้องตั้งค่า defaultValue มิฉะนั้นการนำทางจะโยนข้อยกเว้นหากพารามิเตอร์หายไป
ตั้งแต่ Navigation 2.8.0 แนะนำให้ใช้การนำทางแบบ Type-Safe: กำหนด sealed class หรือ data class สำหรับเส้นทางด้วย Kotlin Serialization แทนที่เส้นทางสตริง ให้ใช้ composable<RouteType> { backStackEntry -> } ซึ่งช่วยขจัดข้อผิดพลาดในการพิมพ์ในเส้นทางและสร้าง NavType สำหรับอาร์กิวเมนต์โดยอัตโนมัติ หากต้องการย้าย ให้เพิ่มการพึ่งพา navigation-compose-typesafe และปลั๊กอิน Kotlin Serialization
nested graphs — กลไกสำหรับจัดกลุ่มเส้นทางภายใน NavHost โดยใช้ฟังก์ชัน navigation(route, startDestination) กราฟแบบซ้อนมีคำนำหน้าเส้นทางและ startDestination ของตัวเอง และเส้นทางทั้งหมดสามารถเข้าถึงได้ผ่านคำนำหน้า กราฟแบบซ้อนใช้สำหรับสถาปัตยกรรมแบบโมดูล ซึ่งแต่ละโมดูลคุณสมบัติจะลงทะเบียนกราฟย่อยของตัวเอง
ประโยชน์ของกราฟแบบซ้อน: การแยกเส้นทางภายในโมดูล แบ็กสแต็กแบบรวมสำหรับกลุ่มหน้าจอ และความสามารถในการนำทางตามคำนำหน้าโดยไม่เปิดเผยโครงสร้างภายใน ตัวอย่างเช่น กราฟ "auth" ประกอบด้วย "auth/login" และ "auth/register" การนำทางสามารถทำได้ทั้งผ่านเส้นทางแบบเต็มหรือผ่านคำนำหน้าด้วยการเปลี่ยนเส้นทางไปยัง startDestination
NavHost(navController = navController, startDestination = "main") {
composable("main") { MainScreen(navController) }
navigation(
route = "auth",
startDestination = "auth/login"
) {
composable("auth/login") { LoginScreen(navController) }
composable("auth/register") { RegisterScreen(navController) }
}
}
กราฟแบบซ้อนรองรับการส่งอาร์กิวเมนต์ในระดับกราฟ: พารามิเตอร์ที่ประกาศในเส้นทางของกราฟจะถูกส่งไปยังเส้นทางภายในทั้งหมด หากต้องการล้างกราฟแบบซ้อน ให้ใช้ popBackStack(route) — มันจะลบรายการภายในทั้งหมด กราฟแบบซ้อนไม่มีขีดจำกัดความลึก แต่แนะนำไม่เกิน 3 ระดับเพื่อความอ่านง่าย
NavHost รองรับแอนิเมชันการเปลี่ยนภาพระหว่างเส้นทาง composable ผ่านพารามิเตอร์ enterTransition, exitTransition, popEnterTransition และ popExitTransition แอนิเมชันถูกตั้งค่าครั้งเดียวสำหรับ NavHost และนำไปใช้กับทุกเส้นทาง หรือเป็นรายบุคคลสำหรับแต่ละ composable โดยค่าเริ่มต้น แอนิเมชันจะถูกปิดใช้งาน
การกำหนดค่าทั่วไป: enterTransition = slideInHorizontally(initialOffsetX = { it }) — หน้าจอเลื่อนเข้ามาจากด้านขวา; exitTransition = slideOutHorizontally(targetOffsetX = { -it }) — หน้าจอเลื่อนออกไปทางด้านซ้าย สำหรับแอนิเมชัน pop ทิศทางจะกลับด้าน: หน้าจอเลื่อนเข้ามาจากด้านซ้ายและเลื่อนออกไปทางด้านขวา สำหรับ BottomNavigation ใช้ fadeIn/fadeOut โดยไม่มีการเลื่อน
NavHost(
navController = navController,
startDestination = "home",
enterTransition = { slideInHorizontally(initialOffsetX = { it }) + fadeIn() },
exitTransition = { slideOutHorizontally(targetOffsetX = { -it }) + fadeOut() },
popEnterTransition = { slideInHorizontally(initialOffsetX = { -it }) + fadeIn() },
popExitTransition = { slideOutHorizontally(targetOffsetX = { it }) + fadeOut() }
) { /* composable routes */ }
แอนิเมชันแบบกำหนดเองถูกสร้างขึ้นโดยใช้ Compose Animation API: AnimatedContentTransitionScope ให้การเข้าถึงมิติของคอนเทนเนอร์ ความคืบหน้าของแอนิเมชัน และทิศทาง สำหรับการเปลี่ยนภาพองค์ประกอบที่ใช้ร่วมกัน (องค์ประกอบหนึ่งย้ายไปยังอีกหน้าจอหนึ่งอย่างราบรื่น) จำเป็นต้องใช้ไลบรารี Accompanist Navigation Animation หรือการนำไปใช้แบบกำหนดเองผ่าน sharedElement Modifier ตามข้อมูลของ Android Developers (2025) แอนิเมชันเลื่อนเริ่มต้น (เข้าจากด้านขวา ออกทางด้านซ้าย) ถูกใช้ใน 80% ของแอปพลิเคชัน Android ที่มีการนำทาง
คำถามที่พบบ่อย
ในทางเทคนิคได้ แต่ไม่แนะนำ แต่ละ NavHost สร้างแบ็กสแต็กอิสระ ซึ่งทำลายการนำทางแบบรวม ข้อยกเว้นคือพื้นที่แยกกัน เช่น NavHost สำหรับเนื้อหาหลักและ NavHost สำหรับ BottomSheet ที่มีการนำทางของตัวเอง
NavHost คือคอนเทนเนอร์นำทางที่สลับหน้าจอ Scaffold คือเค้าโครงของทั้งหน้า (TopAppBar, BottomNavigation, FloatingActionButton) โดยทั่วไป NavHost จะถูกวางไว้ภายใน Scaffold.content Scaffold ไม่ได้จัดการการนำทาง มันเพียงให้ช่องสำหรับส่วนประกอบ UI
ViewModel ถูกสร้างขึ้นภายใน NavBackStackEntry ผ่าน viewModel() หากต้องการแชร์ ViewModel ระหว่างหน้าจอ ให้ใช้ parentNavController: ผูก ViewModel ที่ใช้ร่วมกันกับรายการหลัก ทางเลือกอื่นคือ DI (Hilt/Koin) ที่มีขอบเขต NavGraph
นี่คือพฤติกรรมปกติ — NavHost จะลบ composable ออกจากการประกอบเมื่อออกจากเส้นทาง หากต้องการรักษาสถานะ ให้ใช้ rememberSaveable สำหรับสถานะ UI และ ViewModel กับ SavedStateHandle สำหรับตรรกะทางธุรกิจ
เพิ่มเส้นทางสุดท้าย composable("404") และนำทางไปยังเส้นทางนั้นเมื่อได้รับดีพลิงก์ที่ไม่รู้จัก NavHost ไม่มีเส้นทาง catch-all — ตรวจสอบเส้นทางในตัวจัดการอินเทนต์ดีพลิงก์ก่อน navigate() หากไม่พบเส้นทาง ให้นำทางไปที่ 404
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม