30 October 2012

ลองสร้าง JoyStick Controller ไว้ใช้ในแอป

Updated on

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


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

        ทีนี้ก็มาดูที่ตัวคำสั่งกันเลยดีกว่าว่ามีอะไรบ้าง


JoyStickClass.java
package app.akexorcist.joystickcontroller;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;

public class JoyStickClass {
    public static final int STICK_NONE = 0;
    public static final int STICK_UP = 1;
    public static final int STICK_UPRIGHT = 2;
    public static final int STICK_RIGHT = 3;
    public static final int STICK_DOWNRIGHT = 4;
    public static final int STICK_DOWN = 5;
    public static final int STICK_DOWNLEFT = 6;
    public static final int STICK_LEFT = 7;
    public static final int STICK_UPLEFT = 8; 
    
    private int STICK_ALPHA = 200;
    private int LAYOUT_ALPHA = 200;
    private int OFFSET = 0;
    
    private Context mContext;
    private ViewGroup mLayout;
    private LayoutParams params;
    private int stick_width, stick_height;
    
    private int position_x = 0, position_y = 0, min_distance = 0;
    private float distance = 0, angle = 0;
    
    private DrawCanvas draw;
    private Paint paint;
    private Bitmap stick;
    
    private boolean touch_state = false;
    
    public JoyStickClass (Context context, ViewGroup layout, int stick_res_id) {
        mContext = context;

        stick = BitmapFactory.decodeResource(mContext.getResources(), stick_res_id);
        
        stick_width = stick.getWidth();
        stick_height = stick.getHeight();
        
        draw = new DrawCanvas(mContext);
        paint = new Paint();
        mLayout = layout;
        params = mLayout.getLayoutParams();
    }
    
    public void drawStick(MotionEvent arg1) {
        position_x = (int) (arg1.getX() - (params.width / 2));
        position_y = (int) (arg1.getY() - (params.height / 2));
        distance = (float) Math.sqrt(Math.pow(position_x, 2) + Math.pow(position_y, 2));
        angle = (float) cal_angle(position_x, position_y);
        
        
        if(arg1.getAction() == MotionEvent.ACTION_DOWN) {
            if(distance <= (params.width / 2) - OFFSET) {
                draw.position(arg1.getX(), arg1.getY());
                draw();
                touch_state = true;
            }
        } else if(arg1.getAction() == MotionEvent.ACTION_MOVE && touch_state) {
            if(distance <= (params.width / 2) - OFFSET) {
                draw.position(arg1.getX(), arg1.getY());
                draw();
            } else if(distance > (params.width / 2) - OFFSET){
                float x = (float) (Math.cos(Math.toRadians(cal_angle(position_x, position_y))) 
                        * ((params.width / 2) - OFFSET));
                float y = (float) (Math.sin(Math.toRadians(cal_angle(position_x, position_y))) 
                        * ((params.height / 2) - OFFSET));
                x += (params.width / 2);
                y += (params.height / 2);
                draw.position(x, y);
                draw();
            } else {
                mLayout.removeView(draw);
            }
        } else if(arg1.getAction() == MotionEvent.ACTION_UP) {
            mLayout.removeView(draw);
            touch_state = false;
        }
    }
    
    public int[] getPosition() {
        if(distance > min_distance && touch_state) {
            return new int[] { position_x, position_y };
        }
        return new int[] { 0, 0 };
    }
    
    public int getX() {
        if(distance > min_distance && touch_state) {
            return position_x;
        }
        return 0;
    }
    
    public int getY() {
        if(distance > min_distance && touch_state) {
            return position_y;
        }
        return 0;
    }
    
    public float getAngle() {
        if(distance > min_distance && touch_state) {
            return angle;
        }
        return 0;
    }
    
    public float getDistance() {
        if(distance > min_distance && touch_state) {
            return distance;
        }
        return 0;
    }
    
    public void setMinimumDistance(int minDistance) {
        min_distance = minDistance;
    }
    
    public int getMinimumDistance() {
        return min_distance;
    }
    
