藍牙遊戲手柄監聽操作app

首先藍牙手柄連接又android官方協議,我們只需要在activity或者view中監聽即可。
手柄一般可分爲模擬按鍵、安卓和PC等模式,我們這裏簡單介紹下模擬按鍵和安卓的2種模式

1、模擬按鍵

顧名思義,手柄操作相當於在屏幕上點擊,可以用以下代碼進行監聽

package com.**.activity.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.util.AttributeSet;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;

import java.util.ArrayList;

/**
 * Debug測試手柄按鍵的SurfaceView
 */
public class HandTouchView extends SurfaceView implements SurfaceHolder.Callback {
    private static String TAG = "HandTouchView";
    public boolean isTest = false;
    private static final int MAX_TOUCHPOINTS = 10;
    public static final int LEFT_JS_CODE = -1;//
    public static final int RIGHT_JS_CODE = -2;//

    public static final double LEFT_JS_MAX_X = 139.27744-58.87142;
    public static final double LEFT_JS_MAX_Y = 430.57452-224.23979;
    private Paint paint;
    private ArrayList<BtnBean> btnList = new ArrayList<>();
    private ArrayList<BtnBean> tempList = new ArrayList<>();

    private int width, height;//SurfaceView的寬和高
    private float scale = 1.0f;
    private Canvas canvas;
    private int pointerCount = 0;//同時onTouch事件的 個數
    //按鈕bean start
    private BtnBean XBean;
    private BtnBean YBean;
    private BtnBean LeftJSBean;
    private BtnBean RightJSBean;
    //按鈕bean end
    //控件對象start
    public ImageView ivBtnX;
    public ImageView ivBtnY;
    public ProgressBar progressbar_l2;
    public ProgressBar progressbar_r2;
    public TextView tvProgressL2;
    public TextView tvProgressR2;
    //控件對象end

    public HandTouchView(Context context) {
        super(context);
        init();
    }

    public HandTouchView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        setZOrderOnTop(true);
        SurfaceHolder holder = getHolder();
        holder.addCallback(this);
        holder.setFormat(PixelFormat.TRANSLUCENT);
        setFocusable(true); // 確保我們的View能獲得輸入焦點
        setFocusableInTouchMode(true); // 確保能接收到觸屏事件

