28 สิงหาคม 2556

[Android Code] ทำตัวเลือกสำหรับเมนู [Menu Cursor]


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


หรือก็คืออยากทำแบบเกม Zenonia นั่นเอง

อย่างแรกเลยที่สำคัญที่สุดคือ ผู้ที่หลงเข้ามาอ่านไม่รู้หรอก
ว่าในภาพเค้าใช้วิธีอะไร แต่จงนึกวิธีที่ใกล้เคียงที่สุดที่ใช้ได้

ของพวกนี้ล้วนเกิดมาจากอุปกรณ์พื้นฐานทั้งนั้นนั่นแหละ
ถ้าเป็นเจ้าของบล็อกก็จะนึกถึง Radio Button เป็นอย่างแรกเลย
ถ้าเอา Radio Group มาใช้ก็ยิ่งสบายเลย (ถ้าไม่รู้ก็ไปศึกษาเอง)
ส่วนปุ่มเลื่อนขึ้นลง ตอนนี้ขอใช้แค่ Button ธรรมดาๆไปก่อน 
เพื่อที่ว่าบทความนี้จะได้ไม่งงกับโค๊ดอื่นๆที่ไม่เกี่ยวข้องมากนัก

ทีนี้เจ้าของบล็อกก็ลองใช้  Radio Group กับ Button วางเลียนแบบก่อน


ตกแต่งไว้ทีหลัง มาสนใจเรื่องหลักการทำงานกันก่อน

หลักการก็คือจะให้เก็บลำดับของแต่ละเมนูที่มีทั้งหมดสามเมนูไว้
โดยจะให้แถวแรกคือ 0 แถวสองคือ 1 และแถวสามคือ 2 (อิงตามอาร์เรย์)
และจะสร้างตัวแปร Integer ไว้ตัวหนึ่ง เพื่อเก็บสถานะปัจจุบันว่าเลือกแถวไหน

ให้มองเมนูแต่ละตัวเป็น Radio Button โดยจะรวมกันอยู่ในรูปอาร์เรย์
ทำไมต้องอาร์เรย์? เพื่อให้สามารถระบุ Radio Button ทั้งสามตัวด้วยตัวเลขได้
 อย่างเช่น radio_button[0], radio_button[1] และ radio_button[2] นั่นเอง
ถึงจุดนี้แล้วน่าจะเข้าใจกันแล้วว่าจะใช้ current_cursor เพื่อระบุ Radio Button 
สมมติว่า current_cursor เป็น 2 ก็คือ กำลังเลือกที่ Option นั่นเอง
ก็จะไปกำหนดให้ radio_button[current_cursor] ใช้คำสั่ง setChecked(true)
สำหรับ Radio Button ตัวที่เหลือไม่จำเป็นต้องใช้คำสั่ง setChecked(false)
เพราะอย่าลืมว่าทั้ง 3 Radio Button อยู่ใน Radio Group อยู่แล้วตั้งแต่แรก
ดังนั้นเมื่อมี Radio Button ตัวไหนถูกเลือก ตัวอื่นๆก็จะไม่ถูกเลือกโดยทันที
int[] rdo_id = new int[] { R.id.rdoNewGame , R.id.rdoLoadGame, R.id.rdoOption }; RadioButton[] arr_rdo = new RadioButton[3]; for(int i = 0 ; i < rdo_id.length ; i++) arr_rdo[i] = (RadioButton)findViewById(rdo_id[i]);

วิธีประกาศ Radio Button จะใช้ For เข้ามาวนลูปในการประกาศแทน
(เหมาะสำหรับคลาสที่ประกาศเยอะและใช้งานเหมือนๆกัน)


แล้วทำปุ่มกดให้เลื่อนเคอร์เซอร์ขึ้นลงได้อย่างไรล่ะ?
ถ้าถึงตรงนี้แล้วยังถามคำถามนี้อยู่ แปลว่ายังไม่สามารถประยุกต์ได้นะ
แต่ถ้ามองออกแล้วก็แปลว่าผู้ที่หลงเข้ามาอ่านประยุกต์ต่อไปได้แล้วล่ะ


เห็นแบบนี้คงเข้าใจได้ไม่ยาก ว่าจะเลื่อนเคอร์เซอร์กันยังไง
แต่อาจจะสงสัยว่าทำไมปุ่ม Up ถึงเป็นลบ แล้ว Down เป็นบวก
ให้นึกภาพตามนะ คือ Radio Button เรียงกันเป็นแนวตั้ง
โดยที่ตัวแรกอยู่บนสุด และตัวสุดท้ายอยู่ล่างสุด
ดังนั้นเลขจากมากไปหาน้อยก็จะไล่จากบนลงล่าง
ดังนั้นถ้าอยากให้เคอร์เซอร์เลื่อนมาข้างล่างก็ต้องบวก
และถ้าอยากให้เลื่อนขึ้นข้างบนก็ต้องลบนั่นเอง
และต้องใช้ If เช็คด้วยว่าค่าเกินจำนวน Radio Button ที่มีมั้ย
ถ้าเกินหรือน้อยกว่า 0 ก็ให้วนลูปนั่นเอง ด้วยการกำหนดค่าลงไป
เมื่อกำหนดค่าให้กับ current_sursor เสร็จเรียบร้อยแล้ว
ก็๋เอาไประบุ Radio Button ที่ต้องการให้เคอร์เซอร์เลือก