    public int get8Direction() {
        if(distance > min_distance && touch_state) {
            if(angle >= 247.5 && angle < 292.5 ) {
                return STICK_UP;
            } else if(angle >= 292.5 && angle < 337.5 ) {
                return STICK_UPRIGHT;
            } else if(angle >= 337.5 || angle < 22.5 ) {
                return STICK_RIGHT;
            } else if(angle >= 22.5 && angle < 67.5 ) {
                return STICK_DOWNRIGHT;
            } else if(angle >= 67.5 && angle < 112.5 ) {
                return STICK_DOWN;
            } else if(angle >= 112.5 && angle < 157.5 ) {
                return STICK_DOWNLEFT;
            } else if(angle >= 157.5 && angle < 202.5 ) {
                return STICK_LEFT;
            } else if(angle >= 202.5 && angle < 247.5 ) {
                return STICK_UPLEFT;
            }
        } else if(distance <= min_distance && touch_state) {
            return STICK_NONE;
        }
        return 0;
    }
    
    public int get4Direction() {
        if(distance > min_distance && touch_state) {
            if(angle >= 225 && angle < 315 ) {
                return STICK_UP;
            } else if(angle >= 315 || angle < 45 ) {
                return STICK_RIGHT;
            } else if(angle >= 45 && angle < 135 ) {
                return STICK_DOWN;
            } else if(angle >= 135 && angle < 225 ) {
                return STICK_LEFT;
            }
        } else if(distance <= min_distance && touch_state) {
            return STICK_NONE;
        }
        return 0;
    }
    
    public void setOffset(int offset) {
        OFFSET = offset;
    }
    
    public int getOffset() {
        return OFFSET;
    }
    
    public void setStickAlpha(int alpha) {
        STICK_ALPHA = alpha;
        paint.setAlpha(alpha);
    }
    
    public int getStickAlpha() {
        return STICK_ALPHA;
    }
    
    public void setLayoutAlpha(int alpha) {
        LAYOUT_ALPHA = alpha;
        mLayout.getBackground().setAlpha(alpha);
    }
    
    public int getLayoutAlpha() {
        return LAYOUT_ALPHA;
    }
    
    public void setStickSize(int width, int height) {
        stick = Bitmap.createScaledBitmap(stick, width, height, false);
        stick_width = stick.getWidth();
        stick_height = stick.getHeight();
    }
    
    public void setStickWidth(int width) {
        stick = Bitmap.createScaledBitmap(stick, width, stick_height, false);
        stick_width = stick.getWidth();
    }
    
    public void setStickHeight(int height) {
        stick = Bitmap.createScaledBitmap(stick, stick_width, height, false);
        stick_height = stick.getHeight();
    }
    
    public int getStickWidth() {
        return stick_width;
    }
    
    public int getStickHeight() {
        return stick_height;
    }
    
    public void setLayoutSize(int width, int height) {
        params.width = width;
        params.height = height;
    }

    public int getLayoutWidth() {
        return params.width;
    }

    public int getLayoutHeight() {
        return params.height;
    }
    
    private double cal_angle(float x, float y) {
        if(x >= 0 && y >= 0)
            return Math.toDegrees(Math.atan(y / x));
        else if(x < 0 && y >= 0)
            return Math.toDegrees(Math.atan(y / x)) + 180;
        else if(x < 0 && y < 0)
            return Math.toDegrees(Math.atan(y / x)) + 180;
        else if(x >= 0 && y < 0) 
            return Math.toDegrees(Math.atan(y / x)) + 360;
        return 0;
    }
     
    private void draw() {
        try {
            mLayout.removeView(draw);
        } catch (Exception e) { }
        mLayout.addView(draw);
    }
     
    private class DrawCanvas extends View{
         float x, y;
         
         private DrawCanvas(Context mContext) {
             super(mContext);
         }
         
         public void onDraw(Canvas canvas) {
             canvas.drawBitmap(stick, x, y, paint);
         }
         
