30 สิงหาคม 2556

[Android Code] D-Pad Controller


บทความรอบนี้กลับมาเป็นคลาสที่เขียนมาแจกกันบ้างดีกว่า 
จากเดิมที่เคยทำ Joystick Controller ไปแล้ว คราวนี้มาทำ D-Pad บ้าง


D-Pad หรือที่รู้จักกันก็คือปุ่มกดแบบลูกศรทิศทางนั่นเอง
ในบทความนี้เจ้าของบล็อกก็จะทำเป็นปุ่มแบบ 4 ทิศทาง
โดยจะใช้ Linear Layout สำหรับทำเป็น D-Pad Controller
การที่จะรู้ว่ากดทิศทางไหนอยู่ก็จะใช้ OnTouchListener 
โดยที่ OnTouchListener จะบอกได้ว่าผู้ใช้จิ้มที่พิกัดเท่าไร
จากนั้นก็เอาไปคำนวณอีกทีนึงเพื่อหาว่าผู้ใช้กดปุ่มใด


ก่อนอื่นวาดเส้นแบ่งให้ดูก่อนเลย จะได้เข้าใจได้ง่ายๆสำหรับมือใหม่
เนื่องจากเจ้าของบล็อกไม่ได้ต้องการรับรู้การจิ้มบนพื้นที่ทั้งหมด
แต่ต้องการแค่พื้นที่ที่เป็นปุ่มทิศทางทั้ง 4 ปุ่มเท่านั้น ก็เลยแบ่งตามภาพ
โดยจะแบ่งเป็น 3 x 3 ก็จะเห็นแล้วว่าที่อัตราส่วนเท่าไรเป็นปุ่มไหน

ดังนั้นเมื่อเจ้าของบล็อกสรุปเป็นเงื่อนไขก็จะได้ตามรูปข้างล่างนี้เลย


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

สมมติว่ามีขนาด 400 x 400px ขอบเขตของปุ่มซ้ายก็จะอยู่ที่
x มากกว่า 0 และน้อยกว่า 400 * 1 / 3 (0 และ 133.33)
y มากกว่า 400 * 1 / 3 และน้อยกว่า 400 * 2 / 3 (133.33 และ 266.67)

ดังนั้นใน OnTouchListener เจ้าของบล็อกก็จะเช็คขนาดของ Linear Layout
แล้วคำนวณออกมาเป็นพิกัด X Y แล้วเช็คว่าอยู่ที่ตำแหน่งปุ่มใดๆ
public void onTouch(View v, MotionEvent event) { int action = event.getAction(); if(action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE) { float x = event.getX(); float y = event.getY(); if(x >= v.getWidth() / 3 && x <= v.getWidth() * 2 / 3 && y >= 0 && y <= v.getHeight() * 1 / 3) { v.setBackgroundResource(แสดงภาพปุ่มขึ้นถูกกด); } else if(x >= v.getWidth() / 3 && x <= v.getWidth() * 2 / 3 && y >= v.getHeight() * 2 / 3 && y <= v.getHeight()) { v.setBackgroundResource(แสดงภาพปุ่มลงถูกกด); } else if(x >= 0 && x <= v.getWidth() / 3 && y >= v.getHeight() * 1 / 3 && y <= v.getHeight() * 2 / 3) { v.setBackgroundResource(แสดงภาพปุ่มซ้ายถูกกด); } else if(x >= v.getWidth() * 2 / 3 && x <= v.getWidth() && y >= v.getHeight() * 1 / 3 && y <= v.getHeight() * 2 / 3) { v.setBackgroundResource(แสดงภาพปุ่มขวาถูกกด); } else { direction = D_NONE; v.setBackgroundResource(แสดงภาพปกติ); } } else if(action == MotionEvent.ACTION_UP) { direction = D_NONE; v.setBackgroundResource(แสดงภาพปกติ); } }
ให้สังเกตที่ View v ให้ดีๆ จะเห็นว่า เจ้าของบล็อกใช้คำสั่ง
วัดความกว้างและความสูงจาก v แทนที่จะเป็น Linear Layout
อันนี้ก็เป็นส่วนหนึ่งที่ผู้ที่หลงเข้ามาอ่านหลายๆคนไม่เคยรู้เลย
ว่าเจ้า v ที่ส่งเข้ามาในฟังก์ชันนี้เนี่ยมันคืออะไร ใช้แต่ MotionEvent
จากฟังก์ชันนี้กำหนดไว้ว่า v คือ View ใดๆก็ตามที่ถูก Touch
ซึ่งก็คือ Linear Layout นั่นเอง จึงไม่จำเป็นต้องเรียกจากข้างนอก
สามารถเรียกใช้งาน v ได้เลย ผลลัพธ์ก็เหมือนกันเลยนั่นแหละ
โดยจะคำนวณเป็นพิกัดออกมาตามที่อธิบายไว้ก่อนหน้า
แล้วมาเปรียบเทียบว่าแตะที่ปุ่มใด แล้วก็เปลี่ยนภาพพื้นหลัง
ของ Linear Layout ให้ปุ่มนั้นๆถูกกดแทน (จะเหมือนปุ่มถูกกดจริงๆ)
ดังนั้นก็ต้องเตรียมภาพเมื่อปุ่มต่างๆถูกกดด้วย ก็จะขอใช้ดังนี้





