ถ้าพูดถึงการทำให้แอปฯแสดงหน้าจอแบบ Fullscreen หรือ Immersive ก็คงจะร้องอ๋อกัน ซึ่งในบทความนี้ก็จะขอพูดถึงการควบคุมการทำงานของ System UI ภายในแอปฯกันครับ

เนื้อหาในบทความนี้จะไม่ได้พูดถึง WindowInsets API ตัวใหม่ของ Android 11

ซึ่ง System UI ที่ว่านี้ก็หมายถึง Status Bar, Navigation Bar และ Action Bar (หรือ Toolbar) ที่คุ้นเคยกันดีนั่นเอง ซึ่งระบบแอนดรอยด์ก็เปิดให้แอปฯสามารถเปลี่ยนรูปแบบการแสดงผลของ System UI ให้เหมาะกับการใช้งานแอปฯในแต่ละรูปแบบได้

ซึ่งควบคุมยังไงได้บ้างนั้นก็จะขึ้นอยู่กับเวอร์ชันของแอนดรอยด์ด้วย เพราะเวอร์ชันเก่าๆก็อาจจะทำงานบางอย่างไม่ได้ แต่จะมีอะไรบ้างนั้นมาดูกันต่อเลย

Dimming หรือ Low Profile

เป็นความสามารถที่ถูกเพิ่มเข้ามาตั้งแต่สมัย Android 4.0 Ice Cream Sandwich (API Level 14) โดยจะลดการแสดงผลของ Status Bar และ Navigation Bar ให้รบกวนผู้ใช้น้อยลง ซึ่งเหมาะกับแอปที่มีเนื้อหาที่ผู้ใช้ต้องดูเป็นระยะเวลานาน และไม่อยากให้การทำงานส่วนอื่นๆมารบกวนสายตาของผู้ใช้

เมื่อเปิดใช้งาน Dimming หรือ Low Profile ผู้ใช้จะเห็นแค่ระดับแบตเตอรีที่ Status Bar เท่านั้น ส่วนไอคอนอื่นๆรวมไปถึง Notification จากแอปต่างๆจะถูกซ่อนออกไป ในขณะเดียวกันปุ่มที่อยู่บน Navigation Bar ก็จะปรับสีเพื่อลด Contrast ลงไปด้วย

การแสดงผลแบบ Dimming หรือ Low Profile จะต้องใช้คำสั่งแบบนี้

val window: Window = /* ... */
val decorView: View = window.decorView
decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE

จะเรียกใช้งานเมื่อไรก็ได้ โดยที่ Dimming จะไม่ถูกยกเลิกจนกว่าผู้ใช้จะกดที่ Status Bar/Navigation Bar หรือใช้คำสั่งเพื่อยกเลิกแบบนี้

val window: Window = /* ... */
val decorView = window.decorView
decorView.systemUiVisibility = 0

การแสดง Status Bar

เวลาที่ต้องการซ่อน Status Bar จะต้องกำหนด Flag ให้กับ setSystemUiVisibility(...) แบบนี้

val window: Window = /* ... */
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN

และถ้าอยากให้ Status Bar กลับมาแสดงอีกครั้งก็ให้เคลียร์ค่าเป็น 0 ซะ

val window: Window = /* ... */
window.decorView.systemUiVisibility = 0

และเมื่อ Status Bar มีการซ่อนหรือแสดงจะทำให้ UI ของแอปมีการปรับความสูงเพื่อให้พอดีกับขนาดหน้าจอด้วย

การแสดง Navigation Bar

สำหรับการควบคุม Navigation Bar นั้นจะทำได้ตั้งแต่ Android 4.1 Jelly Bean (API Level 16) ขึ้นไป โดยมีคำสั่งคล้ายกันกับของ Status Bar เลย แต่ว่าจะใช้ Flag ที่แตกต่างกันเล็กน้อย

// คำสั่งซ่อน Navigation Bar
val window: Window = /* ... */
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION

// คำสั่งแสดง Navigation Bar
val window: Window = /* ... */
window.decorView.systemUiVisibility = 0

แน่นอนว่าเมื่อ Navigation Bar มีการซ่อนหรือแสดงก็จะทำให้ UI ของแอปปรับความสูงให้พอดีกับหน้าจอด้วยเช่นกัน

การแสดง Navigation และ Status Bar พร้อมๆกัน

เนื่องจากคำสั่ง setSystemUiVisibility เป็นการกำหนดค่าแบบ Flag ดังนั้นจึงกำหนดค่าหลายๆตัวพร้อมๆกันได้ จึงทำให้สามารถซ่อน/แสดง Navigation Bar และ Status Bar ไปพร้อมๆกันได้เลย

// คำสั่งซ่อน Navigation Bar และ Status Bar
val window: Window = /* ... */
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN

// คำสั่งแสดง Navigation Bar และ Status Bar
val window: Window = /* ... */
window.decorView.systemUiVisibility = 0

Immersive Fullscreen

จากที่ผ่านมาจะเห็นว่าถ้าอยากให้แอปฯสามารถแสดงผลแบบ Fullscreen ได้ ก็จะต้องเขียนโค้ดเพิ่มเข้าไปเอง ซึ่งยังไม่รวมไปถึงการออกจากโหมด Fullscreen และการทำปุ่มเพื่อกดออกจาก Fullscreen ก็อาจจะดูไม่สะดวกเสมอไป ดังนั้นบน Android 4.4 KitKat (API 19) จึงได้เพิ่มความสามารถในการแสดงผลแบบ Fullscreen ให้สะดวกและง่ายมากขึ้นโดยมีชื่อเรียกว่า Immersive Fullscreen

