22 March 2017

ดัก Screen Orientation Event ใน Activity อย่างไรให้ถูกต้อง

Updated on

        สำหรับแอนดรอยด์แล้ว การทำให้ 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 เปลี่ยนหรือไม่

MainActivity.java
import android.content.res.Configuration;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    private int screenOrientation;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (screenOrientation != newConfig.orientation) {
            onScreenOrientationChanged();
            screenOrientation = newConfig.orientation;
        }
    }

    public void onScreenOrientationChanged() {
        // Do something here when screen orientation has changed
    }
}


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

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

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

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

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

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

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

SampleActivity.java
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class SampleActivity extends AppCompatActivity {
    private int lastOrientation;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);

        if (savedInstanceState == null) {
            lastOrientation = getResources().getConfiguration().orientation;
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        checkOrientationChanged();
    }

    private void checkOrientationChanged() {
        int currentOrientation = getResources().getConfiguration().orientation;
        if (currentOrientation != lastOrientation) {
            onScreenOrientationChanged(currentOrientation);
            lastOrientation = currentOrientation;
        }
    }

    public void onScreenOrientationChanged(int currentOrientation) {
        // Do something here when screen orientation changed
    }
}

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

SampleActivity.java
package com.akexorcist.myapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class SampleActivity extends AppCompatActivity {
    public static final String KEY_LAST_ORIENTATION = "last_orientation";
    private int lastOrientation;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);

        if (savedInstanceState == null) {
            lastOrientation = getResources().getConfiguration().orientation;
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        checkOrientationChanged();
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        lastOrientation = savedInstanceState.getInt(KEY_LAST_ORIENTATION);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(KEY_LAST_ORIENTATION, lastOrientation);
    }

    private void checkOrientationChanged() {
        int currentOrientation = getResources().getConfiguration().orientation;
        if (currentOrientation != lastOrientation) {
            onScreenOrientationChanged(currentOrientation);
            lastOrientation = currentOrientation;
        }
    }

    public void onScreenOrientationChanged(int currentOrientation) {
        // Do something here when screen orientation changed
    }
}

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

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

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

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

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

compile 'com.akexorcist:screenorientationhelper:1.0.0'

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

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

BaseActivity.java
import android.support.v7.app.AppCompatActivity;

public class BaseActivity extends AppCompatActivity {
    
}

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

BaseActivity.java
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;

import com.akexorcist.screenorientationhelper.ScreenOrientationHelper;


public class BaseActivity extends AppCompatActivity implements ScreenOrientationHelper.ScreenOrientationChangeListener {
    private ScreenOrientationHelper helper = new ScreenOrientationHelper(this);

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        helper.onCreate(savedInstanceState);
        helper.setScreenOrientationChangeListener(this);
    }

    @Override
    protected void onStart() {
        super.onStart();
        helper.onStart();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        helper.onSaveInstanceState(outState);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        helper.onRestoreInstanceState(savedInstanceState);
    }

    @Override
    public void onScreenOrientationChanged(int orientation) {

    }
}

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

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

import android.os.Bundle;

public class MainActivity extends BaseActivity {

    ...

    @Override
    public void onScreenOrientationChanged(int orientation) {
        // Do something when screen orientation changed
    }
}

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

สรุป

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

        ดังนั้นเลิกเถอะครับ เลิกไปยุ่งกับ Configuration Change โดยไม่จำเป็น เพียงแค่เพราะอยากจะดัก Event ตอนที่ผู้ใช้หมุนหน้าจอ

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