指指指指南针
实现指南针能够旋转要求手机内部有地磁传感器和加速度传感器,然后用自定义View画出指南针。通过地磁传感器计算出的角度,通过一系列的计算将这个角度转换成合适的角度后利用自定义View中的角度的set方法来将角度传到自定义View中。在surface中开启一个线程实现指南针的动画。
传感器部分
Android中的传感器的用法其实都很类似。首先第一步要获取到SensorManager的实例
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
SensorManager是系统所有传感器的管理器,调用getDefaultSensor()方法来得到任意的传感器类型。
Sensor magneticSensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
Log.d("data", "magneticSensor的值:" + magneticSensor);
Sensor accelerometerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
Log.d("data", "accelerometerSensor的值 : " + accelerometerSensor);
此时的magneticSensor 就代表着一个地磁传感器,accelerometerSensor 代表着一个加速度传感器。
接下来对传感器输出的信号进行监听,借助SensorEventListener来实现。SensorEventListener是一个接口,其中定义了onSensorChanged()和onAccuaryChanged()这两个方法,仔细看注释。
private SensorEventListener listener = new SensorEventListener() {
float[] accelerometerValues = new float[3];
float[] magneticValues = new float[3];
@Override
public void onSensorChanged(SensorEvent event) {
// 判断当前是加速度传感器还是地磁传感器
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
// 注意赋值时要调用clone()方法,不然accelerometerValues和magneticValues将会指向同一个引用。
accelerometerValues = event.values.clone();
} else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
// 注意赋值时要调用clone()方法,不然accelerometerValues和magneticValues将会指向同一个引用。
magneticValues = event.values.clone();
}
float[] R = new float[9];
float[] values = new float[3];
// 得到一个包含旋转矩阵的R数组,第一个参数R是一个长度为9的float数组,这个方法计算出的旋转数据就会赋值到这个数组中
// ,第二个参数是一个用于将地磁向量转换成重力坐标的旋转矩阵,通常指定为null即可。
// 第三个参数和第四个参数分别就是加速度传感器和地磁传感器输出的values值。
SensorManager.getRotationMatrix(R, null, accelerometerValues, magneticValues);
// 计算手机的旋转数据
// values是一个长度为3的float数组,手机在各个方向上的旋转数据将会存放到这个数组当中
// ,其中values[0]记录着手机围绕Z轴的旋转角度,value[1]和values[2]分别对应X轴和Y轴,这些数据都以弧度为单位,
// 需要用Math.toDegrees(values[0])转换成角度。
SensorManager.getOrientation(R, values);
degree = (float) -Math.toDegrees(values[0]);
myView.setDegree(degree);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
下面需要调用SensorManager的registerListener来注册才能生效,传入三个参数,第一个是SensorEventListener的实例,第二个是Sensor的实例,第三个参数是传感器输出信息的跟新速率。
sensorManager.registerListener(listener, magneticSensor, SensorManager.SENSOR_DELAY_GAME);
sensorManager.registerListener(listener, accelerometerSensor, SensorManager.SENSOR_DELAY_GAME);
最后在onDestory中销毁传感器即可
@Override
protected void onDestroy() {
super.onDestroy();
sensorManager.unregisterListener(listener);
}
这样传感器部分就完成了。
自己画指南针
接下来是自定义View部分,就是自己画个指南针,然后指定“动”的部分然后放到线程里旋转画布就搞定了╮(╯▽╰)╭。
这里涉及到SurfaceView,SurfaceView一般会与SurfaceHolder结合使用,SurfaceHolder用于向与之关联的SurfaceView上绘图,调用SurfaceView的getHolder方法即可获取SurfaceView关联的SurfaceHolder
SurfaceHolder提供了如下方法来获取Canvas对象
- Canvas lockCanvas():锁定整个SurfaceView对象,获取该Surface上的Canvas。
- Canvas lockCanvas(Rect dirty):锁定SurfaceView上Rect划分的区域,获取该Surface上的Canvas
当通过lockCanvas()获取指定了SurfaceView上的Canvas后,接下来程序就可以调用Canvas进行绘图了,Canvas绘图完成后通过unlockCanvasAndPost()来释放绘图,提交所画的图形
public class MyView extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder surfaceHolder;
private Paint mPaint;
private Paint mPaintNorth;
private int heigth;
private int width;
private Thread mThread;
private float degree;
private boolean flag=true;
public void setDegree(float degree) {
this.degree = degree;
}
public MyView(Context context) {
super(context);
surfaceHolder = getHolder();
surfaceHolder.addCallback(this);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
surfaceHolder = getHolder();
surfaceHolder.addCallback(this);
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(5);
mPaint.setAntiAlias(true);
mPaint.setColor(Color.BLACK);
mPaintNorth = new Paint();
mPaintNorth.setColor(Color.RED);
mPaintNorth.setStrokeWidth(10);
mPaintNorth.setAntiAlias(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
heigth = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
setMeasuredDimension(width, heigth);
}
private Canvas canvas;
@Override
public void surfaceCreated(SurfaceHolder holder) {
mThread = new Thread(new Runnable() {
@Override
public void run() {
while (flag) {
canvas = surfaceHolder.lockCanvas();
Log.d("data", "canvas值: "+canvas);
canvas.drawColor(Color.LTGRAY);
canvas.drawLine(width / 2, heigth / 2 - 270, width / 2, heigth / 2 + 270, mPaint);
canvas.drawLine(width / 2 - 270, heigth / 2, width / 2 + 270, heigth / 2, mPaint);
canvas.save();//保存当前状态
canvas.rotate(degree, width / 2, heigth / 2);//绕中心点旋转,实现指针旋转部分
canvas.drawLine(width / 2, heigth / 2 - 204, width / 2, heigth / 2 - 240, mPaintNorth);
canvas.drawCircle(width / 2, heigth / 2, 222, mPaint);
canvas.restore();
surfaceHolder.unlockCanvasAndPost(canvas);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
mThread.start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
flag=false;
}
}
上面的程序还添加了一个CallBack实例,其中定义了如下三个方法
- void surfaceCreated(SurfaceHolder holder):当surface被创建时回调该方法
- void surfaceChanged(SurfaceHolder holder, int format, int width, int height):当一个surface格式或大小发生改变时回调该方法。
- void surfaceDestroyed(SurfaceHolder holder):当surface将要销毁时回调该方法。
控制指南针动的部分
canvas.save();//保存当前状态
canvas.rotate(degree, width / 2, heigth / 2);//绕中心点旋转,实现指针旋转部分
canvas.drawLine(width / 2, heigth / 2 - 204, width / 2, heigth / 2 - 240, mPaintNorth);
canvas.drawCircle(width / 2, heigth / 2, 222, mPaint);
canvas.restore();
活动中的全部代码
package com.example.laowang.mycompas;
import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends Activity {
private SensorManager sensorManager;
private MyView myView;
private float degree;
private SensorEventListener listener = new SensorEventListener() {
float[] accelerometerValues = new float[3];
float[] magneticValues = new float[3];
@Override
public void onSensorChanged(SensorEvent event) {
// 判断当前是加速度传感器还是地磁传感器
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
// 注意赋值时要调用clone()方法,不然accelerometerValues和magneticValues将会指向同一个引用。
accelerometerValues = event.values.clone();
} else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
// 注意赋值时要调用clone()方法,不然accelerometerValues和magneticValues将会指向同一个引用。
magneticValues = event.values.clone();
}
float[] R = new float[9];
float[] values = new float[3];
// 得到一个包含旋转矩阵的R数组,第一个参数R是一个长度为9的float数组,这个方法计算出的旋转数据就会赋值到这个数组中
// ,第二个参数是一个用于将地磁向量转换成重力坐标的旋转矩阵,通常指定为null即可。
// 第三个参数和第四个参数分别就是加速度传感器和地磁传感器输出的values值。
SensorManager.getRotationMatrix(R, null, accelerometerValues, magneticValues);
// 计算手机的旋转数据
// values是一个长度为3的float数组,手机在各个方向上的旋转数据将会存放到这个数组当中
// ,其中values[0]记录着手机围绕Z轴的旋转角度,value[1]和values[2]分别对应X轴和Y轴,这些数据都以弧度为单位,
// 需要用Math.toDegrees(values[0])转换成角度。
SensorManager.getOrientation(R, values);
degree = (float) -Math.toDegrees(values[0]);
myView.setDegree(degree);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myView = (MyView) findViewById(R.id.my_compass_view);
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
Sensor magneticSensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
Log.d("data", "magneticSensor的值:" + magneticSensor);
Sensor accelerometerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
Log.d("data", "accelerometerSensor的值 : " + accelerometerSensor);
sensorManager.registerListener(listener, magneticSensor, SensorManager.SENSOR_DELAY_GAME);
sensorManager.registerListener(listener, accelerometerSensor, SensorManager.SENSOR_DELAY_GAME);
}
@Override
protected void onDestroy() {
super.onDestroy();
sensorManager.unregisterListener(listener);
}
}