ดังนั้นคำสั่งของ Button ทั้งสองตัวนี้ก็จะเป็นดังนี้
(ปุ่มซ้ายและขวาไม่นับแค่เอามาเป็นองค์ประกอบเฉยๆ)

Button btnUp = (Button)findViewById(R.id.btnUp); btnUp.setOnClickListener(new OnClickListener() { public void onClick(View v) { current_cursor--; if(current_cursor < 0) current_cursor = rdo_id.length - 1; arr_rdo[current_cursor].setChecked(true); } }); Button btnDown = (Button)findViewById(R.id.btnDown); btnDown.setOnClickListener(new OnClickListener() { public void onClick(View v) { current_cursor++; if(current_cursor >= rdo_id.length) current_cursor = 0; arr_rdo[current_cursor].setChecked(true); } });

ทั้งหมดนี้ก็เป็นอันเสร็จเรียบร้อยสำหรับการใช้ Button เลื่อนเคอร์เซอร์
แต่อย่าลืมอีกอย่างหนึ่ง ก็คือ Radio Button ผู้ใช้สามารถกดเลือกโดยตรงได้
ดังนั้นก็ให้เขียนโค๊ดรองรับเผื่อไว้ด้วย ว่าผู้ใช้กดเลือก Radio Button อันที่เท่าไร
แล้วเอาไปอัพเดทค่าให้กับ current_cursor ดังนั้นคำสั่งก็จะเป็น
int[] rdo_id = new int[] { R.id.rdoNewGame , R.id.rdoLoadGame, R.id.rdoOption }; RadioGroup rdoGroup = (RadioGroup)findViewById(R.id.rdoGroup); rdoGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() { public void onCheckedChanged(RadioGroup group, int checkedId) { for(int i = 0 ; i < rdo_id.length ; i++) { if(rdo_id[i] == checkedId) current_cursor = i; } } });
จะเห็นว่าคำสั่งแปลกตาเล็กน้อย ก็คือจะเช็คว่า Radio Button 
ที่ผู้ใช้กดเลือกเป็นลำดับที่เท่าไร เพราะอย่าลืมว่า onCheckChanged
ไม่ได้ส่งค่ามาบอกโดยตรงว่าเลือกตัวที่ 0 หรือ 1 หรือ 2
แต่จะส่งมาเป็น Id ของตัวที่กด (ในตัวแปร checkedId) แทน
ซึ่งก็คือ R.id.rdoNewGame, R.id.rdoLoadGame หรือ R.id.rdoOption
ดังนั้นก็ต้องใช้ For วนลูปเพื่อเช็คว่าเป็น Radio Button ตัวไหน
เมื่อรู้แล้วก็ไปกำหนด current_cursor ว่าเลือกตัวที่เท่าไร
สมมติเลือกที่ R.id.rdoNewGame ก็จะได้เป็น 0 นั่นเอง

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


แต่จะเปลี่ยนโดยตรงก็ทำไม่ได้ เพราะต้องมีกรณีที่ว่า
เวลาเลือกไม่ได้เลือก Radio Button ตัวใด เคอร์เซอร์ที่
Radio Button ตัวนั้นก็จะต้องไม่แสดงให้เห็นด้วย
ซึ่งจะมานั่งเขียนโปรแกรมคอยเช็คก็ดูลำบากไปหน่อย
จะใช้วิธีเดียวกับที่ใช้ทำ Custom Button แทน


โดยจะกำหนดว่าถ้า Radio Button ไม่ถูกเลือก จะใช้ภาพเคอร์เซอร์
เป็นภาพว่างเปล่าที่ไม่มีอะไร (ในขนาดภาพที่เท่ากัน) มาแสดง
และถ้า Radio Button ถูกเลือก ก็จะแสดงภาพเคอร์เซอร์แทน

<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android" > <item android:state_checked="true" android:drawable="@drawable/cursor_check" /> <item android:state_checked="false" android:drawable="@drawable/cursor_uncheck" /> </selector>


เมื่อเอาไปกำหนดให้กับ Radio Button ก็จะได้เป็นแบบนี้


ทีนี้ Button ก็เปลี่ยนบ้าง โดยใช้วิธี Custom Button นั่นแหละ

