从这篇文章开始,主要记录android 传感器相关的知识:
这是这个系列的第一篇,主要讲解android 主要的传感器,以及方向传感器详解。
官网上我们看到,android中涉及的传感器主要有
1:Motion sensors
这些传感器沿三个轴测量加速力和旋转力。此类别包括加速度计,重力传感器,陀螺仪和旋转矢量传感器。
2:Position sensors
这些传感器测量各种环境参数,例如环境空气温度和压力,照明和湿度。此类别包括气压计,光度计和温度计。
3:Environment sensrs
这些传感器测量设备的物理位置。此类别包括方向传感器和磁力计。
下面这个截图是android平台支持的传感器类型。
传感器部分主要用到几个类: 1:sensor 2:sensorManager 3:SensorEvent 4:SensorEventListener
我们先通过sensorManger获取下android设备支持的sensor列表,同时熟悉下对应的方法。
第一步:获取传感器管理对象。
manager= (SensorManager) getSystemService(SENSOR_SERVICE);
第二步:获取所有支持的sensor
// 得到设备支持的所有传感器的List
sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
第三步:编写adapter,展示。这里展示的sensor的name属性。
这里我是在模拟器运行的。4.4的设备,所以支持的较少。
这一部分具体介绍传感器的方向传感器。
具体对应的就是
我们通过TYPE_ORIENTATION来获取方向传感器对象,具体获取方法如下:
//获取传感器管理对象
manager= (SensorManager) getSystemService(SENSOR_SERVICE);
//获取方向传感器
orientationSensor=manager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
获取到对象后,我们需要在onresume()方法中注册监听事件,在onpause()中解除监听。
protected void onResume() {
super.onResume();
if (orientationSensor!=null){
//三个参数分别是SensorEventListener,sensor,速率
manager.registerListener(this,orientationSensor,SensorManager.SENSOR_DELAY_NORMAL);
}
}
@Override
protected void onPause() {
super.onPause();
if (manager!=null){
manager.unregisterListener(this);
}
}
注册监听时第三个参数指的是延迟时间:有四种类型
/** get sensor data as fast as possible */
public static final int SENSOR_DELAY_FASTEST = 0;
/** rate suitable for games */
public static final int SENSOR_DELAY_GAME = 1;
/** rate suitable for the user interface */
public static final int SENSOR_DELAY_UI = 2;
/** rate (default) suitable for screen orientation changes */
public static final int SENSOR_DELAY_NORMAL = 3;
查看源码,我们可以得到每个类型对应的延迟时间。
private static int getDelay(int rate) {
int delay = -1;
switch (rate) {
case SENSOR_DELAY_FASTEST:
delay = 0;
break;
case SENSOR_DELAY_GAME:
delay = 20000;
break;
case SENSOR_DELAY_UI:
delay = 66667;
break;
case SENSOR_DELAY_NORMAL:
delay = 200000;
break;
default:
delay = rate;
break;
}
return delay;
}
具体的数据在SensorEventListener的返回方法中,下面是对应的方法。
根据SensorEvent.values获取返回的数据,我们可以看到返回的是一个数组。
//当有新的传感器事件时调用
@Override
public void onSensorChanged(SensorEvent event) {
float[] values = event.values;
StringBuilder sb=new StringBuilder();
sb.append("z轴:"+values[0]+"\n");
sb.append("x轴:"+values[1]+"\n");
sb.append("y轴:"+values[2]+"\n");
binding.tvValue.setText("旧API:\n"+sb.toString());
}
//当已注册传感器的精度发生变化时调用
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
运行下看下结果:
当我们使用TYPE_ORIENTATION时,会发现这是个已经过时的方法,那最新的获取设备方向的方法是什么呢?
/**
* A constant describing an orientation sensor type.
* See {@link android.hardware.SensorEvent#values SensorEvent.values}
* for more details.
*
* @deprecated use {@link android.hardware.SensorManager#getOrientation
* SensorManager.getOrientation()} instead.
*/
@Deprecated
public static final int TYPE_ORIENTATION = 3;
查看源码我们发现,官方已经给出了替代的方法,那就是使用SensorManager.getOrientation()。
那这个方法具体是怎么使用,或者返回的是什么呢?
/* @param R
* rotation matrix see {@link #getRotationMatrix}.
*
* @param values
* an array of 3 floats to hold the result.
*
*/
public static float[] getOrientation(float[] R, float[] values) {
/*
* 4x4 (length=16) case:
* / R[ 0] R[ 1] R[ 2] 0 \
* | R[ 4] R[ 5] R[ 6] 0 |
* | R[ 8] R[ 9] R[10] 0 |
* \ 0 0 0 1 /
*
* 3x3 (length=9) case:
* / R[ 0] R[ 1] R[ 2] \
* | R[ 3] R[ 4] R[ 5] |
* \ R[ 6] R[ 7] R[ 8] /
*
*/
if (R.length == 9) {
values[0] = (float) Math.atan2(R[1], R[4]);
values[1] = (float) Math.asin(-R[7]);
values[2] = (float) Math.atan2(-R[6], R[8]);
} else {
values[0] = (float) Math.atan2(R[1], R[5]);
values[1] = (float) Math.asin(-R[9]);
values[2] = (float) Math.atan2(-R[8], R[10]);
}
return values;
}
从源码这里我们可以看到这个方法有两个参数,
R
用来保存磁场和加速度的数据,通过该函数获取方位角。也就是说第二个参数就是我们需要的模拟方向的数据。
另外第一个参数数组R我们可以看到是通过getRotationMatrix()获取到的。
public static boolean getRotationMatrix(float[] R, float[] I,
float[] gravity, float[] geomagnetic) {
// TODO: move this to native code for efficiency
float Ax = gravity[0];
float Ay = gravity[1];
float Az = gravity[2];
final float normsqA = (Ax * Ax + Ay * Ay + Az * Az);
final float g = 9.81f;
final float freeFallGravitySquared = 0.01f * g * g;
if (normsqA < freeFallGravitySquared) {
// gravity less than 10% of normal value
return false;
}
final float Ex = geomagnetic[0];
final float Ey = geomagnetic[1];
final float Ez = geomagnetic[2];
float Hx = Ey * Az - Ez * Ay;
float Hy = Ez * Ax - Ex * Az;
float Hz = Ex * Ay - Ey * Ax;
final float normH = (float) Math.sqrt(Hx * Hx + Hy * Hy + Hz * Hz);
if (normH < 0.1f) {
// device is close to free fall (or in space?), or close to
// magnetic north pole. Typical values are > 100.
return false;
}
final float invH = 1.0f / normH;
Hx *= invH;
Hy *= invH;
Hz *= invH;
final float invA = 1.0f / (float) Math.sqrt(Ax * Ax + Ay * Ay + Az * Az);
Ax *= invA;
Ay *= invA;
Az *= invA;
final float Mx = Ay * Hz - Az * Hy;
final float My = Az * Hx - Ax * Hz;
final float Mz = Ax * Hy - Ay * Hx;
if (R != null) {
if (R.length == 9) {
R[0] = Hx; R[1] = Hy; R[2] = Hz;
R[3] = Mx; R[4] = My; R[5] = Mz;
R[6] = Ax; R[7] = Ay; R[8] = Az;
} else if (R.length == 16) {
R[0] = Hx; R[1] = Hy; R[2] = Hz; R[3] = 0;
R[4] = Mx; R[5] = My; R[6] = Mz; R[7] = 0;
R[8] = Ax; R[9] = Ay; R[10] = Az; R[11] = 0;
R[12] = 0; R[13] = 0; R[14] = 0; R[15] = 1;
}
}
if (I != null) {
// compute the inclination matrix by projecting the geomagnetic
// vector onto the Z (gravity) and X (horizontal component
// of geomagnetic vector) axes.
final float invE = 1.0f / (float) Math.sqrt(Ex * Ex + Ey * Ey + Ez * Ez);
final float c = (Ex * Mx + Ey * My + Ez * Mz) * invE;
final float s = (Ex * Ax + Ey * Ay + Ez * Az) * invE;
if (I.length == 9) {
I[0] = 1; I[1] = 0; I[2] = 0;
I[3] = 0; I[4] = c; I[5] = s;
I[6] = 0; I[7] = -s; I[8] = c;
} else if (I.length == 16) {
I[0] = 1; I[1] = 0; I[2] = 0;
I[4] = 0; I[5] = c; I[6] = s;
I[8] = 0; I[9] = -s; I[10] = c;
I[3] = I[7] = I[11] = I[12] = I[13] = I[14] = 0;
I[15] = 1;
}
}
return true;
}
我们看到getRotationMatrix()这个方法有四个参数,这四个参数分别是什么呢?
1:float[] R 是一个包含旋转矩阵的浮点数组,也是要填充的数组
2:float[] I 将磁场数据转换进实际的重力坐标中,一般传null。
3:float[] gravity 加速度传感器数据 数组长度3
4:float[] geomagnetic 地磁传感器数据 长度 3
具体使用步骤
首先定义参数:
//加速度传感器数据
private final float[] accelerometerReading = new float[3];
//地磁传感器数据
private final float[] magnetometerReading = new float[3];
//旋转矩阵,用来保存磁场和加速度的数据
private final float[] rotationMatrix = new float[9];
//方向数据
private final float[] orientationAngles = new float[3];
注册listener:
@Override
protected void onResume() {
super.onResume();
//加速度传感器
Sensor accelerometer = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (accelerometer != null) {
manager.registerListener(this, accelerometer,
SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI);
}
//地磁传感器
Sensor magneticField = manager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
if (magneticField != null) {
manager.registerListener(this, magneticField,
SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI);
}
}
//另外这几行代码是在官网上复制的,但是有个问题,官网两个传感器在获取对象时传的type是同一个,注意下。。。。。
@Override
public void onSensorChanged(SensorEvent event) {
//判断sensor类型
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
System.arraycopy(event.values, 0, accelerometerReading,
0, accelerometerReading.length);
} else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
System.arraycopy(event.values, 0, magnetometerReading,
0, magnetometerReading.length);
}
updateOrientationAngles();
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
public void updateOrientationAngles() {
// 更新旋转矩阵.
// 参数1:
// 参数2 :将磁场数据转换进实际的重力坐标中,一般默认情况下可以设置为null
// 参数3:加速度
// 参数4:地磁
SensorManager.getRotationMatrix(rotationMatrix, null,
accelerometerReading, magnetometerReading);
//根据旋转矩阵计算设备的方向
//参数1:旋转数组
//参数2:模拟方向传感器的数据
SensorManager.getOrientation(rotationMatrix, orientationAngles);
StringBuilder sb=new StringBuilder();
if (orientationAngles.length>=3){
sb.append("z轴:"+orientationAngles[0]+"\n");
sb.append("x轴:"+orientationAngles[1]+"\n");
sb.append("y轴:"+orientationAngles[2]+"\n");
binding.tvValueNew.setText("\n新API:\n"+sb.toString());
}
}
看下结果:
另外加个指南针图片,根据获取的degrees旋转。
//顺时针转动为正,故手机顺时针转动时,图片得逆时针转动
//让图片相对自身中心点转动,开始角度默认为0;此后开始角度等于上一次结束角度
RotateAnimation ra = new RotateAnimation(fromDegrees, -degrees, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
//动画时间200毫秒
ra.setDuration(200);
ra.setFillAfter(true);
binding.ivCompass.startAnimation(ra);
fromDegrees = -degrees;
最后附上git的地址: