Android自定义摇杆实现蓝牙控制小车
1.1 背景
本文继《Android通过蓝牙HC06与Arduino通信实例》一文进行UI设计,考虑到四方向按键操作智能小车的体验性,不如做一个摇杆来控制来得好。1.2 需求
1)控制摇杆由摇杆(小圆)和底座(大圆)组成; 2)全屏触摸,摇杆位置不离开底座范围; 3)停止触摸,摇杆恢复到中心,小车停止运动; 4)摇杆分成6个方向,分别控制小车 前进、后退、前进左、前进右、后退左、后退右。2.1 SurfaceView
在Android系统中,有一种特殊的视图,称为SurfaceView,它拥有独立的绘图表面,即它不与其宿主窗口共享同一个绘图表面。由于拥有独立的绘图表面,因此SurfaceView的UI就可以在一个独立的线程中进行绘制。又由于不会占用主线程资源,SurfaceView一方面可以实现复杂而高效的UI,另一方面又不会导致用户输入得不到及时响应[1]。 Surface是纵深排序(Z-ordered)的,这表明它总在自己所在窗口的后面。SurfaceView提供了一个可见区域,只有在这个可见区域内 的surface部分内容才可见,可见区域外的部分不可见。实现步骤: 1) SurfaceView.getHolder()获得SurfaceHolder对象
2) SurfaceHolder.addCallback(callback)添加回调函数
3) SurfaceHolder.lockCanvas()获得Canvas对象并锁定画布
4) Canvas绘画
5) SurfaceHolder.unlockCanvasAndPost(Canvas canvas)结束锁定画图,并提交改变,将图形显示
2.2 三角函数
使用余弦函数、反余弦函数对摇杆的角度进行计算3.1 SurfaceView绘制
创建了SurfaceView类,MySurfaceViewprivate static final String TAG = "MySurfaceView"; private SurfaceHolder mHolder; private Paint mPaint; private Thread mThread; private boolean mFlag; private Canvas mCanvas; public double mRad, mAngle; public int mLogicType, mLogicStatus; public Context mContext; private int mRockCentX, mRockCentY, mRockRadius; private int mBaseCentX, mBaseCentY, mBaseRadius; private final int LOGIC_STOP = 0x00; private final int LOGIC_UP = 0x01; private final int LOGIC_DOWN = 0x02; private final int LOGIC_ULEFT = 0x03; private final int LOGIC_URIGHT = 0x04; private final int LOGIC_DLEFT = 0x05; private final int LOGIC_DRIGHT = 0x06;构造函数,对摇杆大小进行传参
/** * Constructor to initialize the size * @param context * @param x * @param y * @param r */ public MySurfaceView(Context context, int x, int y, int r) { super(context); mContext = context; mRockCentX = mBaseCentX = x; mRockCentY = mBaseCentY = y; mRockRadius = r; mBaseRadius = r * 3; mLogicStatus = -1; mHolder = this.getHolder(); mHolder.addCallback(this); mPaint = new Paint(); mPaint.setColor(Color.BLUE); mPaint.setAntiAlias(true); setFocusable(true); }生命周期
@Override public void surfaceCreated(SurfaceHolder holder) { mFlag = true; /* Setup thread to handle events and plain canvas */ mThread = new Thread(this); mThread.start(); }然后看一下 绘制画布的线程,每50ms(粗糙值)进行绘图,其中绘图的圆心坐标,半径大小取决于给定的参数
/** * Thread */ @Override public void run() { while (mFlag) { myDraw(); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } }
/** * Draw the rocker in the thread */ public void myDraw() { try { mCanvas = mHolder.lockCanvas(); if (mCanvas != null) { mPaint.setAlpha(0x77); mCanvas.drawColor(Color.WHITE); /* draw base */ mCanvas.drawCircle(mBaseCentX, mBaseCentY, mBaseRadius, mPaint); /* draw rocker */ mCanvas.drawCircle(mRockCentX, mRockCentY, mRockRadius, mPaint); } } catch (Exception e) { // TODO: handle exception } finally { if (mCanvas != null) { mHolder.unlockCanvasAndPost(mCanvas); } } }摇杆位置则由 touch event来计算得出
@Override public boolean onTouchEvent(MotionEvent event) { /* Reset rocker when touch up */ if (event.getAction() == MotionEvent.ACTION_UP) { mRockCentX = mBaseCentX; mRockCentY = mBaseCentY; mLogicType = LOGIC_STOP; }
<span style="white-space:pre"> </span>else { float pointDx = event.getX() - mBaseCentX; float pointDy = event.getY() - mBaseCentY; double pointR = Math.sqrt(pointDx * pointDx + pointDy * pointDy); if ( pointR <= mBaseRadius ) { mRockCentX = (int) event.getX(); mRockCentY = (int) event.getY(); } else { mRockCentX = mBaseCentX + (int) (mBaseRadius * pointDx / pointR); mRockCentY = mBaseCentY + (int) (mBaseRadius * pointDy / pointR); } mRad = Math.acos(pointDx / pointR); if ( event.getY() > mRockCentY ) { mRad = -mRad; } else { mAngle = Math.toDegrees(mRad); mLogicType = setLogicType(mAngle); Log.i(TAG, "Degrees: " + mAngle + "", Set Logic Type: " + mLogicType); } } mSendBroadcast(0x04, mLogicType); return true; }上述也就是核心的计算过程: 需求2实现,不触摸摇杆则将摇杆圆心恢复到底座圆心位置去,mBaseCentX/Y是底座固定的值;
需求3实现,摇杆圆心位置 不超出底座范围,根据touch获取到用户触摸坐标, 若触摸的位置超出底座范围,则将坐标 按照同样角度投影到 底座边缘上, 若触摸处于底座范围内,则将坐标 直接给摇杆圆心位置。 需求4实现,根据触摸坐标 与圆心坐标 形成的角度进行判断 划分不同的动作,具体看一下setLogicType()
/** * Calculate logic action type * @param angle - rocker angle -180~180 * @return type 0~5 on success, -1 on failure */ int setLogicType(double angle) { int type = -1; if (angle > 0 && angle <= 60) { type = LOGIC_URIGHT; } else if (angle > 60 && angle <= 120) { type = LOGIC_UP; } else if (angle > 120 && angle <= 180) { type = LOGIC_ULEFT; } else if (angle > -180 && angle <= -120) { type = LOGIC_DLEFT; } else if (angle > -120 && angle <= -60) { type = LOGIC_DOWN; } else if (angle > -60 && angle <= 0) { type = LOGIC_DRIGHT; } return type; }最后回顾一下,绘图部分OK了,逻辑部分处理也OK了,就差去响应了 发广播给Service,告诉小车去执行动作,注意下,这MySurfaceView类是在Activity中实例化的。
/** * Send message to service * @param cmd * @param value */ public void mSendBroadcast(int cmd, int value) { if (mLogicStatus != value) { Intent intent = new Intent(); intent.setAction("android.intent.action.cmdservice"); intent.putExtra("cmd", cmd); intent.putExtra("value", value); mContext.sendBroadcast(intent); Log.d(TAG, "sendBroadcast: " + cmd + " " + value); mLogicStatus = value; } }Service实现的步骤请看第一期的文章《Android创建Service后台常驻服务并使用Broadcast通信》
文章三篇下来已经完成了智能小车的第一期计划:手机蓝牙遥控小车。 这三期文章主要的侧重点还是Android这块的编程,单片机上的编程涉及的确实不多,我的想法是把UI这一块给拿起来了,到时需要什么界面就自己动手做了。 后续的计划,将智能小车三轮底盘升级成双轮平衡小车,中间需要一个PID算法,哈当然是工作有闲下来后展开。
工程下载地址:http://download.csdn.net/detail/stayneckwind2/8708607
参考文章:
[1] 老罗blog的详解, http://blog.csdn.net/luoshengyang/article/details/8661317
[2] Android游戏开发 http://blog.csdn.net/xiaominghimi/article/details/6423983
声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。
- 上一篇: android虚拟摇杆
- 下一篇: QT 模仿Android游戏中虚拟摇杆(1)让摇杆跟着鼠标飞