อยู่ดี ๆ ทำไมแอปแสดงผลเต็มจอ? แถม UI โดนทับอีกต่างหาก
มาดูวิธีแก้ใน Android 15 กัน
เพราะมีสิ่งที่เรียกว่า Edge-to-Edge นั่นเอง
แล้วมันคืออะไร? มาได้ยังไง? แล้วจะแก้ยังไงดี? มาอ่านกัน
โค้ดยาวนิดนึง กด save กันก่อนได้ มีบล็อกใต้เม้น
.
🟢 รู้จัก window insets กันก่อน?
การที่มี Edge-to-Edge มันจะแสดงหน้าแอพของเรา มันครอบคลุมพื้นที่ไปยัง window insets อย่าง status bar และ navigation bar อีกด้วย ทำให้มีปัญหาในการกดปุ่มบางอย่างที่อยู่แ ถวนั้น
ซึ่ง status bar จะอยู่ข้างบน แสดงสถานะต่าง ๆ เช่น แบตเตอรี่ การเชื่อมต่อเครือข่าย เวลา และ notification ต่าง ๆ
ส่วน navigation bar จะอยู่ด้านล่าง บางคนใช้แบบ 3- button บางคนใช้แบบ gesture ดังนั้นขนาดจะไม่เท่ากันตามแต่แบบ โดยแบบ 3-button จะมีพื้นที่ด้านล่างมากกว่าเพื่อให้กดปุ่มได้
-
🟢 แล้ว Edge-to-Edge มันมาได้อย่างไร?
ถ้าเครื่องที่เป็น Android 15 ขึ้นไป และแอพที่ตั้งค่า targetSdkVersion = 35 ขึ้น จะได้สิ่งนี้อัตโนมัติ
แต่ถ้าอยากเปิด edge-to-edge สามารถใส่
`WindowCompat.enableEdgeToEdge(window)` ได้ที่ `onCreate()` ของ Activity นั้น ๆ นะ หรือถ้าเป็น Jetpack Compose ที่เป็นโปรเจกต์ใหม่ ๆ จะใส่ `enableEdgeToEdge()` ที่ `onCreate()` ให้เหมือนกันนะ
แต่ส่วนใหญ่อาจจะไม่อยากเปิดก็ได้ เพราะมันทับ status bar ด้านบน กับ navigation bar ด้านล่าง ล่ะสิ ซึ่งมันแสดงเต็มจอจริง แต่ UI ซ้อนกันไม่สวยเลย
-
🟢 ถ้าอยากแสดงเหมือนเดิม ทำ ยังไงดี?
เพราะบางที toolbar ก็โดน status bar ทับไปแล้ว บางทีก็โดน navigation bar ทับหน้าจอแอพอ่ะ สำหรับคนที่ใช้แบบ button แล้วถ้าไปมีปุ่มหรืออะไรสักอย่างอยู่ข้างล่าง ทีงี้กดลำบากเลย
❌ วิธีที่เราเห็นบ่อย แต่เขาไม่แนะนำให้ทำ คือ ใส่ `android:fitsSystemWindows="true"` ที่ root layout ใน xml เนื่องจากมัน legacy ไปแล้ว และอาจทำให้การจัดการ UI ที่ซับซ้อนทำได้ยากขึ้นในระยะยาว
✅ ส่วนวิธีที่ทาง Google เขาแนะนำ คือ จัดการเรื่อง overlap ด้วย window insets เพื่อคำนวณพื้นที่ว่างที่เหมาะสมให้ Layout ของเรา
โดยเพิ่มพื้นที่ status bar และ navigation bar ดังนี้
```kotlin
ViewCompat.setOnApplyWindowInsetsListener(binding.rootview) { view, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
view.setPadding(0, insets.top, 0, insets.bottom)
WindowInsetsCompat.CONSUMED
}
// Handle AppBarLayout insets to add spacing between status bar and toolbar
ViewCompat.setOnApplyWindowInsetsListener(binding.appBarLayout) { view, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars())
view.setPadding(0, insets.top, 0, 0)
WindowInsetsCompat.CONSUMED
}
// Handle navigation bar insets at LinearLayout level to prevent content from going under navigation bar
ViewCompat.setOnApplyWindowInsetsListener(binding.contentLinearLayout) { view, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars())
view.setPadding(0, 0, 0, insets.bottom)
WindowInsetsCompat.CONSUMED
}
```
- ดึงค่า Insets ที่เราต้องการ เช่น ถ้าเอาทั้ง Status Bar ด้านบน และ Navigation Bar ด้านล่าง ใส่ Type.systemBars() เอาแค่ Status Bar ก็ใส่ Type.systemBars() ส่วน Navigation Bar จะเป็น Type.navigationBars()
- กำหนด padding ให้กับ view ที่เราต้องการ ไม่โดนทับ ในที่นี้ คือ rootview
- คืนค่า CONSUMED เพื่อบอกว่าเราจัดการ Insets นี้แล้ว
-
🟢 แล้วสี Status Bar จัดการยังไง?
อีกเรื่องนึงที่ต้องจัดการ คือ Status Bar ด้านบน แก้แล้วขาวโพลนเลย มองไม่เห็นไอคอนของระบบเลย แล้วจะแก้ยังไงดี ให้รองรับแอพที่มี dark mode ด้วย
```kotlin
val isLightMode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_NO
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars = isLightMode
```
- check ก่อนว่าแอพตอนนี้เป็น light model หรือ dark mode
- เรียกใช้งาน controller และให้สลับสีไอคอนให้ตรงข้ามกับพื้นหลัง เช่น ถ้าเป็น light mode มันจะเป็นพื้นสีขาว ให้แสดงไอคอนเป็นสีดำ
แต่ถ้ามีวิธีอื่นที่ดีกว่าเพื่อน ๆ สามารถนำเสนอได้นะ หรืออาจจะกำหนดสีตามหน้า UI นั้น ๆ ก็ได้แหละเนอะ
.
🟢 แล้วถ้าใน Jetpack Compose ล่ะ
สามารถเพิ่ม `safeDrawingPadding()` ไปที่ Modifier ของ Row, Column หรือ Box ที่เราต้องการได้เลย เพื่อให้ content ไม่ทับ system bars
แต่ถ้าเอาแค่ Status Bar ใช้ `statusBarsPadding()` หรือ เอาแค่ Navigation Bar ใช้ `navigationBarsPadding()`
```kotlin
Column(
modifier = Modifier.safeDrawingPadding()
) {
// content
}
Column(
modifier = Modifier
.statusBarsPadding()
.navigationBarsPadding()
) {
// content
}
```
ในส่วนการแสดง status bar สามารถไปใส่เพิ่มใน SideEffect ใน Theme ได้เลย
```kotlin
@Composable
fun GasTrackerTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
//...
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = android.graphics.Color.TRANSPARENT
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
}
}
// ...
}
```
แล้วเพื่อน ๆ คนไหนเจอเคสแปลก ๆ ตอนทำ Edge-to-Edge หรือมีเทคนิคจัดการปัญหานี้ สามารถมาแชร์กันได้เลย👇
#รวมเรื่องไอที #สาวกแอนดรอยด์ #programmer #developer #Lemon8ฮาวทู







อ่านโค้ดเต็มได้ที่นี่ https://www.mikkipastel.com/fix-edge-to-edge-android-15/