首先蓝牙手柄连接又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
}
image.png
网友评论