พอดีเจ้าของบล็อกต้องเขียนแอปพลิเคชันที่รองรับการหมุนจอ แต่ทีนี้บนแอนดรอยด์นั้นมีปัญหาเรื่อง Fragment อย่างหนึ่ง คือเรื่องความต่างระหว่าง Phone, Tablet 7" และ Tablet 10" ก็เลยเขียนคลาสสำหรับจัดการกับเรื่องการหมุนหน้าจอเล่นๆ

ก่อนอื่นคงต้องขอพูดเรื่อง Orientation ของอุปกรณ์แอนดรอยด์กันก่อน
Orientation หรือทิศทางของตัวเครื่อง เพราะว่า Smart Device เหล่านี้ สามารถใช้งานโดยหมุนทิศทางของจอได้ตามการใช้งาน ดังนั้นจึงต้องมีการรู้ว่า Orientation ของเครื่อง ณ ตอนนั้นอยู่ทิศทางใด

สำหรับอุปกรณ์แอนดรอยด์นั้นจะมีทิศทางหลักหรือ Natural Orientation ซึ่งเป็นทิศทางการใช้งานหลักของเครื่องแต่ละแบบดังนี้

สำหรับ Tablet 10 นิ้ว ถูกสร้างขึ้นมาเพื่อใช้งานในแนวนอนเป็นหลัก เน้นไปที่การใช้งานด้วยการถือสองมือซะมากกว่า ในขณะที่ Tablet 7 นิ้วกับ Phone เน้นการใช้านที่แนวตั้งเป็นหลัก โดยถูกออกแบบมาให้ใช้งานด้วยการถือด้วยมือเดียวได้

ทีนี้ก็ต่อกันที่ว่าทิศทางของมือถือมีแบบไหนบ้าง

สำหรับคลาส Orientation Manager จะช่วยบอกให้ได้ว่าตอนนี้เครื่องหมุนอยู่ในทิศทางใด และมี Listener คอยเช็คเวลาเครื่องหมุน และที่สำคัญเลยก็คือการหมุนแบบ Mirror หรือทิศทางตรงข้าม 180 องศา โดยปกติการหมุนหน้าจอหนึ่งครั้ง onResume จะถูกเรียกใช้งานใหม่ทุกครั้ง ทำให้สามารถรู้ได้ว่าหน้าจอถูกหมุนเพื่อเปลี่ยนทิศทางอยู่ แต่ในกรณีที่หมุนไปทิศทางตรงข้าม 180 องศาเลย สมมติว่าหมุนจาก Normal Landscape เป็น Reverse Landscape โดย onResume จะไม่ถูกเรียกใหม่อีกครั้ง หรือ onConfigurationChanged ก็เช่นกัน

ในการทำงานของแอนดรอยด์จะรับรู้จากการเปลี่ยนแปลงของ Layout เท่านั้น อย่างเช่นจาก Normal Landscape ไปเป็น Normal Landscape Layout จะมีการเปลี่ยนแปลงคือจาก Layout แบบแนวตั้งก็เปลี่ยนเป็นแนวนอน ทำให้ onResume และ onConfigurationChanged ถูกเรียกให้ทำงานใหม่ แต่สำหรับการหมุนแบบ Mirror นั้น Layout จะไม่มีการเปลี่ยนแปลง แค่หมุน 180 องศาเท่านั้น แต่อื่นๆที่อยู่ใน Layout ยังคงเดิม จึงทำให้ onResume และ onConfigurationChanged ไม่ถูกเรียกใช้งานนั่นเอง

สรุปคือ onResume ไม่ได้ทำงานเมื่อมีการหมุนหน้าจอเสมอไป แต่ขึ้นอยู่กับการเปลี่ยนแปลงของ Layout บนหน้าแอปนั้นๆ