กำหนดชื่อตามใจชอบ ขอให้แยกแยะชื่อไฟล์ถูกละกัน
เสร็จแล้วแหละ สำหรับการทำ D-Pad Controller

แต่ในบทความนี้ไม่ได้ให้ผู้ที่หลงเข้ามาอ่านทำเองทั้งหมด
แต่เจ้าของบล็อกได้ทำคลาสมาให้ใช้งานสั้นๆเรียบร้อยแล้ว


DPadController.java
package app.akexorcist.dpadcontroller; import android.view.MotionEvent; import android.view.View; public class DPadController { public static final int D_NONE = 0; public static final int D_UP = 1; public static final int D_DOWN = 2; public static final int D_LEFT = 3; public static final int D_RIGHT = 4; private int direction = D_NONE; private int imageNormal, imageUp, imageDown, imageLeft, imageRight; public DPadController(int imageNormal, int imageUp , int imageDown, int imageLeft, int imageRight) { this.imageNormal = imageNormal; this.imageUp = imageUp; this.imageDown = imageDown; this.imageLeft = imageLeft; this.imageRight = imageRight; } public void onTouch(View v, MotionEvent event) { int action = event.getAction(); if(action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE) { float x = event.getX(); float y = event.getY(); if(x >= v.getWidth() / 3 && x <= v.getWidth() * 2 / 3 && y >= 0 && y <= v.getHeight() * 1 / 3) { direction = D_UP; v.setBackgroundResource(imageUp); } else if(x >= v.getWidth() / 3 && x <= v.getWidth() * 2 / 3 && y >= v.getHeight() * 2 / 3 && y <= v.getHeight()) { direction = D_DOWN; v.setBackgroundResource(imageDown); } else if(x >= 0 && x <= v.getWidth() / 3 && y >= v.getHeight() * 1 / 3 && y <= v.getHeight() * 2 / 3) { direction = D_LEFT; v.setBackgroundResource(imageLeft); } else if(x >= v.getWidth() * 2 / 3 && x <= v.getWidth() && y >= v.getHeight() * 1 / 3 && y <= v.getHeight() * 2 / 3) { direction = D_RIGHT; v.setBackgroundResource(imageRight); } else { direction = D_NONE; v.setBackgroundResource(imageNormal); } } else if(action == MotionEvent.ACTION_UP) { direction = D_NONE; v.setBackgroundResource(imageNormal); } } public int getDirection() { return direction; } }
ให้ดูที่ตัวหนังสือสีแดงชุดแรกก่อนจะเห็นว่าประกาศตัวแปรคงที่ไว้
เป็นแบบ Integer ที่ชื่อตัวแปรเป็นทิศทางต่างๆ โดยเก็บตัวเลขต่างกัน
เดี๋ยวไว้ดูตอนใช้งานก็จะเข้าใจเองว่าทำไมต้องทำแบบนี้

