สำหรับแอนดรอยด์แล้ว การทำให้ Layout สามารถแสดงผลแยกกันระหว่างหน้าจอแนวตั้งกับแนวนอนนั้นไม่ใช่เรื่องอยากซักเท่าไร เพราะแอนดรอยด์ได้สร้างสิ่งที่เรียกว่า Configuration Qualifier เพื่อช่วยจัดการเรื่องนี้แล้ว แต่ถ้าอยากจะดัก Event เมื่อผู้ใช้มีการหมุนหน้าจออุปกรณ์แอนดรอยด์ล่ะ ต้องทำยังไง?

วิธีที่นักพัฒนาส่วนใหญ่ใช้กัน

เนื่องจากแอนดรอยด์ไม่มีคำสั่งเช็ค Event ดังกล่าวให้ ดังนั้นนักพัฒนาส่วนใหญ่จึงใช้วิธีแบบนี้แทน

เริ่มจากกำหนด configChange ใน Android Manifest ให้กับ Activity ที่ต้องการดังนี้

<!-- AndroidManifest.xml -->
<?xml version="1.0" encoding="utf-8"?>
<manifest>

    ...

    <application>
        <activity
            android:name=".MainActivity"
            android:configChanges="screenSize|orientation"/>

        ...
    </application>

</manifest>

แล้วใน Activity ก็ดัก Event จาก onConfigurationChanged เพื่อเทียบดูว่าค่า Orientation เปลี่ยนหรือไม่

class MainActivity : AppCompatActivity() {
    private var screenOrientation = 0

    override fun onConfigurationChanged(newConfig: Configuration) {
        super.onConfigurationChanged(newConfig)
        if (screenOrientation != newConfig.orientation) {
            screenOrientation = newConfig.orientation
            // Do something here when screen orientation has changed
        }
    }
}

ซึ่งบอกเลยว่าวิธีนี้อาจจะดูเหมือนทำงานได้ แต่เอาเข้าจริงแล้วเจ้าของบล็อกไม่แนะนำให้ทำแบบนี้ด้วยซ้ำ

ทำไมวิธีนี้ถึงไม่ถูกต้อง?

ถ้าอยากจะรู้เรื่องนี้แบบละเอียดๆ ให้ไปอ่านที่ Configuration Changes อีกหนึ่งอย่างที่นักพัฒนาแอนดรอยด์ควรรู้จัก

แต่ถ้าจะให้อธิบายสั้นๆก็ต้องบอกว่า android:configChange ไม่ได้ทำมาให้ใช้งานแบบนี้ ดังนั้นถ้าใช้วิธีแบบนี้ นั่นหมายความว่า Configuration Change ของ Activity ตัวนั้นๆจะทำงานไม่ถูกต้อง เช่น แบ่ง Layout ไว้เป็น Portrait หรือ Landscape และเมื่อผู้ใช้หมุนหน้าจอจะไม่เปลี่ยนไปตาม Behavior ที่ถูกต้อง

วิธีที่ดีกว่าการไปยุ่งกับ Configuration Change

เนื่องจากการไปยุ่งกับ Configuration Change ไม่ใช่วิธีที่ถูกต้อง ดังนั้นเจ้าของบล็อกจึงต้องมองหาวิธีอื่นๆที่สามารถรับรู้ได้ว่า “เฮ้ย หน้าจอมีการหมุนนะ”

และวิธีที่เลือกใช้ก็คือเช็ค Screen Orientation ตอน onStart แล้วเก็บค่าไว้ และเมื่อมีการหมุนหน้าจอ onStart ก็จะถูกเรียกอีกครั้ง ก็จะดึงค่า Screen Orientation มาเทียบกับของเก่าว่ามีการเปลี่ยนแปลงหรือไม่ (เนื่องจาก onStart สามารถถูกเรียกได้ตลอดเวลา)

class MainActivity : AppCompatActivity() {
    private var lastOrientation = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        if (savedInstanceState == null) {
            lastOrientation = resources.configuration.orientation
        }
    }

    override fun onStart() {
        ...
        checkOrientationChanged()
    }

    private fun checkOrientationChanged() {
        val currentOrientation = resources.configuration.orientation
        if (currentOrientation != lastOrientation) {
            onScreenOrientationChanged(currentOrientation)
            lastOrientation = currentOrientation
        }
    }

    private fun onScreenOrientationChanged(currentOrientation: Int) {
        // Do something here when screen orientation changed
    }
}

และสำหรับค่า lastOrientation ถ้าจะให้ดีที่สุดก็ควร Handle ใน Save/Restore State ด้วย ก็เลยเขียนเพิ่มเข้ามาอีกนิดหน่อย กลายเป็นแบบนี้