ดังนั้นเจ้าของบล็อกจึงต้องใช้ Orientation Listener เพื่อเช็คมุมการหมุนเอง แล้วสร้างเป็น Orientation Manager Listener เพื่อบอกว่าหน้าจอหมุนแล้ว โดยเขียนให้รองรับการหมุนแบบ Mirror ด้วย จึงหมดปัญหาเรื่องนี้ไปเลย อีกทั้งยังเช็คได้ทันทีว่าทิศทางของเครื่องอยู่ในทิศทางใด และมีคำสั่งในการล็อคหน้าจอไม่ให้หมุนไปมาด้วย เพราะในบางครั้งผู้ที่หลงเข้ามาอ่านอยากให้มีปุ่มล็อคหน้าจอ แบบแอปที่ล็อคให้หยุดหมุนจอได้ และปลดล็อคเพื่อให้หมุนต่อได้

คลาสนี้เจ้าของบล็อกตั้งชื่อมันว่า OrientationManager มีโค๊ดดังนี้

app.akexorcist.orientationmanager; java.lang.reflect.Method; android.annotation.SuppressLint; android.app.Activity; android.content.Context; android.content.pm.ActivityInfo; import android.hardware.SensorManager; android.os.Build; android.util.DisplayMetrics; android.view.Display; android.view.OrientationEventListener; android.view.Surface; import android.view.WindowManager; public class OrientationManager { public final static int  PORTRAIT_NORMAL = 0; public final static int  PORTRAIT_REVERSE = 1; public final static int  LANDSCAPE_NORMAL = 2; public final static int LANDSCAPE_REVERSE = 3; Context context; Activity activity; String device_orientation = ""; OrientationEventListener mOrientationEventListener; OrientationManagerListener mOrientationManagerListener; @SuppressWarnings("deprecation") @SuppressLint("NewApi") OrientationManager(Activity activity) { .activity = activity; .context = activity.getApplicationContext(); xres = 0, yres = 0; Method mGetRawH; Display display = activity.getWindowManager().getDefaultDisplay(); DisplayMetrics dm = new DisplayMetrics(); activity.getWindowManager().getDefaultDisplay().getMetrics(dm); if(Build.VERSION. SDK_INT < Build.VERSION_CODES."getRawWidth"); xres = (Integer) mGetRawW.invoke(display); yres = (Integer) mGetRawH.invoke(display); } (Exception e) { xres = display.getWidth(); yres = display.getHeight(); } } (Build.VERSION. JELLY_BEAN_MR1) { try { mGetRawH = Display.class.getMethod("getRawHeight"); Method mGetRawW = Display.class.getMethod(widthPixels; yres = outMetrics.heightPixels; } hdp = (int)(yres * (1f / dm. SDK_INT >= Build.VERSION_CODES.density)); JELLY_BEAN_MR1) { DisplayMetrics outMetrics = DisplayMetrics (); display.getRealMetrics(outMetrics); xres = outMetrics.density)); sw = (hdp < wdp) ? hdp : wdp; device_orientation = (sw >= 720) ? "landscape" : "portrait"; mOrientationEventListener = OrientationEventListener(context , SensorManager. SENSOR_DELAY_NORMAL ){ orientation = -1; public void  onOrientationChanged(arg0) { (orientation == -1) { orientation = getOrientation(); } { (orientation != getOrientation()) { ((orientation == PORTRAIT_NORMAL && getOrientation() == PORTRAIT_REVERSE) || (orientation == PORTRAIT_REVERSE && getOrientation() == PORTRAIT_NORMAL) || (orientation == LANDSCAPE_NORMAL && getOrientation() == LANDSCAPE_REVERSE) || (orientation == LANDSCAPE_REVERSE && getOrientation() == LANDSCAPE_NORMAL)) { mOrientationManagerListener.onMirrorRotatation( getOrientation()); mOrientationManagerListener.onOrientationChanged( getOrientation(), ); } { mOrientationManagerListener.onOrientationChanged( getOrientation(), ); } orientation = getOrientation(); } } } }; } public void  setOnOrientationListener (OrientationManagerListener listener) { mOrientationManagerListener = listener; } int wdp = (int)(xres * (1f / dm.public void enable() { mOrientationEventListener.enable(); } public void  disable() { mOrientationEventListener.disable(); } @SuppressWarnings("static-access") getOrientation() { WindowManager wm = (WindowManager)context.getSystemService( context. WINDOW_SERVICE ); rotation = wm.getDefaultDisplay().getRotation(); (device_orientation.equals("portrait")) { (rotation == Surface.) { ROTATION_90 ) { LANDSCAPE_NORMAL; } (rotation == Surface."landscape")) { (rotation == Surface.) { ROTATION_90 ) { PORTRAIT_REVERSE; } (rotation == Surface. ROTATION_180 ) { LANDSCAPE_REVERSE; } (rotation == Surface. return PORTRAIT_NORMAL; } (rotation == Surface. SCREEN_ORIENTATION_PORTRAIT ; } rotation = getOrientation(); (rotation) { OrientationManager.PORTRAIT_NORMAL: activity.setRequestedOrientation( ActivityInfo. ROTATION_180) { return PORTRAIT_REVERSE; } (rotation == Surface. ROTATION_270) { LANDSCAPE_REVERSE; } } (device_orientation.equals(LANDSCAPE_REVERSE: activity.setRequestedOrientation( SCREEN_ORIENTATION_REVERSE_LANDSCAPE); ; } } public void  enableRotation() { enable(); activity.setRequestedOrientation( ActivityInfo. return LANDSCAPE_NORMAL; } (rotation == Surface. public interface  OrientationManagerListener{ public void  onOrientationChanged( ROTATION_270) { PORTRAIT_NORMAL; } } -1; } public void disableRotation() { disable(); SCREEN_ORIENTATION_REVERSE_LANDSCAPE = 8; SCREEN_ORIENTATION_REVERSE_PORTRAIT = 9; public void  onMirrorRotatation( if (Build.VERSION. SDK_INT <= Build.VERSION_CODES. FROYO){ SCREEN_ORIENTATION_REVERSE_LANDSCAPE = ActivityInfo. SCREEN_ORIENTATION_LANDSCAPE; SCREEN_ORIENTATION_REVERSE_PORTRAIT = ActivityInfo. SCREEN_ORIENTATION_PORTRAIT); ; OrientationManager. PORTRAIT_REVERSE: activity.setRequestedOrientation( SCREEN_ORIENTATION_REVERSE_PORTRAIT); ; OrientationManager.LANDSCAPE_NORMAL: activity.setRequestedOrientation( ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); ; OrientationManager. SCREEN_ORIENTATION_UNSPECIFIED); } int orientation, isMirror); int orientation); } }




ไม่ต้องไปซีเรียสมากกับโค๊ดอันนี้นะ เจ้าของบล็อกเขียนขึ้นมาให้ใช้งานเฉยๆ

สำหรับการเรียกใช้งานคลาส OrientationManager จะเป็นดังนี้

สมมติว่าเจ้าของบล็อกตั้งชื่อออบเจ็คว่า om ละกันนะ ให้ดูตรงที่ ActivityName ให้ใส่ชื่อ Activity ที่เรียกใช้คลาสนี้ เช่น เจ้าของบล็อกเรียกใช้คลาสนี้ใน Main.java ก็จะเป็น

โดยจะมีคำสั่งทั้งหมดดังนี้

om.getOrientation(); om.enable(); om.disable(); om.enableRotation(); om.disableRotation(); om.setOnOrientationListener(Listener);




เป็นคำสั่งสำหรับรับค่าทิศทางของตัวเครื่องนั้น โดยค่าที่ Return กลับมาจะเป็นค่า Integer แทนทิศทางดังนี้

public final static int PORTRAIT_NORMAL = 0; public final static int PORTRAIT_REVERSE = 1; public final static int LANDSCAPE_NORMAL= 2; public final static int  LANDSCAPE_REVERSE = 3;




เริ่มทำการเช็คทิศทางของเครื่องเพื่อตรวจจับการเปลี่ยนแปลงใช้ในกรณีที่เรียกใช้ OrientationManagerListener เท่านั้น โดยเรียกใช้คำสั่งนี้ใน onResume

หยุดทำการเช็คทิศทางของเครื่องเพื่อตรวจจับการเปลี่ยนแปลงใช้ในกรณีที่เรียกใช้ OrientationManagerListener เท่านั้น โดยเรียกใช้คำสั่งนี้ใน onPause

กำหนดให้ Activity นั้นๆสามารถหมุนทิศทางจอได้

กำหนดให้ Activity นั้นๆไม่สามารถหมุนทิศทางจอได้

setOnOrientationListener

สร้าง Listener สำหรับตรวจจับเมื่อเครื่องมีการเปลี่ยนแปลงทิศทางจอ โดยมีฟังก์ชันสองฟังก์ชันคือ onOrientationChanged กับ onMirrorRotation

om.setOnOrientationListener( new OrientationManagerListener() { public void onOrientationChanged( int orientation, boolean isMirror) { // เมื่อมีการหมุนหน้าจอเปลี่ยนไปในทิศทางใดๆ } public void onMirrorRotatation(// เมื่อมีการหมุนหน้าจอในทิศทางตรงกันข้าม } }); int orientation) {




สำหรับ onOrientationChanged จะทำงานทุกครั้งที่หน้าจอเปลี่ยนแปลงทิศทาง แม้แต่การหมุนแบบทิศทางตรงกันข้ามอย่าง Mirror ก็ด้วย โดยจะมีพารามิเตอร์ส่งเข้ามาในฟังก์ชันนี้ด้วยกันสองตัว

public void  onOrientationChanged( int orientation, boolean isMirror) { // orientation : ทิศทางของหน้าจอ (ค่าเดียวกับคำสั่ง getOrientation) // isMirror : เป็นการหมุนแบบทิศทางตรงกันข้ามหรือไม่ (true ใช่, false ไม่ใช่) }




ส่วน onMirrorRotation เอาไว้สำหรับกรณีที่หมุนในทิศทางตรงกันข้ามโดยเฉพาะ

*- เพิ่มเติมสำหรับมือใหม่ -*

จะรู้ได้ไงว่าค่าทิศทางจากตัวแปร orientation เป็นทิศทางไหน ก็ใช้ IF หรือ Switch-Case ในการเช็คว่าค่าตรงกับทิศทางไหน

(orientation == OrientationManager. PORTRAIT_NORMAL) { // หน้าจอแสดงทิศทาง Normal Portrait } (orientation == OrientationManager. PORTRAIT_REVERSE) { // หน้าจอแสดงทิศทาง Reverse Portrait } (orientation == OrientationManager.// หน้าจอแสดงทิศทาง Normal Landscape } (orientation == OrientationManager. LANDSCAPE_NORMAL) { // หน้าจอแสดงทิศทาง Reverse Landscape } LANDSCAPE_REVERSE) {




เท่านี้ก็รู้ได้แล้วว่าเป็นทิศทางใดอยู่

ทีนี้มาดูตัวอย่างการใช้งานกันเลยดีกว่า โดยเจ้าของบล็อกจะให้แสดงบนหน้าจอว่า ตอนนี้เครื่องกำลังอยู่ในทิศทางแบบใดอยู่ โดยแสดงบอกว่า Text View ที่อยู่กลางหน้าจอและมุมขวาบนจะมีปุ่ม Toggle Button เอาไว้เปิด-ปิดการล็อคหน้าจอ ถ้า On จะเป็นการล็อค

<RelativeLayout xmlns:android=xmlns:tools="http://schemas.android.com/apk/res/android""http://schemas.android.com/tools"  android:layout_width="match_parent"  android:layout_height="match_parent"  android:paddingBottom="@dimen/activity_vertical_margin"  android:paddingLeft="@dimen/activity_horizontal_margin"  android:paddingRight="@dimen/activity_horizontal_margin"  android:paddingTop="@dimen/activity_vertical_margin"  > <TextView android:id="@+id/textView1"  android:layout_width=android:layout_height=android:layout_centerHorizontal= android:layout_centerVertical= android:text="@string/hello_world"  /> <ToggleButton android:id=android:layout_width="wrap_content" android:layout_height=android:layout_alignParentRight="wrap_content" android:layout_alignParentTop= android:text="ToggleButton"  /> </RelativeLayout>"@+id/toggleButton1""wrap_content""wrap_content""true"
app.akexorcist.orientationmanager; android.os.Bundle; android.app.Activity; android.util.Log; android.widget.CompoundButton; android.widget.CompoundButton.OnCheckedChangeListener; android.widget.TextView; import android.widget.ToggleButton; app.akexorcist.orientationmanager.OrientationManager.OrientationManagerListener; public class  Main Activity { OrientationManager om; TextView textView1; public void onCreate(Bundle savedInstanceState) { .onCreate(savedInstanceState); setContentView(R.layout.); textView1 = (TextView)findViewById(R.id.); ToggleButton toggleButton1 = (ToggleButton)findViewById(R.id. toggleButton1 ); toggleButton1.setOnCheckedChangeListener( OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView , isChecked) { (isChecked) om.disableRotation(); om.enableRotation(); } }); om = OrientationManager(Main.); om.setOnOrientationListener(OrientationManagerListener() { public void  onOrientationChanged(orientation , isMirror) { (!isMirror) { Log.i("OrientationManager", "Orientation Changed"); checkOrientation(orientation); } } public void  onMirrorRotatation("OrientationManager", int orientation) { Log.i("Mirror Rotatation"); checkOrientation(orientation); } }); checkOrientation(om.getOrientation()); } public void  onResume() { .onResume(); om.enable(); } public void  onPause() { .onPause(); om.disable(); } public void  checkOrientation("Normal Portrait"); int orientation) { (orientation == OrientationManager. PORTRAIT_NORMAL) textView1.setText("Reverse Portrait"); (orientation == OrientationManager."Normal Landscape"); else if(orientation == OrientationManager. PORTRAIT_REVERSE) textView1.setText( LANDSCAPE_REVERSE ) textView1.setText("Reverse Landscape"); } } LANDSCAPE_NORMAL) textView1.setText( else if(orientation == OrientationManager.

3. ประกาศและกำหนดค่าให้กับ Toggle Button และเรียกใช้ Listener แบบ onCheckChangedListener ที่จะทำงานเมื่อ Toggle Button เปลี่ยนจากสถานะ On เป็น Off หรือจาก Off เป็น On ถ้าสถานะเป็น On ให้ล็อคการหมุนจอด้วยคำสั่ง disableRotation และถ้าเป็น Off ให้ปลดล็อคการหมุนจอด้วยคำสั่ง enableRotation

4. กำหนดค่าให้กับ OrientationManager และเรียกใช้ Listener แบบ onOrientationManager ที่จะทำงานเมื่อมีการหมุนหน้าจอ

5. เมื่อมีการหมุนหน้าจอจะทำการเช็คก่อนว่าหมุนแบบ 180 องศาหรือไม่ โดยเช็คจากตัวแปร isMirror ว่าเป็น True หรือ False ถ้าเป็น False ก็คือหมุนแค่ 90 องศาปกติ จะให้แสดงข้อความบน Log และเรียกฟังก์ชัน checkOrientation เพื่อแสดงบน Text View ว่าหมุนทิศทางใด

6. เมื่อมีการหมุนหน้าจอแบบ 180 องศา จะให้แสดงข้อความบน Log และเรียกฟังก์ชัน checkOrientation เพื่อแสดงบน Text View ว่าหมุนทิศทางใด

7. เรียกฟังก์ชัน checkOrientation เพื่อแสดงบน Text View ว่าหมุนทิศทางใด โดยเรียกตั้งแต่แรกเริ่มใน onCreate เพื่อให้เปิดแอปขึ้นมาแล้วแสดงค่าทันที

8. ฟังก์ชัน onResume เรียกคำสั่ง enable เพื่อเช็คทิศทางการหมุนจอ สำหรับ OrientationManagerListener

9. ฟังก์ชัน onPause เรียกคำสั่ง disable เพื่อหยุดเช็คทิศทางการหมุนจอ สำหรับ OrientationManagerListener

10. ฟังก์ชันที่สร้างขึ้นมาเพื่อเช็คค่าจากตัวแปร orientation ว่าตอนนั้นๆหน้าจอกำลังหมุนอยู่ในทิศทางใด โดยเปรียบเทียบกับค่าในคลาส OrientationManager แล้วแสดงทิศทางที่ได้บน Text View

** ในกรณีที่ไม่ใช้ OrientationManagerListener ก็เอาคำสั่ง enable กับ disable ออกได้เลย **