ต่อมาคือฟังก์ชัน onTouch ไม่ใช่ฟังก์ชันของ Listener โดยตรงนะครับ
แต่เป็นฟังก์ชันที่เจ้าของบล็อกสร้างมาเองเพื่อรับช่วงต่ออีกทีนึง
ไว้เดี๋ยวตอนใช้งานก็จะเห็นภาพเอง ตอนนี้ให้จำสองส่วนนี้ไว้ก่อน


เวลาใช้งานก็ง่ายมากๆ อย่างแรกเลยคือ สร้าง Linear Layout
ที่จะทำเป็น D-Pad Controller ก่อน สมมติว่าสร้างดังนี้


main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="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" > <LinearLayout android:id="@+id/layoutDPad" android:layout_width="200dp" android:layout_height="200dp" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:background="@drawable/dpad_normal" android:orientation="vertical" > </LinearLayout> <TextView android:id="@+id/tvDirection" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:textSize="25sp" /> </RelativeLayout>

จะเห็นว่ามี Text View อยู่กลางจอด้วย ไม่ได้ซ่อนไว้นะ
แต่ว่ายังไม่ได้กำหนดข้อความลงไป เลยมองไม่เห็น
ซึ่ง Text View ตัวนี้เจ้าของบล็อกจะใช้แสดงว่ากดปุ่มใด
ก็จะมี Up, Down, Left และ Right แต่ในตอนนี้ยังไม่ได้กด
ก็เลยไม่ได้แสดง ไว้กดเมื่อไรก็ค่อยกำหนดให้แสดง
และเมื่อไม่ได้กดหรือออกนอกขอบเขตของปุ่มก็ไม่แสดง

ส่วน Linear Layout วางไว้ที่มุมซ้ายล่างมีขนาด 200 x 200dp
โดยใส่ภาพเริ่มต้นก่อนเป็นภาพปุ่มธรรมดาแบบยังไม่ถูกกด


ทีนี้มาดูการใช้งานคลาส DPadController กันดีกว่า ซึ่งใช้ไม่ยาก
ก่อนอื่นก็พูดถึงการประกาศตัวแปรก่อน ประกาศง่ายๆตามนี้
DPadController dpc = new DPadController(ภาพปกติ , ภาพปุ่มขึ้นถูกกด, ภาพปุ่มลงถูกกด , ภาพปุ่มซ้ายถูกกด, ภาพปุ่มขวาถูกกด);

ที่ต้องกำหนดตอนกำหนดค่าให้กับคลาสนี้ก็คือภาพในกรณีต่างๆ
สำหรับค่าที่กำหนดลงไปก็คือ Resource ID หรือก็คือ R.drawable.xxx
DPadController dpc = new DPadController(R.drawable.dpad_normal , R.drawable.dpad_up, R.drawable.dpad_down , R.drawable.dpad_left, R.drawable.dpad_right);

เวลาใช้งานก็สร้างคลาส Linear Layout ตามปกติเลย
LinearLayout layoutDPad; layoutDPad = (LinearLayout)findViewById(R.id.layoutDPad); layoutDPad.setOnTouchListener(new OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { return true; } });


