好久之前就已经研究了方向传感器Sensor.TYPE_ORIENTATION。根据自已实践,改写了网上的两个水准仪的例子,又重新封装使用了一下,最后也用在了项目中。
1、前言介绍
下面这段话是出自Android 传感器之方向传感器
一般情况下,在android系统中获取手机的方位信息在api中有TYPE_ORIENTATION常量,可以像得到加速度传感器那样得到方向传感器sm.getDefaultSensor(Sensor.TYPE_ORIENTATION);然而我们这样做的话在最新版的SDK中就会看到这么一句话:“TYPE_ORIENTATION This constant is deprecated. use SensorManager.getOrientation() instead. ”即这种方式也过期,不建议使用!Google建议我们在应用程序中使用SensorManager.getOrientation()来获得原始数据。
public static float[] getOrientation (float[] R, float[] values)
第一个参数是R[] 是一个旋转矩阵,用来保存磁场和加速度的数据,可以理解为这个函数的传入值,通过它这个函数给你求出方位角。
第二个参数就是这个函数的输出了,他有函数自动为我们填充,这就是我们想要的。
values[0] :方向角,但用(磁场+加速度)得到的数据范围是(-180~180),也就是说,0表示正北,90表示正东,180/-180表示正南,-90表示正西。而直接通过方向感应器数据范围是(0~359)360/0表示正北,90表示正东,180表示正南,270表示正西。
values[1] pitch 倾斜角 即由静止状态开始,前后翻转,手机顶部往上抬起(0~-90),手机尾部往上抬起(0~90)
values[2] roll 旋转角 即由静止状态开始,左右翻转,手机左侧抬起(0~90),手机右侧抬起(0~-90)
2、示例代码一
package com.level.level1;
import com.level.level1.R;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
public class LevelView extends View
{
// 定义水平仪大圆盘图片
Bitmap compass;
// 定义水平仪中的气泡图标
Bitmap ball;
// 定义水平仪中气泡 的X、Y座标
int ballX, ballY;
// 定义气泡位于中间时(水平仪完全水平),气泡的X、Y座标
int cx, cy;
// 定义水平仪大圆盘中心座标X、Y
int backCx;
int backCy;
// 定义灵敏度,即水平仪能处理的最大倾斜角,超过该角度,气泡将直接在位于边界。
int SENSITIVITY = 30;
public LevelView(Context context, AttributeSet attrs)
{
super(context, attrs);
// 加载水平仪大圆盘图片和气泡图片
compass = BitmapFactory.decodeResource(getResources()
, R.drawable.back);
ball = BitmapFactory
.decodeResource(getResources(), R.drawable.small);
// 计算出 水平仪完全水平时 气泡位置 左上角为原点
cx = (compass.getWidth() - ball.getWidth()) / 2;
cy = (compass.getHeight() - ball.getHeight()) / 2;
// 计算出水平仪大圆盘中心座标X、Y 左上角为原点
backCx = compass.getWidth() / 2;
backCy = compass.getWidth() / 2;
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
// 绘制水平仪大圆盘图片
canvas.drawBitmap(compass, 0, 0, null);
// 根据气泡坐标绘制气泡
canvas.drawBitmap(ball, ballX, ballY, null);
}
public void onChangeXY(int zAngle,int yAngle,int xAngle){
// 定义气泡当前位置X Y坐标值
int x, y;
x = cx;
y = cy;
// 如果沿x轴的倾斜角 在最大角度之内则计算出其相应坐标值
if (Math.abs(xAngle) <= SENSITIVITY) {
// 根据与x轴的倾斜角度计算X座标的变化值(倾斜角度越大,X座标变化越大)
int deltaX = (int) (cx * xAngle / SENSITIVITY);
x += deltaX;
}
// 如果沿x轴的倾斜角已经大于MAX_ANGLE,气泡应到最左边
else if (xAngle > SENSITIVITY) {
x = 0;
}
// 如果与x轴的倾斜角已经小于负的MAX_ANGLE,气泡应到最右边
else {
x = cx * 2;
}
// 如果沿Y轴的倾斜角还在最大角度之内
if (Math.abs(yAngle) <= SENSITIVITY) {
// 根据沿Y轴的倾斜角度计算Y座标的变化值(倾斜角度越大,Y座标变化越大)
int deltaY = (int) (cy * yAngle / SENSITIVITY);
y += deltaY;
}
// 如果沿Y轴的倾斜角已经大于MAX_ANGLE,气泡应到最下边
else if (yAngle > SENSITIVITY) {
y = cy * 2;
}
// 如果沿Y轴的倾斜角已经小于负的MAX_ANGLE,气泡应到最右边
else {
y = 0;
}
// 如果计算出来的X、Y座标还位于水平仪的仪表盘内,更新水平仪的气泡座标
if (isContain(x, y)) {
ballX = x;
ballY = y;
} else {
// 有待后续继续完成
}
//重绘界面
invalidate();
}
// 计算x、y点的气泡是否处于水平仪的大圆盘内
private boolean isContain(int x, int y) {
// 计算气泡的圆心座标X、Y
int ballCx = x + ball.getWidth() / 2;
int ballCy = y + ball.getWidth() / 2;
// 计算气泡的圆心与水平仪大圆盘中中心之间的距离。
double distance = Math.sqrt((ballCx - backCx) * (ballCx - backCx)
+ (ballCy - backCy) * (ballCy - backCy));
// 若两个圆心的距离小于它们的半径差,即可认为处于该点的气泡依然位于仪表盘内
if (distance < cx) {
return true;
} else {
return false;
}
}
}
package com.level.level1;
import com.level.level1.R;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.app.Activity;
import android.widget.TextView;
public class MyGradienter extends Activity implements SensorEventListener {
// 定义水平仪的仪大圆盘
private LevelView myview;
// 定义Sensor管理器
SensorManager mySM;
// 定义显示栏 显示X Y Z轴方向转过角度与当前方位
private TextView tx, ty, tz, td;
private Sensor acc_sensor;
private Sensor mag_sensor;
// 加速度传感器数据
float accValues[] = new float[3];
// 地磁传感器数据
float magValues[] = new float[3];
// 旋转矩阵,用来保存磁场和加速度的数据
float r[] = new float[9];
// 模拟方向传感器的数据(原始数据为弧度)
float values[] = new float[3];
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_gradienter);
myview = (LevelView) findViewById(R.id.myview);
tx = (TextView) findViewById(R.id.testviewx);
ty = (TextView) findViewById(R.id.testviewy);
tz = (TextView) findViewById(R.id.testviewz);
td = (TextView) findViewById(R.id.testviewd);
// 获取手机传感器管理服务
mySM = (SensorManager) getSystemService(SENSOR_SERVICE);
}
@Override
public void onResume() {
super.onResume();
acc_sensor = mySM.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mag_sensor = mySM.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
// 给传感器注册监听:
mySM.registerListener(this, acc_sensor, SensorManager.SENSOR_DELAY_GAME);
mySM.registerListener(this, mag_sensor, SensorManager.SENSOR_DELAY_GAME);
}
@Override
protected void onPause() {
// 取消方向传感器的监听
mySM.unregisterListener(this);
super.onPause();
}
@Override
protected void onStop() {
// 取消方向传感器的监听
mySM.unregisterListener(this);
super.onStop();
}
@Override
public void onAccuracyChanged(Sensor arg0, int arg1) {
// TODO Auto-generated method stub
}
@Override
public void onSensorChanged(SensorEvent event) {
// 获取手机触发event的传感器的类型
int sensorType = event.sensor.getType();
switch (sensorType) {
case Sensor.TYPE_ACCELEROMETER:
accValues = event.values.clone();
break;
case Sensor.TYPE_MAGNETIC_FIELD:
magValues = event.values.clone();
break;
}
SensorManager.getRotationMatrix(r, null, accValues, magValues);
SensorManager.getOrientation(r, values);
// 获取 沿着Z轴转过的角度
int zAngle = (int) Math.toDegrees(values[0]);
tz.setText("Z轴方向转过的角度:" + zAngle);
// 显示当前的方位
displayCompass(zAngle);
// 获取 沿着X轴倾斜时 与Y轴的夹角
int yAngle = (int) Math.toDegrees(values[1]);
ty.setText("Y轴方向翘起的角度:" + yAngle);
// 获取 沿着Y轴的滚动时 与X轴的角度
int xAngle = (int) Math.toDegrees(values[2]);
tx.setText("x轴方向翘起的角度:" + xAngle);
myview.onChangeXY(zAngle,yAngle,xAngle);
}
private void displayCompass(int angle) {
if ((angle < 22.5) || (angle > 337.5))
td.setText("手机顶部当前方位: 北");
if ((angle > 22.5) && (angle < 67.5))
td.setText("手机顶部当前方位: 西北");
if ((angle > 67.5) && (angle < 112.5))
td.setText("手机顶部当前方位: 西");
if ((angle > 112.5) && (angle < 157.5))
td.setText("手机顶部当前方位: 西北");
if ((angle > 157.5) && (angle < 202.5))
td.setText("手机顶部当前方位: 南");
if ((angle > 202.5) && (angle < 247.5))
td.setText("手机顶部当前方位: 东南");
if ((angle > 247.5) && (angle < 292.5))
td.setText("手机顶部当前方位: 东");
if ((angle > 292.5) && (angle < 337.5))
td.setText("手机顶部当前方位: 东北");
}
}
3、示例代码二
package com.tcjt.level2;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
public class MainView extends View {
Paint paint = new Paint(); //画笔
Bitmap shangBitmap1; //上面的大矩形图
Bitmap shangBitmap2; //上面的气泡
Bitmap zuoBitmap1; //左面的大矩形图
Bitmap zuoBitmap2; //左面图的气泡
Bitmap zhongBitmap1; //中间的大圆图
Bitmap zhongBitmap2; //中间的小气泡
Bitmap xiaBitmap1; //右下的矩形图
Bitmap xiaBitmap2; //右下的气泡
//背景矩形的位置声明
int shang1_X = 60; //上面的大矩形图
int shang1_Y = 12;
int zuo1_X = 12; //左面的大矩形图
int zuo1_Y = 60;
int zhong1_X = 65; //中间的大圆图
int zhong1_Y = 65;
int xia1_X = 145; //右下的矩形图
int xia1_Y = 145;//水泡的位置声明
int shang2_X; //上面的气泡XY 坐标
int shang2_Y;
int zuo2_X; //左面图的气泡XY 坐标
int zuo2_Y;
int zhong2_X; //中间的小气泡XY 坐标
int zhong2_Y;
int xia2_X; //右下的气泡XY 坐标
int xia2_Y;
public MainView(Context context, AttributeSet attrs){
super(context, attrs);
initBitmap(); //初始化图片资源
initLocation(); //初始化气泡的位置
}
private void initBitmap(){ //初始化图片的方法
//该处省略了部分代码,将在后面进行介绍
shangBitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.level_shang1);
shangBitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.level_shang2);
zuoBitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.level_zuo1);
zuoBitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.level_zuo2);
zhongBitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.level_zhong1);
zhongBitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.level_zhong2);
xiaBitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.level_xia1);
xiaBitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.level_xia2);
}
private void initLocation(){ //初始化气泡位置的方法
//该处省略了部分代码,将在后面进行介绍
shang2_X = shang1_X + shangBitmap1.getWidth()/2- shangBitmap2.getWidth()/2;
shang2_Y = shang1_Y + shangBitmap1.getHeight()/2- shangBitmap2.getHeight()/2;
zuo2_X = zuo1_X + zuoBitmap1.getWidth()/2- zuoBitmap2.getWidth()/2;
zuo2_Y = zuo1_Y + zuoBitmap1.getHeight()/2- zuoBitmap2.getHeight()/2;
zhong2_X = zhong1_X + zhongBitmap1.getWidth()/2- zhongBitmap2.getWidth()/2;
zhong2_Y = zhong1_Y + zhongBitmap1.getHeight()/2- zhongBitmap2.getHeight()/2;
xia2_X = xia1_X + xiaBitmap1.getWidth()/2- xiaBitmap2.getWidth()/2;
xia2_Y = xia1_Y + xiaBitmap1.getHeight()/2- xiaBitmap2.getHeight()/2;
}
@Override
protected void onDraw(Canvas canvas){//重写的绘制方法
super.onDraw(canvas);
//该处省略了部分代码,将在后面进行介绍
canvas.drawColor(Color.WHITE); //设置背景色为白色
paint.setColor(Color.BLUE); //设置画笔颜色
paint.setStyle(Style.STROKE); //设置画笔为不填充
canvas.drawRect(5, 5, 315, 315, paint);//绘制外边框矩形
//画背景矩形
canvas.drawBitmap(shangBitmap1, shang1_X,shang1_Y, paint); //上
canvas.drawBitmap(zuoBitmap1, zuo1_X,zuo1_Y, paint); //左
canvas.drawBitmap(zhongBitmap1, zhong1_X,zhong1_Y, paint); //中
canvas.drawBitmap(xiaBitmap1, xia1_X,xia1_Y, paint); //下
//开始绘制气泡
canvas.drawBitmap(shangBitmap2, shang2_X,shang2_Y, paint); //上
canvas.drawBitmap(zuoBitmap2, zuo2_X,zuo2_Y, paint); //左
canvas.drawBitmap(zhongBitmap2, zhong2_X,zhong2_Y, paint); //中
canvas.drawBitmap(xiaBitmap2, xia2_X, xia2_Y, paint);//下
paint.setColor(Color.GRAY);//设置画笔颜色用来绘制刻度
//绘制上面方框中的刻度
canvas.drawLine (shang1_X+shangBitmap1.getWidth()/2-7,shang1_Y, shang1_X+shangBitmap1.getWidth()/2-7,shang1_Y+shangBitmap1.getHeight()-2, paint);
canvas.drawLine (shang1_X+shangBitmap1.getWidth()/2+7,shang1_Y, shang1_X+shangBitmap1.getWidth()/2+7,shang1_Y+shangBitmap1.getHeight()-2, paint);
//绘制左面方框中的刻度
canvas.drawLine(zuo1_X,zuo1_Y+zuoBitmap1.getHeight()/2-7,zuo1_X+zuoBitmap1.getWidth()-2,zuo1_Y+zuoBitmap1.getHeight()/2-7, paint);canvas.drawLine(zuo1_X,zuo1_Y+zuoBitmap1.getHeight()/2+7,zuo1_X+zuoBitmap1.getWidth()-2,zuo1_Y+zuoBitmap1.getHeight()/2+7, paint);
//绘制下面方框中的刻度
canvas.drawLine(xia1_X+xiaBitmap1.getWidth()/2-10,xia1_Y+xiaBitmap1.getHeight()/2-20,xia1_X+xiaBitmap1.getWidth()/2+20,xia1_Y+xiaBitmap1.getHeight()/2+10, paint);
canvas.drawLine(xia1_X+xiaBitmap1.getWidth()/2-20,xia1_Y+xiaBitmap1.getHeight()/2-10,xia1_X+xiaBitmap1.getWidth()/2+10,xia1_Y+xiaBitmap1.getHeight()/2+20, paint);
//中间圆圈中的刻度(小圆)
RectF oval = new RectF(zhong1_X+zhongBitmap1.getWidth()/2-10,zhong1_Y+zhongBitmap1.getHeight()/2-10,zhong1_X+zhongBitmap1.getWidth()/2+10,zhong1_Y+zhongBitmap1.getHeight()/2+10);
canvas.drawOval(oval, paint);//绘制基准线(圆)
}
}
package com.tcjt.level2;
import android.hardware.SensorListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
@SuppressWarnings("deprecation")
public class LevelActivity extends Activity {
int k = 45; //灵敏度
MainView mv;
//真机
SensorManager mySensorManager;
//测试时
// SensorManagerSimulator mySensorManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_level);
mv = (MainView) findViewById(R.id.mainView);
mySensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);//真机
}
private final SensorListener mSensorLisener =new SensorListener(){
@Override
public void onAccuracyChanged(int sensor, int accuracy) { }
public boolean isContain(int x, int y){//判断点是否在圆内
int tempx =(int) (x + mv.zhongBitmap2.getWidth()/2.0);
int tempy =(int) (y + mv.zhongBitmap2.getWidth()/2.0);
int ox = (int) (mv.zhong1_X+ mv.zhongBitmap1.getWidth()/2.0);
int oy = (int) (mv.zhong1_X+ mv.zhongBitmap1.getWidth()/2.0);
if(Math.sqrt((tempx-ox)*(tempx-ox)+(tempy-oy)*(tempy-oy))>(mv.zhongBitmap1.getWidth()/2.0-mv.zhongBitmap2.getWidth()/2.0)){
//不在圆内
return false;
}else{
//在圆内时
return true;
}
}
@Override
public void onSensorChanged(int sensor, float[] values) {
if(sensor == SensorManager.SENSOR_ORIENTATION){
double pitch = values[SensorManager.DATA_Y];
double roll = values[SensorManager.DATA_Z];
int x=0; int y=0;//临时变量,算中间水泡坐标时用
int tempX=0; int tempY=0;//下面气泡的临时变量
//开始调整x 的值
if(Math.abs(roll)<=k){
mv.shang2_X = mv.shang1_X //上面的
+ (int)(((mv.shangBitmap1.getWidth()
-mv.shangBitmap2.getWidth())/2.0)
-(((mv.shangBitmap1.getWidth()
-mv.shangBitmap2.getWidth())/2.0)*roll)/k);
x = mv.zhong1_X //中间的
+ (int)(((mv.zhongBitmap1.getWidth()
-mv.zhongBitmap2.getWidth())/2.0)
-(((mv.zhongBitmap1.getWidth()
-mv.zhongBitmap2.getWidth())/2.0)*roll)/k);
}else if(roll>k){
mv.shang2_X=mv.shang1_X; x = mv.zhong1_X;
}else{
mv.shang2_X=mv.shang1_X+
mv.shangBitmap1.getWidth()
- mv.shangBitmap2.getWidth();
x = mv.zhong1_X+ mv.zhongBitmap1.getWidth()
- mv.zhongBitmap2.getWidth();
}
//开始调整y 的值
if(Math.abs(pitch)<=k){
mv.zuo2_Y=mv.zuo1_Y //左面的
+ (int)(((mv.zuoBitmap1.getHeight()
-mv.zuoBitmap2.getHeight())/2.0)
+(((mv.zuoBitmap1.getHeight()
-mv.zuoBitmap2.getHeight())/2.0)*pitch)/k);
y =mv.zhong1_Y+ //中间的
(int)(((mv.zhongBitmap1.getHeight()
-mv.zhongBitmap2.getHeight())/2.0)
+(((mv.zhongBitmap1.getHeight()
-mv.zhongBitmap2.getHeight())/2.0)*pitch)/k);
}else if(pitch>k){
mv.zuo2_Y=mv.zuo1_Y
+mv.zuoBitmap1.getHeight()
-mv.zuoBitmap2.getHeight();
y=mv.zhong1_Y+mv.zhongBitmap1.getHeight()
-mv.zhongBitmap2.getHeight();
}else{
mv.zuo2_Y = mv.zuo1_Y; y = mv.zhong1_Y;
}
//下面的
tempX = -(int) (((mv.xiaBitmap1.getWidth()/2-28)*roll
+(mv.xiaBitmap1.getWidth()/2-28)*pitch)/k);
tempY = -(int) ((-(mv.xiaBitmap1.getWidth()/2-28)*roll
-(mv.xiaBitmap1.getWidth()/2-28)*pitch)/k);
//限制下面的气泡范围
if(tempY>mv.xiaBitmap1.getHeight()/2-28){
tempY = mv.xiaBitmap1.getHeight()/2-28;
}
if(tempY < -mv.xiaBitmap1.getHeight()/2+28){
tempY = -mv.xiaBitmap1.getHeight()/2+28;
}
if(tempX > mv.xiaBitmap1.getWidth()/2-28){
tempX = mv.xiaBitmap1.getWidth()/2-28;
}
if(tempX < -mv.xiaBitmap1.getWidth()/2+28){
tempX = -mv.xiaBitmap1.getWidth()/2+28;
}
mv.xia2_X = tempX + mv.xia1_X + mv.xiaBitmap1.getWidth()/2 -mv.xiaBitmap2.getWidth()/2;
mv.xia2_Y = tempY + mv.xia1_Y + mv.xiaBitmap1.getHeight()/2 - mv.xiaBitmap2.getWidth()/2;
if(isContain(x, y)){//中间的水泡在圆内才改变坐标
mv.zhong2_X = x; mv.zhong2_Y = y;
}
mv.postInvalidate();//重绘MainView
}
} //传感器监听器类
//该处省略了部分代码,将在后面进行介绍
};
@Override
protected void onResume(){ //添加监听
mySensorManager.registerListener(mSensorLisener,SensorManager.SENSOR_ORIENTATION);
super.onResume();
}
@Override
protected void onPause() { //取消监听
mySensorManager.unregisterListener (mSensorLisener);
super.onPause();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_level, menu);
return true;
}
}
两个例子都能实现水准仪的效果。图片我就不贴,可以参考里面的代码。
博客代码下载:。有所封装代码。
参考文章:
Android 传感器之方向传感器
关于Android 传感器坐标与读数的进一步讨论