一.背景
在实现了android手机对Dragonboard 410c系统的远程舵机控制后,我们再回头来优化android客户端的界面控制效果。
二.虚拟摇杆
1.android虚拟摇杆
上一节我们在android的客户端是通过手动输入数值的方式来控制舵机,虽然这也能实现远程控制,但是相对于人的操作方式相当不方便,这里我们考虑使用虚拟的摇杆来优化这个控制方式。
图1 虚拟摇杆效果
2.摇杆实现代码
2.1.RockerView.java
package com.boss.xiao.streerocker;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnPreDrawListener;
public class RockerView extends View {
//固定摇杆背景圆形的X,Y坐标以及半径
private float mRockerBg_X;
private float mRockerBg_Y;
private float mRockerBg_R;
//摇杆的X,Y坐标以及摇杆的半径
private float mRockerBtn_X;
private float mRockerBtn_Y;
private float mRockerBtn_R;
private Bitmap mBmpRockerBg;
private Bitmap mBmpRockerBtn;
private PointF mCenterPoint;
public RockerView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
// 获取bitmap
mBmpRockerBg = BitmapFactory.decodeResource(context.getResources(), R.drawable.rocker_bg);
mBmpRockerBtn = BitmapFactory.decodeResource(context.getResources(), R.drawable.rocker_btn);
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
// 调用该方法时可以获取view实际的宽getWidth()和高getHeight()
@Override
public boolean onPreDraw() {
// TODO Auto-generated method stub
getViewTreeObserver().removeOnPreDrawListener(this);
Log.e("RockerView", getWidth() + "/" + getHeight());
mCenterPoint = new PointF(getWidth() / 2, getHeight() / 2);
mRockerBg_X = mCenterPoint.x;
mRockerBg_Y = mCenterPoint.y;
mRockerBtn_X = mCenterPoint.x;
mRockerBtn_Y = mCenterPoint.y;
float tmp_f = mBmpRockerBg.getWidth() / (float)(mBmpRockerBg.getWidth() + mBmpRockerBtn.getWidth());
mRockerBg_R = tmp_f * getWidth() / 2;
mRockerBtn_R = (1.0f - tmp_f)* getWidth() / 2;
return true;
}
});
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
//系统调用onDraw方法刷新画面
RockerView.this.postInvalidate();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}).start();
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
canvas.drawBitmap(mBmpRockerBg, null,
new Rect((int)(mRockerBg_X - mRockerBg_R),
(int)(mRockerBg_Y - mRockerBg_R),
(int)(mRockerBg_X + mRockerBg_R),
(int)(mRockerBg_Y + mRockerBg_R)),
null);
canvas.drawBitmap(mBmpRockerBtn, null,
new Rect((int)(mRockerBtn_X - mRockerBtn_R),
(int)(mRockerBtn_Y - mRockerBtn_R),
(int)(mRockerBtn_X + mRockerBtn_R),
(int)(mRockerBtn_Y + mRockerBtn_R)),
null);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) {
// 当触屏区域不在活动范围内
if (Math.sqrt(Math.pow((mRockerBg_X - (int) event.getX()), 2) + Math.pow((mRockerBg_Y - (int) event.getY()), 2)) >= mRockerBg_R) {
//得到摇杆与触屏点所形成的角度
double tempRad = getRad(mRockerBg_X, mRockerBg_Y, event.getX(), event.getY());
//保证内部小圆运动的长度限制
getXY(mRockerBg_X, mRockerBg_Y, mRockerBg_R, tempRad);
} else {//如果小球中心点小于活动区域则随着用户触屏点移动即可
mRockerBtn_X = (int) event.getX();
mRockerBtn_Y = (int) event.getY();
}
if(mRockerChangeListener != null) {
mRockerChangeListener.report(mRockerBtn_X - mCenterPoint.x, mRockerBtn_Y - mCenterPoint.y);
}
} else if (event.getAction() == MotionEvent.ACTION_UP) {
//当释放按键时摇杆要恢复摇杆的位置为初始位置
mRockerBtn_X = mCenterPoint.x;
mRockerBtn_Y = mCenterPoint.y;
if(mRockerChangeListener != null) {
mRockerChangeListener.report(0, 0);
}
}
return true;
}
/***
* 得到两点之间的弧度
*/
public double getRad(float px1, float py1, float px2, float py2) {
//得到两点X的距离
float x = px2 - px1;
//得到两点Y的距离
float y = py1 - py2;
//算出斜边长
float xie = (float) Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
//得到这个角度的余弦值(通过三角函数中的定理 :邻边/斜边=角度余弦值)
float cosAngle = x / xie;
//通过反余弦定理获取到其角度的弧度
float rad = (float) Math.acos(cosAngle);
//注意:当触屏的位置Y坐标
<摇杆的y坐标我们要取反值-0~-180 if (py2 < py1) { rad } return rad; ** * @param r 圆周运动的旋转点 centerx 旋转点x centery 旋转点y 旋转的弧度 public void getxy(float centerx, float centery, r, double rad) 获取圆周运动的x坐标 mrockerbtn_x="(float)" (r math.cos(rad)) + centerx; 获取圆周运动的y坐标 mrockerbtn_y="(float)" math.sin(rad)) centery; rockerchangelistener mrockerchangelistener="rockerChangeListener;" setrockerchangelistener(rockerchangelistener rockerchangelistener) interface report(float x, y); code>
2.2. activity_main.xml
三.远程控制
1.核心控制代码:
MainActivity.java
package com.boss.xiao.streerocker;
import android.app.Activity;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.content.Intent;
import android.os.Bundle;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ToggleButton;
import java.io.IOException;
import java.io.Serializable;
public class MainActivity extends Activity {
private EditText edtTxt_Addr;
private EditText edtTxt_Port;
private ToggleButton tglBtn;
private ToggleButton btn_fire;
private Button btn_rocker;
private String model="connector";
Intent intent;
private RockerView rockerView1;
private RockerView rockerView2;
private static String left_flag="LEFT";
private static String right_flag="RIGHT";
private static String rec_flag="Succee";
private static String open_flag="OPEN";
private static String close_flag="CLOSE";
private static boolean flag_report=true;
// private TextView tv_Msg;
// private EditText edtTxt_Data;
// private Button btn_Send;
private static final String TAG = "MainActivity";
private TcpClientConnector connector;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InitWidgets();
connector = TcpClientConnector.getInstance(); //获取connector实例
tglBtn.setOnCheckedChangeListener(new TglBtnCheckedChangeEvents());
btn_fire.setOnCheckedChangeListener(new fireBtnCheckedChangeEvents());
InitRocker();
// btn_rocker.setOnClickListener(new ButtonClickEvent());
}
/*初始化 摇杆**/
private void InitRocker()
{
rockerView1 = (RockerView) findViewById(R.id.rockerView1);
rockerView2 = (RockerView) findViewById(R.id.rockerView2);
rockerView1.setRockerChangeListener(new RockerView.RockerChangeListener() {
@Override
public void report(float x, float y) {
// TODO Auto-generated method stub
Log.e(x + "/" + y,TAG);
// setLayout(rockerView2, (int)x, (int)y);
//setLayout(rockerView2, (int)x, (int)y);
//String data="1000000";
try{
// connector.send(left_flag);
// if(flag_report)
if(flag_report==true)
{
if(x<0)
{
connector.send(left_flag);
}
else if(x>=0)
{
connector.send(right_flag);
}
/*else
{
Log.d("everything is ok",TAG);
}*/
flag_report=false;
}
else
{
Log.d("this cmd is not acesss",TAG);
}
}catch (IOException e){
e.printStackTrace();
}
}
});
rockerView2.setRockerChangeListener(new RockerView.RockerChangeListener() {
@Override
public void report(float x, float y) {
// TODO Auto-generated method stub
Log.e(x + "/" + y,TAG);
}
});
}
/***
* 控件初始化
*/
private void InitWidgets(){
edtTxt_Addr = (EditText) findViewById(R.id.edtTxt_Addr);
edtTxt_Port = (EditText) findViewById(R.id.edtTxt_Port);
tglBtn = (ToggleButton) findViewById(R.id.tglBtn);
btn_fire= (ToggleButton) findViewById(R.id.btn_fire);
// btn_rocker = (Button) findViewById(R.id.btn_rocker);
// tv_Msg = (TextView) findViewById(R.id.tv_Msg);
// edtTxt_Data = (EditText) findViewById(R.id.edtTxt_Data);
// btn_Send = (Button) findViewById(R.id.btn_Send);
}
class TglBtnCheckedChangeEvents implements ToggleButton.OnCheckedChangeListener{
@Override
public void onCheckedChanged(CompoundButton btnView, boolean isChecked){
if(btnView == tglBtn){
if(isChecked == true){
//连接Tcp服务器端
connector.createConnect("172.27.35.16",8888); //调试使用
// connector.createConnect(edtTxt_Addr.getText().toString(),Integer.parseInt(edtTxt_Port.getText().toString()));
connector.setOnConnectListener(new TcpClientConnector.ConnectListener() {
@Override
public void onReceiveData(String data) {
//Received Data,do somethings.
// tv_Msg.append("Server:"+ data + "\n");
Log.d("data = "+data,TAG);
if(data.equals(rec_flag))
{
flag_report=true;
Log.d("I get report "+data,TAG);
}
}
});
}else{
try{ //断开与服务器的连接
connector.disconnect();
}catch(IOException e){
e.printStackTrace();
}
}
}
}
}
class fireBtnCheckedChangeEvents implements ToggleButton.OnCheckedChangeListener{
@Override
public void onCheckedChanged(CompoundButton btnView, boolean isChecked){
if(btnView == btn_fire){
if(isChecked == true){
try{
connector.send(open_flag);
}catch (IOException e){
e.printStackTrace();
}
}else{
try{
connector.send(close_flag);
}catch (IOException e){
e.printStackTrace();
}
}
}
}
}
/*class ButtonClickEvent implements View.OnClickListener{
public void onClick(View v) {
if (v == btn_rocker){
// connector.send(edtTxt_Data.getText().toString());
// tv_Msg.append("Client:"+ edtTxt_Data.getText().toString() + "\n");
}
}
}*/
}
四.实测效果
图2 android虚拟摇杆图