<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android" > <item android:state_pressed="true" android:drawable="@drawable/button_up_pressed" /> <item android:state_pressed="false" android:drawable="@drawable/button_up_normal" /> </selector>

ทำแบบนี้จนครบทั้ง 4 ปุ่ม แล้วเอาไปกำหนดแทน Button เดิม


เท่านี้ก็เรียบร้อยละ ทีนี้ก็มาสรุปรวมทั้งหมดกันเถอะ

res/drawable/selector_button_down.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android" > <item android:state_pressed="true" android:drawable="@drawable/button_down_pressed" /> <item android:state_pressed="false" android:drawable="@drawable/button_down_normal" /> </selector>

res/drawable/selector_button_left.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android" > <item android:state_pressed="true" android:drawable="@drawable/button_left_pressed" /> <item android:state_pressed="false" android:drawable="@drawable/button_left_normal" /> </selector>

res/drawable/selector_button_right.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android" > <item android:state_pressed="true" android:drawable="@drawable/button_right_pressed" /> <item android:state_pressed="false" android:drawable="@drawable/button_right_normal" /> </selector>

res/drawable/selector_button_up.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android" > <item android:state_pressed="true" android:drawable="@drawable/button_up_pressed" /> <item android:state_pressed="false" android:drawable="@drawable/button_up_normal" /> </selector>

res/drawable/selector_cursor.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android" > <item android:state_checked="true" android:drawable="@drawable/cursor_check" /> <item android:state_checked="false" android:drawable="@drawable/cursor_uncheck" /> </selector>



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" > <RadioGroup android:id="@+id/rdoGroup" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" > <RadioButton android:id="@+id/rdoNewGame" android:layout_width="wrap_content" android:layout_height="wrap_content" android:button="@drawable/selector_cursor" android:checked="true" android:padding="5dp" android:text="New Game" /> <RadioButton android:id="@+id/rdoLoadGame" android:layout_width="wrap_content" android:layout_height="wrap_content" android:button="@drawable/selector_cursor" android:padding="5dp" android:text="Load Game" /> <RadioButton android:id="@+id/rdoOption" android:layout_width="wrap_content" android:layout_height="wrap_content" android:button="@drawable/selector_cursor" android:padding="5dp" android:text="Option" /> </RadioGroup> <LinearLayout android:id="@+id/layoutController" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:orientation="vertical" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" > <Button android:id="@+id/btnUp" android:layout_width="50dp" android:layout_height="50dp" android:background="@drawable/selector_button_up" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" > <Button android:id="@+id/btnLeft" android:layout_width="50dp" android:layout_height="50dp" android:background="@drawable/selector_button_left" /> <LinearLayout android:layout_width="50dp" android:layout_height="50dp" android:orientation="vertical" > </LinearLayout> <Button android:id="@+id/btnRight" android:layout_width="50dp" android:layout_height="50dp" android:background="@drawable/selector_button_right" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" > <Button android:id="@+id/btnDown" android:layout_width="50dp" android:layout_height="50dp" android:background="@drawable/selector_button_down" /> </LinearLayout> </LinearLayout> </RelativeLayout>


Main.java
package app.akexorcist.menucursor; import android.os.Bundle; import android.app.Activity; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.RadioGroup.OnCheckedChangeListener; public class Main extends Activity { RadioButton[] arr_rdo = new RadioButton[3]; int current_cursor = 0; int[] rdo_id = new int[] { R.id.rdoNewGame , R.id.rdoLoadGame, R.id.rdoOption }; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); for(int i = 0 ; i < rdo_id.length ; i++) arr_rdo[i] = (RadioButton)findViewById(rdo_id[i]); RadioGroup rdoGroup = (RadioGroup)findViewById(R.id.rdoGroup); rdoGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() { public void onCheckedChanged(RadioGroup group, int checkedId) { for(int i = 0 ; i < rdo_id.length ; i++) { if(rdo_id[i] == checkedId) current_cursor = i; } } }); Button btnUp = (Button)findViewById(R.id.btnUp); btnUp.setOnClickListener(new OnClickListener() { public void onClick(View v) { current_cursor--; if(current_cursor < 0) current_cursor = rdo_id.length - 1; arr_rdo[current_cursor].setChecked(true); } }); Button btnDown = (Button)findViewById(R.id.btnDown); btnDown.setOnClickListener(new OnClickListener() { public void onClick(View v) { current_cursor++; if(current_cursor >= rdo_id.length) current_cursor = 0; arr_rdo[current_cursor].setChecked(true); } }); } }


AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.akexorcist.menucursor" 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.menucursor.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>


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



สำหรับผู้ที่หลงเข้ามาอ่านคนใดที่ต้องการไฟล์ตัวอย่าง
สามารถดาวน์โหลดได้จาก Menu Cursor [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