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)让摇杆跟着鼠标飞
