首先藍牙手柄連接又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
}