ประโยชน์หลักๆของ Immersive Fullscreen คือนักพัฒนาไม่ต้องจัดการอะไรเกี่ยวกับการแสดงผลมากนัก นอกจากสั่งแค่ให้มันแสดงผลเป็นแบบ Immersive Fullscreen เท่านั้น

โดยคำสั่งของ Immersive Fullscreen จะใช้คำสั่งแบบนี้

val window: Window = /* ... */
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
        View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
        View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
        View.SYSTEM_UI_FLAG_FULLSCREEN or
        View.SYSTEM_UI_FLAG_IMMERSIVE

เมื่อคำสั่งทำงาน แอปฯก็จะเข้าสู่ Immersive Fullscreen ทันที และในกรณีที่แอปฯนั้นๆเพิ่งจะแสดงเป็น Immersive Fullscreen ครั้งแรก จะมีหน้าต่างแสดงให้ผู้ใช้รับรู้ด้วย

สำหรับการออกจาก Immersive Fullscreen สามารถทำได้ด้วยปัดนิ้วที่ขอบด้านบนหรือด้านล่างของหน้าจอ ก็จะออกจาก Immersive Fullscreen ทันที

โดยการทำงานของ Immersive Fullscreen นั้นจะมีสองแบบด้วยกัน คือแบบ Non-Sticky Immersion และ Sticky Immersion

Non-Sticky Immersion

เป็นการเข้าสู่ Immersive Fullscreen เพียงแค่ครั้งเดียว จนกว่าผู้ใช้จะกดออก ซึ่งใช้คำสั่งเดียวกันกับที่เจ้าของบล็อกยกตัวอย่างในตอนแรกนั่นแหละ

val window: Window = /* ... */
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
        View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
        View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
        View.SYSTEM_UI_FLAG_FULLSCREEN or
        View.SYSTEM_UI_FLAG_IMMERSIVE

เหมาะสำหรับแอปฯที่ต้องการแสดงผลแบบ Immersive Fullscreen ในการใช้งานบางอย่างเท่านั้น

Sticky Immersion

แต่ถ้าเกิดอยากจะให้แอปแสดงผลแบบ Immersive Fullscreen ตลอดเวลาเลย ก็ให้ใช้คำสั่งแบบนี้

val window: Window = /* ... */
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
        View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
        View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
        View.SYSTEM_UI_FLAG_FULLSCREEN or
        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY

โดยคำสั่งจะคล้ายๆกับก่อนหน้านี้เลย ต่างกันแค่ตรง View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY เท่านั้นเอง

ซึ่งผลลัพธ์จะออกมาเป็นลักษณะแบบนี้

จะเห็นว่าผู้ใช้จะอยู่ในโหมด Fullscreen เสมอ เมื่อมีการลากนิ้วที่ขอบข้างบน/ล่างของหน้าจอ ก็จะเป็นการแสดง Status Bar กับ Navigation Bar และเมื่อผ่านไปซักพักก็จะซ่อนเองโดยอัตโนมัติ

การกรณีที่ใช้ Immersive Fullscreen แบบ Sticky เจ้าของบล็อกแนะนำว่าให้ใส่โค้ดลงใน onWindowFocusChanged ได้เลย เพื่อที่ว่าจะได้เข้า Immersive Fullscreen ทันทีที่เข้าสู่หน้า Activity นั้นๆ

class MainActivity : Activity() {
    /* ... */
    override fun onWindowFocusChanged(hasFocus: Boolean) {
        super.onWindowFocusChanged(hasFocus)
        if (hasFocus) {
            val decorView = window.decorView
            decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                    or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                    or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                    or View.SYSTEM_UI_FLAG_FULLSCREEN
                    or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
        }
    }
}

การซ่อน Title/Action Bar และ Status Bar ผ่าน Theme

นอกเหนือจากการสั่งงานผ่านโค้ด Java แล้ว ยังสามารถกำหนดให้แสดงผลแบบ Fullscreen ได้ด้วยการกำหนด Theme ให้กับ Activity ที่ต้องการได้เลย

สามารถกำหนด Theme ให้มี Parent เป็น Theme ที่เป็น Fullscreen ได้เลย

จากภาพข้างบนก็จะเห็นว่าการสั่งให้ Fullscreen เพื่อซ่อน Status Bar นั้นจะมาพร้อมกับการซ่อน Title/Action Bar เสมอ ซึ่งก็ไม่เข้าใจเหมือนกันว่าทำไม

แต่ถ้าอยากจะซ่อนเฉพาะ Status Bar จริงๆก็กำหนดเพิ่มเข้าไปใน Theme ที่ใช้อยู่ได้เลย

<resources>
    <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
        <item name="android:windowFullscreen">true</item>
        <!-- ... -->
    </style>
    <!-- ... -->
</resources>

เพียงเท่านี้ Theme ของผู้ที่หลงเข้ามาอ่านก็จะกลายเป็น Theme ที่ซ่อน Status Bar และผู้ใช้ก็ยังคงสามารถปัดนิ้วที่ขอบบนของจอเพื่อแสดง Status Bar ชั่วคราวได้

แต่วิธีแบบนี้จะไม่สามารถซ่อน Navigation Bar ผ่าน Theme ได้นะ

สรุป

การควบคุม System UI นั้นก็คือการซ่อน/แสดง Status Bar และ Navigation Bar นั่นเอง ซึ่งสามารถทำได้หลายแบบ โดยขึ้นอยู่กับความต้องการใช้งาน

ซึ่งจะมีรูปแบบต่างๆดังนี้

  • Dimming
  • Show/Hide Status Bar
  • Show/Hide Navigation Bar
  • Fullscreen
  • Immersive Fullscreen
  • Show/Hide Status Bar with Theme

สุดท้ายแล้วก็ขอให้เลือกใช้งานตามความเหมาะสมนะครับ