        paint = new Paint();
        paint.setColor(Color.BLUE);
        //先用2個 測試 以後換數據庫
        //按鍵的bean start
        XBean = new BtnBean(KeyEvent.KEYCODE_BUTTON_X,
                            0, 0);
        YBean = new BtnBean(KeyEvent.KEYCODE_BUTTON_Y,
                            0, 0);
        LeftJSBean = new BtnBean(LEFT_JS_CODE, 0,
                                 0);//左搖桿
        RightJSBean = new BtnBean(RIGHT_JS_CODE, 0, 0);//右搖桿
        //按鍵的bean end
    }

    /*
     * 處理觸屏事件
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        describeEvent(this, event);
        handleLeftStick(this,event);
        // 獲得屏幕觸點數量
        pointerCount = event.getPointerCount();
        if (pointerCount > MAX_TOUCHPOINTS) {
            pointerCount = MAX_TOUCHPOINTS;
        }
        // 鎖定Canvas,開始進行相應的界面處理
        canvas = getHolder().lockCanvas();
        if (canvas != null) {
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            if (event.getAction() == MotionEvent.ACTION_UP) {
                // 當手離開屏幕時,清屏
            } else {
                // 先在屏幕上畫一個十字,然後畫一個圓
                for (int i = 0; i < pointerCount; i++) {//重繪:相當於把以前的點帶着一起畫
                    // 獲取一個觸點的座標,然後開始繪製
                    int id = event.getPointerId(i);
                    float x = event.getX(i);
                    float y = event.getY(i);
                    drawCrosshairsAndText(x, y, paint, canvas);
                }
                for (int i = 0; i < pointerCount; i++) {
                    int id = event.getPointerId(i);
                    float x = event.getX(i);
                    float y = event.getY(i);
                    drawCircle(x, y, paint, canvas);
                }
            }
            // 畫完後,unlock
            getHolder().unlockCanvasAndPost(canvas);
        }
        return true;
    }
    //將左側手柄按鈕的移動轉化爲座標,傳遞到上層
    private void handleLeftStick(View view,MotionEvent event){
        float x = event.getRawX() - 221.68193f;
        float y = event.getRawY() - 82.87142f;
        if(mStickListener != null){
            mStickListener.onLeftMove(x,y,event.getAction());
        }
    }

    /**
     * 畫十字及座標信息
     *
     * @param x
     * @param y
     * @param paint
     * @param c
     */
    private void drawCrosshairsAndText(float x, float y, Paint paint, Canvas c) {
        c.drawLine(0, y, width, y, paint);//橫線
        c.drawLine(x, 0, x, height, paint);//豎線
    }

    /**
     * 畫圓
     *
     * @param x
     * @param y
     * @param paint
     * @param c
     */
    private void drawCircle(float x, float y, Paint paint, Canvas c) {
        c.drawCircle(x, y, 15 * scale, paint);
    }

    /*
     * 進入程序時背景畫成黑色,然後把"START_TEXT"寫到屏幕
     */
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
                               int height) {
        this.width = width;
        this.height = height;
        if (width > height) {
            this.scale = width / 480f;
        } else {
            this.scale = height / 480f;
        }
    }

    public void surfaceCreated(SurfaceHolder holder) {
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
    }


    //畫點和十字
    private void drawBtnCanvas() {
        canvas = getHolder().lockCanvas();
        if (canvas != null) {
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            // 先在屏幕上畫一個十字,然後畫一個圓
            for (int i = 0; i < btnList.size(); i++) {
                drawCrosshairsAndText(btnList.get(i).x, btnList.get(i).y, paint, canvas);
                drawCircle(btnList.get(i).x, btnList.get(i).y, paint, canvas);
            }
            // 畫完後,unlock
            getHolder().unlockCanvasAndPost(canvas);
        }
    }

    //清除畫布
    private void clearBtnCanvas() {
        canvas = getHolder().lockCanvas();
        if (canvas != null) {
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            // 畫完後,unlock
            getHolder().unlockCanvasAndPost(canvas);
        }
    }

    //btnList添加按鍵
    private void addBtnBean(int keyCode, BtnBean bean) {
        if (keyCode == bean.keyCode) {
            if (!btnList.contains(bean))
                btnList.add(bean);
        }
    }
    private void removeBtnBean(int keyCode, BtnBean bean) {
        if (keyCode == bean.keyCode) {
            if (btnList.contains(bean))
                btnList.remove(bean);
        }
    }

    //打印log start
    private void describeEvent(View view, MotionEvent event) {
        StringBuilder sb = new StringBuilder(300);

        sb.append("Action: ").append(event.getAction()).append("\n");// 獲取觸控動作比如ACTION_DOWN
        sb.append("相對座標: ").append(event.getX()).append("  *  ").append(event.getY()).append("   ");
        sb.append("絕對座標: ").append(event.getRawX()).append("  *  ").append(event.getRawY()).append("\n");

        if (event.getX() < 0 || event.getX() > view.getWidth() || event.getY() < 0 || event.getY() > view.getHeight()) {
            sb.append("未點擊在View範圍內");
        }
        sb.append("Edge flags: ").append(event.getEdgeFlags()).append("  ");// 邊緣標記,但是看設備情況,很可能始終返回0
        sb.append("Pressure: ").append(event.getPressure()).append("  ");// 壓力值,0-1之間,看情況,很可能始終返回1
        sb.append("Size: ").append(event.getSize()).append("\n");// 指壓範圍
        sb.append("Down time: ").append(event.getDownTime()).append("ms   ");
        sb.append("Event time: ").append(event.getEventTime()).append("ms   ");
        sb.append("Elapsed: ").append(event.getEventTime() - event.getDownTime()).append("ms\n");

        Log.e(TAG,"describeEvent:" + sb.toString());
        //sb.toString();
    }
    //打印log end

    //手柄event start    keyCode==4時候 是back
    //KeyEvent.KEYCODE_BUTTON_X   99;
    //KEYCODE_BUTTON_Y = 100
    //KEYCODE_BUTTON_A = 96;
    //KEYCODE_BUTTON_B = 97;
    //KEYCODE_BUTTON_START = 108;
    //KEYCODE_UNKNOWN = 0 -- i鍵
    //KEYCODE_BACK = 4   (Y , B , 手機back鍵也會觸發)
    //KEYCODE_BUTTON_L1 = 102
    //KEYCODE_BUTTON_R1 = 103
    //KEYCODE_BUTTON_L2 = 104 -- 有onGenericMotionEvent屬性 0.003921628不變
    //KEYCODE_BUTTON_R2 = 105 -- 有onGenericMotionEvent屬性 0.003921628不變
    //KEYCODE_DPAD_DOWN = 20;
    //KEYCODE_DPAD_LEFT = 21;
    //KEYCODE_DPAD_RIGHT = 22;
    //KEYCODE_DPAD_UP = 19;
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        Log.e(TAG,"keyDown keyCode = " + keyCode + ", scanCode = " + event.getScanCode());
        InputDevice device = event.getDevice();