class MainActivity : AppCompatActivity() {
    companion object {
        private const val KEY_LAST_ORIENTATION = "last_orientation"
    }
    
    private var lastOrientation = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        if (savedInstanceState == null) {
            lastOrientation = resources.configuration.orientation
        }
    }

    override fun onStart() {
        super.onStart()
        checkOrientationChanged()
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle) {
        super.onRestoreInstanceState(savedInstanceState)
        lastOrientation = savedInstanceState.getInt(KEY_LAST_ORIENTATION)
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putInt(KEY_LAST_ORIENTATION, lastOrientation)
    }


    private fun checkOrientationChanged() {
        val currentOrientation = resources.configuration.orientation
        if (currentOrientation != lastOrientation) {
            onScreenOrientationChanged(currentOrientation)
            lastOrientation = currentOrientation
        }
    }

    private fun onScreenOrientationChanged(currentOrientation: Int) {
        // Do something here when screen orientation changed
    }
}

เท่านี้ก็เรียบร้อย~ สามารถรับรู้ได้ว่ามีการหมุนหน้าจอได้จาก Method ที่ชื่อว่า onScreenOrientationChanged

ไหนๆก็มาถึงจุดนี้แล้ว ทำเป็น Library ไปเลยละกัน

จากโค้ดดังกล่าวจึงทำให้เจ้าของบล็อกรวบคำสั่งแล้วทำเป็น Library ไว้ใช้งานไปเลย จะได้เรียกใช้งานสะดวกๆหน่อย ก็เลยทำเป็น Library ที่ชื่อว่า ScreenOrientationHelper ซะเลย

วิธีใช้งาน Screen Orientation Helper

เพิ่ม Dependency ของ Library ตัวนี้ไว้ใน build.gradle ดังนี้

implementation 'com.akexorcist:screenorientationhelper:1.0.0'

เวลาจะเรียกใช้งานให้สร้าง Base Activity ขึ้นมาครับ เพราะผมเขียนโค้ดไว้ให้มันสามารถนำไป Imprement ใส่ Activity ใดๆก็ได้ตามที่ต้องการ

ยกตัวอย่างเช่น Activity ที่ผู้ที่หลงเข้ามาอ่านใช้งานอยู่คือ AppCompatActivity ดังนั้นก็ให้สร้าง Base Activity ขึ้นมาดังนี้

abstract class BaseActivity: AppCompatActivity() { ... }

แล้วเพิ่มคำสั่งของ ScreenOrientationHelper เข้าไปดังนี้

abstract class BaseActivity : AppCompatActivity() {
    private val helper = ScreenOrientationHelper(this)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        helper.onCreate(savedInstanceState)
        helper.setScreenOrientationChangeListener(this)
    }

    override fun onStart() {
        super.onStart()
        helper.onStart()
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        helper.onSaveInstanceState(outState)
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle) {
        super.onRestoreInstanceState(savedInstanceState)
        helper.onRestoreInstanceState(savedInstanceState)
    }

    open fun onScreenOrientationChanged(orientation: Int) { }
}

โค้ดส่วนนี้เป็น Boilerpate Code ครับ ส่วนหนึ่งเพราะต้องการให้เอาไปใช้กับ Activity ตัวไหนก็ได้นั่นเอง ก็ให้สร้างเป็น Base Activity แบบนี้แล้วเรียกใช้ใน Activity ตัวอื่นๆตามต้องการละกันเนอะ

เวลาเอาไปใช้งานจริงๆก็จะเป็นแบบนี้

class MyActivity: BaseActivity() {
    override fun onScreenOrientationChanged(orientation: Int) {
        super.onScreenOrientationChanged(orientation)
    }
}

แค่ Override Method ที่ชื่อว่า onScreenOrientationChanged ก็จะสามารถดัก Event เมื่อผู้ใช้มีการหมุนหน้าจอได้แล้ววววววววววววว

สรุป

เนื่องจากเจ้าของบล็อกเห็นว่ายังมีนักพัฒนาหลายๆคนที่ใช้วิธีที่ไม่เหมาะสมและอาจจะส่งผลกระทบต่อการทำงานบางอย่างของแอป จึงมองหาวิธีอื่นที่ดีกว่ามาทดแทน แต่พอๆเขียนไปซักพักก็รู้สึกว่าน่าจะจับมาทำเป็น Library เลยดีกว่า ก็เลยออกมาเป็นแบบนี้นี่แหละ

สำหรับ Library ของ ScreenOrientationHelper สามารถเข้าไปดูโค้ดที่เจ้าของบล็อกเขียนไว้ได้ที่ Screen Orientation Helper [GitHub]

akexorcist/ScreenOrientationHelper
[Android] Screen orientation event listener helper for activity in Android - akexorcist/ScreenOrientationHelper