เวลาใช้งานคลาส DPadController ก็ใช้คำสั่งข้างใน onTouch ดังนี้
LinearLayout layoutDPad; layoutDPad = (LinearLayout)findViewById(R.id.layoutDPad); layoutDPad.setOnTouchListener(new OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { dpc.onTouch(v, event); int direction = dpc.getDirection(); return true; } });
เจ้าของบล็อกใช้วิธีรับค่าจาก v และ event ไปคำนวณในฟังก์ชันอีกที
จำตอนที่เจ้าของบล็อกบอกให้จำตัวหนังสือสีแดงสองชุดได้มั้ย
นั่นคือที่มาของฟังก์ชัน onTouch ในคลาส DPadController

แล้วใช้คำสั่ง getDirection ก็จะบอกออกมาเป็นตัวเลขว่าทิศใด
ตัวอักษรชุดแรกนั่นแหละที่เป็นตัวแปร Integer ให้เก็บตัวเลขไว้นั่นเอง
if(direction == DPadController.D_NONE) // ผู้ใช้ไม่ได้กดปุ่ม else if(direction == DPadController.D_UP) // ผู้ใช้กดปุ่มขึ้น else if(direction == DPadController.D_DOWN) // ผู้ใช้กดปุ่มลง else if(direction == DPadController.D_RIGHT) // ผู้ใช้กดปุ่มขวา else if(direction == DPadController.D_LEFT) // ผู้ใช้กดปุ่มซ้าย

ดังนั้น เมื่อสรุปคำสั่งในการใช้งานก็จะได้ออกมาดังนี้
โดยจะให้แสดงข้อความบอกที่ Text View ว่ากดปุ่มอะไร


Main.java
package app.akexorcist.dpadcontroller; import android.os.Bundle; import android.app.Activity; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.widget.LinearLayout; import android.widget.TextView; public class Main extends Activity { LinearLayout layoutDPad; TextView tvDirection; DPadController dpc; int direction = DPadController.D_NONE; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); dpc = new DPadController(R.drawable.dpad_normal , R.drawable.dpad_up, R.drawable.dpad_down , R.drawable.dpad_left, R.drawable.dpad_right); tvDirection = (TextView)findViewById(R.id.tvDirection); layoutDPad = (LinearLayout)findViewById(R.id.layoutDPad); layoutDPad.setOnTouchListener(new OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { dpc.onTouch(v, event); direction = dpc.getDirection(); if(direction == DPadController.D_NONE) tvDirection.setText(""); else if(direction == DPadController.D_UP) tvDirection.setText("Up"); else if(direction == DPadController.D_DOWN) tvDirection.setText("Down"); else if(direction == DPadController.D_RIGHT) tvDirection.setText("Right"); else if(direction == DPadController.D_LEFT) tvDirection.setText("Left"); return true; } }); } }


AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.akexorcist.dpadcontroller" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="app.akexorcist.dpadcontroller.Main" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>


ตอนทดสอบก็ให้ลองกดที่ D-Pad ดูจะเห็นว่าเมื่อกดปุ่มใดๆแล้ว
ก็จะเป็นภาพปุ่มทิศนั้นถูกกดพร้อมกับมีข้อความแสดงกลางจอด้วย


จะเห็นว่าภาพที่แสดงทิศทางการกด แท้ที่จริงแล้ว
ใช้วิธีเช็คด้วยโค๊ดแล้วใช้คำสั่งเปลี่ยนภาพพื้นหลังเลย
ข้อดีคือผู้ใช้สามารถลากนิ้วข้ามไปยังอีกปุ่มได้เลย
ต่างกับการใช้ Button 4 อัน เพราะต้องยกนิ้วแล้วไปกดแทน

สำหรับผู้ที่หลงเข้ามาอ่านที่ต้องการไฟล์ตัวอย่าง
สามารถดาวน์โหลดได้ที่ DPadController [Google Drive]




เหล่าพันธมิตรแอนดรอยด์

Devahoy Layer Net NuuNeoI The Cheese Factory Somkiat CC Mart Routine Artit-K Arnondora Kamonway Try to be android developer Oatrice Benz Nest Studios Kotchaphan@Medium Jirawatee@Medium Travispea@Medium