         private void position(float pos_x, float pos_y) {
             x = pos_x - (stick_width / 2);
             y = pos_y - (stick_height / 2);
         }
     }
}


        สำหรับคลาส JoyStickClass นี้ จะรับค่าเข้ามาด้วยกันอยู่ 2 ตัวคือ ViewGroup layout อันนี้คือ Layout ใดๆก็ตามที่ต้องการทำเป็นพื้นที่สำหรับ JoyStick โดยที่ Layout นั้นๆให้กำหนดภาพพื้นหลังเป็นภาพพื้นที่ของ JoyStick และอีกค่าคือ int stick_res_id เป็นภาพที่จะนำมาใช้เป็นคันโยกนั่นเอง

        เมื่อสร้างคลาสนี้ขึ้นมา ก็จะทำการแปลงภาพที่ใช้เป็นคันโยกที่เดิมเป็นแบบ Drawable มาแปลงเป็น Bitmap ก่อน เพราะการเคลื่อนที่ของคันโยกเจ้าของบล้อกจะใช้วิธีการวาด Canvas จากนั้นก็เก็บค่าความกว้างและความสูงของภาพไว้ (จริงๆเก็บแค่อย่างใดอย่างหนึ่งก็พอ เพราะภาพวงกลม ยังไงก็เท่ากันอยู่แล้ว)

        แล้วจึงทำการสร้างคลาส DrawCanvas อันนี้เป็นคลาสที่เจ้าของบล็๋อกสร้างขึ้นมาในคลาสนี้อีกทีหนึ่ง เอาไว้วาด Canvas นั่นแหละ และจะเห็นว่ามีการสร้าง LayoutParams ด้วย อันนี้เอาไว้วัดขนาดของ Layout

        ทีนี้มาดูที่ฟังก์ชัน drawStick กันต่อ อันนี้เอาไว้วาดคันโยกด้วย Canvas นั่นเอง โดยจะมีการรับค่า MotionEvent เข้ามา ซึ่งฟังก์ชันนี้เจ้าของบล็อกจะให้ใช้กับ onTouchListener นั่นเอง สำหรับ onTouchListener จะให้ผู้ใช้สร้างเองตอนที่เรียกใช้ เมื่อเข้ามาในฟังก์ชันนี้ ก็จะทำการอ่านพิกัดที่สัมผัส position_x และ position_y อ้างอิงที่ตรงกลาง Layout คือพิกัด (0, 0) และคำนวณ distance ว่าจุดที่สัมผัสห่างจากพิกัด (0, 0) เท่าไร ใช้วิธีคำนวณด้วยตรีโกณมิติพื้นฐานน่ะแหละ

        แล้วก็ทำการคำนวณ angle ออกมาว่าจุดที่สัมผัสทำมุมเท่าไรกับจุด (0, 0) จากนั้นก็จะเข้าสู้เงื่อนไข ACTION_DOWN ซึ่งคือเมื่อผู้ใช้แตะลงบนพื้นที่ก็จะทำการเช็คอีกทีว่า ระยะที่สัมผัส อยู่ในช่วงที่ต้องการหรือป่าว เพราะอย่าลืมว่าภาพเป็นสี่เหลียม แต่พื้นที่ที่เป็นจอยสติ๊กเป็นวงกลม

        เจ้าของบล็อกจึงคำนวณว่า ถ้า distance ที่ได้เกินระยะของขอบเขตของจอยสติ๊กก็จะไม่ถือว่าเป็นการแสดงคันโยกแต่อย่างใด แต่ถ้าอยู่ขอบเขตจอยสติ๊กก็จะทำการวาดคันโยกด้วย Canvas มาแสดงที่ตำแหน่งที่สัมผัส (ฟังก์ชันของ canvas จะกล่าวในภายหลัง) แล้วก็มีการใช้ตัวแปร touch_state เพื่อใช้ตรวจสอบว่าเริ่มทำการแสดงคันโยกแล้วหรือยัง

        ทีนี้ก็มาดูเงื่อนไขของ ACTION_MOVE ต่อ ซึ่งเป็นเงื่อนไขที่ผู้ใช้แตะหน้าจอแล้วลากนิ้วไปมาบนจอ (หรือ Drag นั่นเอง) ก็จะมีการเช็คตัวแปร touch_state ก่อนด้วยว่าเริ่มแสดงคันโยกแล้วหรือยัง จากนั้นก็จะมีการเช็คด้วยเงื่อนไขสองเงื่อนไขที่ว่า

        distance <= (params.width / 2) - OFFSET

        อันนี้คือกรณีที่ลากนิ้วไปมาในพื้นที่ที่เป็นจอยสติ๊กก็จะให้ทำการวาดคันโยกด้วย Canvas บนตำแหน่งที่สัมผัสเหมือนเดิม 

        แต่ถ้า

        distance > (params.width / 2) - OFFSET

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

        งงล่ะสิ ว่าแล้วเจ้าของบล็อกก็เลยทำภาพประกอบมาช่วยอธิบาย

        จากภาพจะเห็นว่าจุดที่นิ้วสัมผัส (สีฟ้า) อยู่ในพื้นที่ของจอยสติ๊ก (สีเหลือง) ก็จะให้แสดงภาพคันโยก (สีส้ม) ตามตำแหน่งที่สัมผัสตามปกติ


        ส่วนสองภาพบนจะเห็นว่าถ้าลากนิ้วออกนอกพื้นที่จอยสติ๊ก ภาพคันโยกก็ยังแสดงอยู่ เพียงแต่ว่าจะอยู่สุดขอบของพื้นที่เท่านั้น

        และในกรณีที่นอกเหนือจากนี้ก็จะให้ลบภาพคันโยกออกด้วยคำสั่ง

        mLayout.removeView(draw);

        และสำหรับกรณี ACTION_UP คือผู้ใช้ปล่อยนิ้วออกจากหน้าจอก็จะให้ลบภาพคันโยกออก แล้วกำหนดตัวแปร touch_state เป็น false เพื่อรอการสัมผัสจอยสติ๊กในครั้งต่อไป

        ฟังก์ชัน getPosition เอาไว้ให้อ่านค่าตำแหน่งที่สัมผัส โดยอ้างอิงที่ตรงกลางพื้นที่จอยสติ๊กคือตำแหน่ง (0, 0) ฟังก์ชันนี้จะ Return ค่าเป็น Integer Array สองช่อง ช่องแรกคือพิกัดแกน X และช่องที่สองคือพิกัดแกน Y

        ฟังก์ชัน getX เอาไว้อ่านค่าตำแหน่งในแกน X และฟังก์ชัน getY ก็เอาไว้อ่านค่าตำแหน่งในแกน Y น่ะแหละ สองฟังก์ชันนี้ทำมาเผื่อใครต้องการอ่านเพียงค่าใดค่าหนึ่ง

        ฟังก์ชัน getAngle เอาไว้อ่านมุมของจุดสัมผัสว่าทำมุมกับพิกัด (0, 0) เป็นมุมกี่องศา


        ฟังก์ชัน getDistance เอาไว้คำนวณว่าจุดที่สัมผัสห่างจากกึ่งกลางของพื้นที่จอยสติ๊กหรือพิกัด (0, 0) เป็นระยะทางเท่าไร

        ฟังก์ชัน setMinimumDistance คือกำหนดระยะที่สั้นที่สุดที่่จะคำนวณ อันนี้เอาไว้แก้ปัญหาเวลาที่เวลานิ้วผู้ใช้เลื่อนไปมาตรงกลางจะทำให้ทิศทางเปลี่ยนไปมาอย่างรวดเร็ว จึงมีการกำหนดว่า ถ้าจุดที่สัมผัสอยู่ใกล้กับพิกัด (0, 0) มากเกินไป จะไม่มีการรับค่า


        ฟังก์ชัน getMinimumDistance ก็เอาไว้อ่านค่าระยะที่ว่านั่นแหละ

        สำหรับฟังก์ชัน get8Direction เอาไว้คำนวณออกมาว่า จุดที่สัมผัสอยู่ฝั่งไหนของพิกัด (0, 0) หรือก็คือผู้ใช้โยกคันโยกไปทิศทางไหน โดยฟังก์ชันนี้ก็จะเป็นแบบ 8 ทิศทาง โดยจะส่งค่าออกมาเป็น Integer แต่เจ้าของบล็อกประกาศเป็นค่าคงที่ไว้แล้ว ทั้งหมด 9 ตัว มีดังนี้

        STICK_NONE จุดสัมผัสอยู่ในขอบเขต Minimum Distance

        STICK_UP จุดสัมผัสอยู่ด้านบนของพิกัด (0, 0) หรือพูดภาษาคนปกติว่า กดปุ่มขึ้นหรือโยกคันโยกขึ้นนั่นแหละ

        STICK_DOWN กดปุ่มลงหรือโยกคันโยกลง

        STICK_LEFT กดปุ่มซ้ายหรือโยกคันโยกไปทางซ้าย

        STICK_RIGHT กดปุ่มขวาหรือโยกคันโยกไปทางขวา

        STICK_UPRIGHT กดปุ่มเฉียงขวาขึ้นหรือโยกคันโยกเฉียงขวาขึ้น

        STICK_UPLEFT กดปุ่มเฉียงซ้ายขึ้นหรือโยกคันโยกเฉียงซ้ายขึ้น

        STICK_DOWNRIGHT กดปุ่มเฉียงขวาลงหรือโยกคันโยกเฉียงขวาลง

        STICK_DOWNLEFT กดปุ่มเฉียงซ้ายลงหรือโยกคันโยกเฉียงซ้ายลง


        ฟังก์ชัน get4Direction ก็เหมือนกับ get8Direction แหละ แต่ว่าอันนี้จะได้ค่าออกมาแค่ 5 ทิศทาง คือ STICK_NONE, STICK_UP, STICK_DOWN, STICK_LEFT และ STICK_RIGHT


        ฟังก์ชัน setOffset เอาไว้กำหนดค่าตัวแปร OFFSET ที่เจ้าของบล็อกบอกว่าจะอธิบายทีหลังน่ะ ก็คือระยะระหว่างขอบพื้นที่จอยสติ๊กกับขอบภาพจริงๆ

        เพราะว่าภาพที่ใช้ เจ้าของบล็อกขอให้ใช้วงกลมที่เล็กกว่าขอบภาพจริง ถ้าใช้ภาพที่วงกลมชิดขอบจะทำให้เวลาคันโยกชิดขอบ จะแหว่งไปที่ภาพคันโยกแหว่งก็เพราะว่ามันเลยขอบเขตของ Layout เจ้าของบล็อกจึงมีการกำหนด Offset ด้วยเพื่อแก้ปัญหานี้นั่นเอง



        ฟังก์ชัน getOffset เอาไว้อ่านค่า OFFSET

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

        ฟังก์ชัน getStickAlpha ก็เอาไว้อ่านค่าความโปร่งใสของคันโยก

        ฟังก์ชัน setLayoutAlpha เอาไว้กำหนดความโปร่งใสของพื้นที่จอยสติ๊ก

        ฟังก์ชัน getLayoutAlpha ก็เอาไว้อ่านค่าความโปร่งใสของพื้นที่จอยสติ๊ก

        ฟังก์ชัน setStickSize เอาไว้กำหนดขนาดของคันโยก โดยกำหนดความกว้างและความสูงของตัวคันโยก

        ฟังก์ชัน setStickWidth ก็เอาไว้กำหนดแค่ความกว้าง

        ฟังก์ชัน setStickHeight เอาไว้กำหนดความสูงของคันโยก

        ฟังก์ชัน getStickWidth เอาไว้อ่านค่าความกว้างของคันโยก

        ฟังก์ชัน getStickHeight เอาไว้อ่านค่าความสูงของคันโยก

        ฟังก์ชัน setLayoutSize เอาไว้กำหนดขนาดของพื้นที่จอยสติ๊ก โดยกำหนดความกว้างและความสูงของพื้นที่จอยสติ๊ก

        ฟังก์ชัน getLayoutWidth ก็เอาไว้อ่านค่าความกว้างของพื้นที่จอยสติ๊ก

        ฟังก์ชัน getLayoutHeight เอาไว้กำหนดความสูงของพื้นที่จอยสติ๊ก

        ฟังก์ชัน cal_angle เอาไว้คำนวณหาว่าจุดที่สัมผัสทำมุมกับพิกัด (0, 0) เป็นมุมเท่าไร ซึ่งอันนี้เคยอธิบายแล้วใน Circle Selector ตามอ่านกันเองละกัน โดยจะกำหนดเป็น Private ซึ่งเจ้าของบล็อกเอาไว้ใช้ในฟังก์ชัน drawStick เท่านั้น

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

        สำหรับคลาส DrawCanvas ก็เป็นคลาสที่เอาไว้วาดคันโยกด้วย Canvas ซึ่งในนี้ก็จะมีฟังก์ชัน onDraw เอาไว้วาดภาพคันโยกลงบน Layout 

        ฟังก์ชัน position ที่เอาไว้กำหนดว่าจะให้วาดคันโยกที่ตำแหน่งใด


        ในการใช้งานคลาส JoyStickClass ก็ค่อนข้างจะโคตรง่าย เพราะเจ้าของบล็อกนั่งเขียนไว้เพื่ออำนวยความสะดวกให้หมดล่ะ