//        if (device != null && (device.getSources() + "").length() == 8 && keyCode == KeyEvent.KEYCODE_BACK) {
////小米手柄 16779025  手機 4355//三星手柄 16778513  手機 257//moto    16779025  手機 769
//            return true;
//        }
        //添加Btn對象
        addBtnBean(keyCode, XBean);

        addBtnBean(keyCode, YBean);
        // 鎖定Canvas,開始進行相應的界面處理
        drawBtnCanvas();
        return super.onKeyDown(keyCode, event);
    }


    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        Log.e(TAG, "keyCode="+keyCode);
        InputDevice device = event.getDevice();
        if (device != null && (device.getSources() + "").length() == 8 && keyCode == KeyEvent.KEYCODE_BACK) {
            //小米手柄 16779025  手機 4355//三星手柄 16778513  手機 257//moto    16779025  手機 769
            return true;
        }
        removeBtnBean(keyCode, XBean);
        removeBtnBean(keyCode, YBean);
        drawBtnCanvas();
        if (btnList.size() == 0)
            clearBtnCanvas();
        return super.onKeyUp(keyCode, event);
    }

    //搖桿官方示例start
    @Override
    public boolean onGenericMotionEvent(MotionEvent event) {
        Log.e(TAG,"AXIS_LTRIGGER"+event.getAxisValue(MotionEvent.AXIS_LTRIGGER));

        // Check that the event came from a game controller
        if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) ==
                InputDevice.SOURCE_JOYSTICK &&
                event.getAction() == MotionEvent.ACTION_MOVE) {
            // Process all historical movement samples in the batch
//            final int historySize = event.getHistorySize();
//            //處理 歷史事件
//            for (int i = 0; i < historySize; i++) {
//                // Process the event at historical position i
//                processJoystickInput(event, i);
//            }

            // Process the current movement sample in the batch (position -1)
            processJoystickInput(event, -1);

            return true;
        }
        return super.onGenericMotionEvent(event);
    }

    private static float getCenteredAxis(MotionEvent event,
                                         InputDevice device, int axis, int historyPos) {
        final InputDevice.MotionRange range =
                device.getMotionRange(axis, event.getSource());

        // A joystick at rest does not always report an absolute position of
        // (0,0). Use the getFlat() method to determine the range of values
        // bounding the joystick axis center.
        if (range != null) {
            final float flat = range.getFlat();
            final float value =
                    historyPos < 0 ? event.getAxisValue(axis) :
                            event.getHistoricalAxisValue(axis, historyPos);

            // Ignore axis values that are within the 'flat' region of the
            // joystick axis center.
            if (Math.abs(value) > flat) {
                return value;
            }
        }
        return 0;
    }

    private void processJoystickInput(MotionEvent event,
                                      int historyPos) {

        InputDevice mInputDevice = event.getDevice();
        //左搖桿
        float leftx = getCenteredAxis(event, mInputDevice,
                                      MotionEvent.AXIS_X, historyPos);
        float lefty = getCenteredAxis(event, mInputDevice,
                                      MotionEvent.AXIS_Y, historyPos);
        //右搖桿
        float rightx = getCenteredAxis(event, mInputDevice,
                                       MotionEvent.AXIS_Z, historyPos);
        float righty = getCenteredAxis(event, mInputDevice,
                                       MotionEvent.AXIS_RZ, historyPos);
        //L2(0.0--0.7)
        float l2 = getCenteredAxis(event, mInputDevice,
                                   MotionEvent.AXIS_BRAKE, historyPos);
        //R2(0.0--1.0)
        float r2 = getCenteredAxis(event, mInputDevice,
                                   MotionEvent.AXIS_GAS, historyPos);
        //在這裏處理 Axis 事件

        doLeftJoystick(leftx, lefty);//左搖桿
        doRightJoystick(rightx, righty);//右搖桿
        doL2(l2);//L2(0.0--0.7)
        doR2(r2);//R2(0.0--1.0)


        // Update the ship object based on the new x and y values
    }


    private void doLeftJoystick(float x, float y) {
        if (Math.abs(x) > 0 || Math.abs(y) > 0) {
            boolean isLeftAdd = false;
            for (int i = 0; i < btnList.size(); i++) {
                if (btnList.get(i).keyCode == LEFT_JS_CODE) {
                    btnList.get(i).x = 0 ;
                    btnList.get(i).y = 0 ;
                    isLeftAdd = true;
                    break;
                }
            }
            if (!isLeftAdd) {
                LeftJSBean = new BtnBean(LEFT_JS_CODE, 0,
                                         0);//左搖桿
                addBtnBean(LEFT_JS_CODE, LeftJSBean);
            }

        }

        if (x == 0 && y == 0) {
            for (int i = 0; i < btnList.size(); i++) {
                if (btnList.get(i).keyCode != LEFT_JS_CODE)
                    tempList.add(btnList.get(i));
            }
            btnList.clear();
            btnList.addAll(tempList);
            tempList.clear();
        }
        drawBtnCanvas();
        if (btnList.size() == 0)
            clearBtnCanvas();
    }

    private void doRightJoystick(float x, float y) {
        if (Math.abs(x) > 0 || Math.abs(y) > 0) {
            boolean isRightAdd = false;
            for (int i = 0; i < btnList.size(); i++) {
                if (btnList.get(i).keyCode == RIGHT_JS_CODE) {
                    btnList.get(i).x = 0;
                    btnList.get(i).y = 0;
                    isRightAdd = true;
                    break;
                }
            }
            if (!isRightAdd) {
                RightJSBean = new BtnBean(RIGHT_JS_CODE, 0, 0);//右搖桿
                addBtnBean(RIGHT_JS_CODE, RightJSBean);
            }

        }

        if (x == 0 && y == 0) {
            for (int i = 0; i < btnList.size(); i++) {
                if (btnList.get(i).keyCode != RIGHT_JS_CODE)
                    tempList.add(btnList.get(i));
            }
            btnList.clear();
            btnList.addAll(tempList);
            tempList.clear();
        }
        drawBtnCanvas();
        if (btnList.size() == 0)
            clearBtnCanvas();
    }

    private void doL2(float l2) {
        int a = (int) (l2 * 100);
        if (progressbar_l2 != null)
            progressbar_l2.setProgress(a);
        if (tvProgressL2 != null)
            tvProgressL2.setText(a+"");
    }


    private void doR2(float r2) {
        int a = (int) (r2 * 100);
        if (progressbar_r2 != null)
            progressbar_r2.setProgress(a);
        if (tvProgressR2 != null)
            tvProgressR2.setText(a+"");
    }


    //搖桿官方示例end
    //手柄event end

    public class BtnBean{
        public int keyCode;
        public float x;
        public float y;

        public BtnBean(int keyCode, float x, float y) {
            this.keyCode = keyCode;
            this.x = x;
            this.y = y;
        }
    }
    private OnStickMoveListener mStickListener;
    public void setStickMoveListener(OnStickMoveListener listener){
        mStickListener = listener;
    }
    public interface OnStickMoveListener{
        void onLeftMove(float x,float y,int action);
    }
}


2、安卓模式監聽

我這裏只監聽了左搖桿的按鍵和位移,用來操作機器人的前前後左右,核心代碼如下:

    override fun onGenericMotionEvent(event: MotionEvent?): Boolean {
        var x = event?.getAxisValue(MotionEvent.AXIS_X)
        var y = event?.getAxisValue(MotionEvent.AXIS_Y)
        var msg = Message.obtain(mHandler)
        var lineSpeed = if (Math.abs(y!!) > 0.01) -(y/2) else 0f
        var angSpeed = if (Math.abs(x!!) > 0.01) -x/2 else 0f
        if (lineSpeed == 0f && angSpeed == 0f) {
            mHandler?.removeMessages(WHAT_MOVE)
            mHandler?.sendEmptyMessage(WHAT_STOP) //停止移動
        } else {
            msg.what = WHAT_MOVE
            msg.obj = Speed(lineSpeed.toDouble(), angSpeed.toDouble())
            mHandler?.removeMessages(WHAT_MOVE)
            mHandler?.sendMessage(msg)  //開始移動
        }
        return true
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章