Main.java
package app.akexorcist.joystickcontroller;

import android.os.Bundle;
import android.app.Activity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class Main extends Activity {
    RelativeLayout layout_joystick;
    ImageView image_joystick, image_border;
    TextView textView1, textView2, textView3, textView4, textView5;
    
    JoyStickClass js;
    
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        textView1 = (TextView)findViewById(R.id.textView1);
        textView2 = (TextView)findViewById(R.id.textView2);
        textView3 = (TextView)findViewById(R.id.textView3);
        textView4 = (TextView)findViewById(R.id.textView4);
        textView5 = (TextView)findViewById(R.id.textView5);
        
        layout_joystick = (RelativeLayout)findViewById(R.id.layout_joystick);

        js = new JoyStickClass(getApplicationContext(), layout_joystick, R.drawable.image_button);
        js.setStickSize(150, 150);
        js.setLayoutSize(500, 500);
        js.setLayoutAlpha(150);
        js.setStickAlpha(100);
        js.setOffset(90);
        js.setMinimumDistance(50);
        
        layout_joystick.setOnTouchListener(new OnTouchListener() {
            public boolean onTouch(View arg0, MotionEvent arg1) {
                js.drawStick(arg1);
                if(arg1.getAction() == MotionEvent.ACTION_DOWN
                        || arg1.getAction() == MotionEvent.ACTION_MOVE) {
                    textView1.setText("X : " + String.valueOf(js.getX()));
                    textView2.setText("Y : " + String.valueOf(js.getY()));
                    textView3.setText("Angle : " + String.valueOf(js.getAngle()));
                    textView4.setText("Distance : " + String.valueOf(js.getDistance()));
                    
                    int direction = js.get8Direction();
                    if(direction == JoyStickClass.STICK_UP) {
                        textView5.setText("Direction : Up");
                    } else if(direction == JoyStickClass.STICK_UPRIGHT) {
                        textView5.setText("Direction : Up Right");
                    } else if(direction == JoyStickClass.STICK_RIGHT) {
                        textView5.setText("Direction : Right");
                    } else if(direction == JoyStickClass.STICK_DOWNRIGHT) {
                        textView5.setText("Direction : Down Right");
                    } else if(direction == JoyStickClass.STICK_DOWN) {
                        textView5.setText("Direction : Down");
                    } else if(direction == JoyStickClass.STICK_DOWNLEFT) {
                        textView5.setText("Direction : Down Left");
                    } else if(direction == JoyStickClass.STICK_LEFT) {
                        textView5.setText("Direction : Left");
                    } else if(direction == JoyStickClass.STICK_UPLEFT) {
                        textView5.setText("Direction : Up Left");
                    } else if(direction == JoyStickClass.STICK_NONE) {
                        textView5.setText("Direction : Center");
                    }
                } else if(arg1.getAction() == MotionEvent.ACTION_UP) {
                    textView1.setText("X :");
                    textView2.setText("Y :");
                    textView3.setText("Angle :");
                    textView4.setText("Distance :");
                    textView5.setText("Direction :");
                }
                return true;
            }
        });
    }     
}

        จากที่เห็นก็คือกำหนด Layout ที่ต้องการ (ในตัวอย่างใช้ RelativeLayout) แล้วก็ประกาศใช้คลาส JoyStickClass ชื่อ js โดยส่ง Layout และภาพคันโยกให้กับคลาส จากนั้นเจ้าของบล็อกก็กำหนดคุณสมบัติของ js เต็มที่เลย กำหนดขนาดของคันโยก 150 x 150 ขนาดของพื้นที่จอยสติ๊ก 500 x 500 ความโปร่งใสของพื้นที่จอยสติ๊ก 150 (0-255) และ 100 สำหรับคันโยก

        สำหรับ Offset กำหนดไว้ที่ 90 อันนี้ให้กะระยะเอง ภาพที่ใช้มีระยะไม่เท่ากันอยู่แล้ว และกำหนด Minimum Distance ไว้ที่ 50 อันนี้ก็แล้วแต่ผู้ที่หลงเข้ามาอ่านเลย

        จากนั้นก็สร้าง onTouchListener ขึ้นมาเลย โดยที่ในฟังก์ชัน onTouch ก็ให้ใช้คำสั่ง drawStick โดยส่งค่า MotionEvent ที่ได้ให้กับฟังก์ชัน เท่านี้ก็เรียบร้อยแล้ว อยากจะอ่านค่าอะไรก็เรียกใช้ตามที่อธิบายไปแล้วได้เลย

        ในตัวอย่างของเจ้าของบล็อกก็จะให้อ่านค่าจุดสัมผัสในแนวแกน X มาแสดงบน textView1 และแนวแกน Y แสดงบน textView2 ส่วนมุมระหว่างจุดสัมผัสกับจุดกึ่งกลางของจอยสติ๊กแสดงบน textView3 ระยะห่างระหว่างจุดสัมผัสกับจุดกึ่งกลางของจอยสติ๊กแสดงบน textView4

        จากนั้นสร้างตัวแปร direction เพื่อรับค่าจากฟังก์ชัน get8Direction  เพื่อที่จะอ่านค่าทิศทางที่ได้นั่นเอง โดยจะใช้ If เช็คเงื่อนไขว่าตรงกับเงื่อนไขได้ ถ้าตรงกับทิศทางใดก็ให้แสดงทิศทางนั้นๆบน textView5 และถ้าอยู่ตรงกลางที่เป็นพื้นที่นอกขอบเขตที่กำหนดไว้ใน Minimum Distance ก็จะแสดงทิศทางเป็น Center แทน และเมื่อยกนิ้วออกจากจอยสติ๊กก็จะเคลียร์ค่า

main.xml (layout-port)
<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" >
    <LinearLayout
        android:id="@+id/linearLayout1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_margin="10dp"
        android:orientation="vertical" >
        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true"
            android:text="X"
            android:textColor="#444444"
            android:textSize="20dp" />
        <TextView
            android:id="@+id/textView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_below="@+id/textView1"
            android:text="Y"
            android:textColor="#444444"
            android:textSize="20dp" />
        <TextView
            android:id="@+id/textView3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_below="@+id/textView2"
            android:text="Angle"
            android:textColor="#444444"
            android:textSize="20dp" />
        <TextView
            android:id="@+id/textView4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_below="@+id/textView3"
            android:text="Distance"
            android:textColor="#444444"
            android:textSize="20dp" />
        <TextView
            android:id="@+id/textView5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_below="@+id/textView3"
            android:text="Direction"
            android:textColor="#444444"
            android:textSize="20dp" />
    </LinearLayout>
    <RelativeLayout
        android:id="@+id/layout_joystick"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_below="@+id/linearLayout1"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="29dp"
        android:background="@drawable/image_button_bg" >
    </RelativeLayout>
</RelativeLayout>

main.xml (layout-land)
<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" >
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_margin="30dp"
        android:orientation="vertical" >
        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="X"
            android:textColor="#444444"
            android:textSize="20dp" />
        <TextView
            android:id="@+id/textView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Y"
            android:textColor="#444444"
            android:textSize="20dp" />
        <TextView
            android:id="@+id/textView3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Angle"
            android:textColor="#444444"
            android:textSize="20dp" />
        <TextView
            android:id="@+id/textView4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Distance"
            android:textColor="#444444"
            android:textSize="20dp" />
        <TextView
            android:id="@+id/textView5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Direction"
            android:textColor="#444444"
            android:textSize="20dp" />
    </LinearLayout>
    <RelativeLayout
        android:id="@+id/layout_joystick"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:layout_marginRight="36dp"
        android:background="@drawable/image_button_bg" >
    </RelativeLayout>
</RelativeLayout>

        ตัวอย่างนี้ถ้าผู้ที่หลงเข้ามาอ่านคนใดได้ดาวน์โหลดไฟล์ตัวอย่างไป
        จะพบว่าทำไมเจ้าของบล็อกสร้างโฟลเดอร์ layout เป็นสองอัน ก็เพราะว่าโฟลเดอร์ layout-land เป็นหน้าจอสำหรับแนวนอนและโฟลเดอร์ layout-port เป็นหน้าจอสำหรับแนวตั้ง



แนวตั้ง



แนวนอน



        ถ้าใครที่ยังงงๆว่ามันคืออะไร ทำยังไงได้ ให้ลองอ่านบทความนี้ดู [Android Design] Supporting Multiple Screens - การรองรับหน้าจอที่หลากหลาย


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

        เท่านี้ก็เสร็จเรียบร้อยแล้ว เอาจริงๆใช้คำสั่งไม่กี่บรรทัดด้วยซ้ำ แค่สร้าง Layout ไว้ให้พร้อม เตรียมภาพไว้ให้พร้อม กำหนดค่าให้พอดี เท่านี้ก็สร้างจอยสติ๊กได้แล้ว จะเอาไปใช้งานกับอะไรต่อก็เชิญเลย

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


        เขียนบทความนานกว่าเขียนโค๊ดอีก...