水平仪

来源:互联网 发布:mac照片幻灯片 编辑:程序博客网 时间:2024/04/29 02:30

一 原理分析

     在前一篇介绍指南针的实现时,详细说明了方向传感器的三个角度值意义,接下来我们使用方向传感器简单实现下水平仪,而实现水平仪是利用方向传感器的第二个和第三个角度值,当手机顶部翘起时,气泡应该向顶部移动,也就是气泡的位置的Y坐标变小;当手机底部翘起时,气泡应该向底部移动,也就是气泡的位置的Y坐标变大;当手机左侧翘起时,气泡应该向左侧移动,也就是气泡的位置的X坐标变小;当手机右侧翘起时,气泡向右侧移动,也就是气泡的位置的X坐标变大。这样就可以现实手机哪端翘起,水平仪中的气泡就会向哪个方向移动。

二 代码实现

1 自定义View,在该View中绘制仪表盘和气泡
public class GradienterView extends View{
/** 定义 水平仪 仪表盘图片*/
public Bitmap back;
/** 定义 水平仪 气泡图片*/
public Bitmap bubble;
/** 定义水平仪中 气泡的X、Y 坐标*/
public int bubbleX,bubbleY;

public GradienterView(Context context) {
super(context);
}
public GradienterView(Context context, AttributeSet attrs) {
this(context,attrs,0);
}
public GradienterView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
back = BitmapFactory.decodeResource(getResources(),R.mipmap.icon_g);
bubble = BitmapFactory.decodeResource(getResources(),R.mipmap.icon_bubble);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(back,0,0,null);
canvas.drawBitmap(bubble,bubbleX,bubbleY,null);
}
}
2 在布局中使用上述View
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.ecric.sensor.GradienterActivity">
<com.ecric.sensor.GradienterView
android:id="@+id/show"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
3 获取传感器管理服务及find到布局中的View
show = (GradienterView) findViewById(R.id.show);
sm = (SensorManager) getSystemService(SENSOR_SERVICE);
4 获取当前设备支持的所有传感器
//获取手机上支持的所有传感器
deviceSensors = sm.getSensorList(Sensor.TYPE_ALL);
5 判断当前设备是否支持方向传感器,如果有就获取方向传感器
if (!check(Sensor.TYPE_ORIENTATION)) {
Toast.makeText(this, "当前设备不支持方向传感器", Toast.LENGTH_SHORT).show();
}else {
orientation = sm.getDefaultSensor(Sensor.TYPE_ORIENTATION);
}
判断方法实现如下:
/**
* 检测设备是否支持某类型的传感器
*
* @param type 传感器的类型
* @return true 当前设备支持该类型的传感器 否则不支持
*/
private boolean check(int type) {
Iterator<Sensor> iterator = deviceSensors.iterator();
while (iterator.hasNext()) {
Sensor sensor = iterator.next();
if (sensor.getType() == type) {
return true;
}
}
return false;
}
6 给方向传感器设置监听
@Override
protected void onResume() {
super.onResume();
if (orientation != null){
sm.registerListener(this,orientation,SensorManager.SENSOR_DELAY_FASTEST);
}
}
说明,这里直接使Activity实现了SensorEventListener接口。
7 实现SensorEventListener的抽象方法获取手机绕Y、X轴的旋转角度,根据该角度值计算气泡需要移动的位置
@Override
public void onSensorChanged(SensorEvent event) {
float[] values = event.values;
switch (event.sensor.getType()){
case Sensor.TYPE_ORIENTATION:
//获取与y轴的夹角
float AngleY = values[1];
//获取与z轴的夹角
float Anglez = values[2];
//气泡位于中间时(水平仪完全水平), 气泡的位置x,y坐标
int x = (show.back.getWidth() - show.bubble.getWidth()) / 2 ;
int y = (show.back.getHeight() - show.bubble.getHeight()) / 2 ;
//如果与Z轴的倾斜角还在最大角度之内
if (Math.abs(Anglez) <= MAX_ANGLE){
//根据与Z轴的倾斜角度计算x坐标的变化值(倾斜角度越大,变化越大)
int deltaX = (int) ((show.back.getWidth() - show.bubble.getWidth()) / 2 * Anglez / MAX_ANGLE);
x += deltaX;
}else if (Anglez > MAX_ANGLE){//如果与z轴的倾斜角已经大于MAX_ANGLE,气泡应到左边
x = 0;
}else {//如果与Z轴的倾斜角已经小于负的MAX_ANGLE 气泡应到最右边
x = show.back.getWidth() - show.bubble.getWidth();
}

//如果与Y轴的倾斜角还在最大角度之内
if (Math.abs(AngleY) <= MAX_ANGLE){
//根据与y轴的倾斜角度计算x坐标的变化值(倾斜角度越大,变化越大)
int deltaY = (int) ((show.back.getHeight() - show.bubble.getHeight()) / 2 * AngleY / MAX_ANGLE);
y += deltaY;
}else if (AngleY > MAX_ANGLE){//如果与y轴的倾斜角已经大于MAX_ANGLE,气泡应到左边
y = show.back.getHeight() - show.bubble.getHeight();
}else {//如果与y轴的倾斜角已经小于负的MAX_ANGLE 气泡应到最右边
y = 0;
}

if (isContain(x,y)){
show.bubbleX = x;
show.bubbleY = y;
}
show.postInvalidate();
break;
}
}

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {

}
判断x、y坐标是否在仪表盘内的方法如下:
private boolean isContain(int x,int y){
//计算气泡的愿新坐标x,y
int bubbleCX = x + show.bubble.getWidth() / 2;
int bubbleCY = y + show.bubble.getHeight() / 2;
//计算水平仪仪表盘的圆心坐标x,y
int backCX = show.back.getWidth() / 2;
int backCY = show.back.getHeight() / 2;
//计算气泡的圆心与水平仪仪表盘的圆心之间的距离
double distance = Math.sqrt((bubbleCX - backCX) * (bubbleCX - backCX) + (bubbleCY - backCY) * (bubbleCY - backCY));
//若两个圆心的距离小于它们的半径差,即可认为处于该点的气泡依然位于仪表盘内
if (distance < (show.back.getWidth() - show.bubble.getWidth()) / 2){
return true;
}
return false;
}
8 在Activity的onStop的方法中反注册监听
@Override
protected void onStop() {
super.onStop();
if (orientation != null){
sm.unregisterListener(this);
}
}
至此实现了简单的水平仪。下面贴出完整代码:
public class GradienterActivity extends AppCompatActivity implements SensorEventListener{
GradienterView show;
//定义水平仪能处理的最大倾斜角,超过该角度,气泡将直接位于边界
int MAX_ANGLE = 30;
SensorManager sm;
/**方向传感器*/
private Sensor orientation;
private List<Sensor> deviceSensors;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gradienter);
show = (GradienterView) findViewById(R.id.show);
sm = (SensorManager) getSystemService(SENSOR_SERVICE);
//获取手机上支持的所有传感器
deviceSensors = sm.getSensorList(Sensor.TYPE_ALL);
if (!check(Sensor.TYPE_ORIENTATION)) {
Toast.makeText(this, "当前设备不支持方向传感器", Toast.LENGTH_SHORT).show();
}else {
orientation = sm.getDefaultSensor(Sensor.TYPE_ORIENTATION);
}
}
@Override
protected void onResume() {
super.onResume();
if (orientation != null){
sm.registerListener(this,orientation,SensorManager.SENSOR_DELAY_FASTEST);
}
}
@Override
protected void onStop() {
super.onStop();
if (orientation != null){
sm.unregisterListener(this);
}
}

@Override
public void onSensorChanged(SensorEvent event) {
float[] values = event.values;
switch (event.sensor.getType()){
case Sensor.TYPE_ORIENTATION:
//获取与y轴的夹角
float AngleY = values[1];
//获取与z轴的夹角
float Anglez = values[2];
//气泡位于中间时(水平仪完全水平), 气泡的位置x,y坐标
int x = (show.back.getWidth() - show.bubble.getWidth()) / 2 ;
int y = (show.back.getHeight() - show.bubble.getHeight()) / 2 ;
//如果与Z轴的倾斜角还在最大角度之内
if (Math.abs(Anglez) <= MAX_ANGLE){
//根据与Z轴的倾斜角度计算x坐标的变化值(倾斜角度越大,变化越大)
int deltaX = (int) ((show.back.getWidth() - show.bubble.getWidth()) / 2 * Anglez / MAX_ANGLE);
x += deltaX;
}else if (Anglez > MAX_ANGLE){//如果与z轴的倾斜角已经大于MAX_ANGLE,气泡应到左边
x = 0;
}else {//如果与Z轴的倾斜角已经小于负的MAX_ANGLE 气泡应到最右边
x = show.back.getWidth() - show.bubble.getWidth();
}

//如果与Y轴的倾斜角还在最大角度之内
if (Math.abs(AngleY) <= MAX_ANGLE){
//根据与y轴的倾斜角度计算x坐标的变化值(倾斜角度越大,变化越大)
int deltaY = (int) ((show.back.getHeight() - show.bubble.getHeight()) / 2 * AngleY / MAX_ANGLE);
y += deltaY;
}else if (AngleY > MAX_ANGLE){//如果与y轴的倾斜角已经大于MAX_ANGLE,气泡应到左边
y = show.back.getHeight() - show.bubble.getHeight();
}else {//如果与y轴的倾斜角已经小于负的MAX_ANGLE 气泡应到最右边
y = 0;
}

if (isContain(x,y)){
show.bubbleX = x;
show.bubbleY = y;
}
show.postInvalidate();
break;
}
}

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
/**
* 检测设备是否支持某类型的传感器
*
* @param type 传感器的类型
* @return true 当前设备支持该类型的传感器 否则不支持
*/
private boolean check(int type) {
Iterator<Sensor> iterator = deviceSensors.iterator();
while (iterator.hasNext()) {
Sensor sensor = iterator.next();
if (sensor.getType() == type) {
return true;
}
}
return false;
}

/**
* 计算x y点的坐标是否处于水平仪的仪表盘内
*/
private boolean isContain(int x,int y){
//计算气泡的愿新坐标x,y
int bubbleCX = x + show.bubble.getWidth() / 2;
int bubbleCY = y + show.bubble.getHeight() / 2;
//计算水平仪仪表盘的圆心坐标x,y
int backCX = show.back.getWidth() / 2;
int backCY = show.back.getHeight() / 2;
//计算气泡的圆心与水平仪仪表盘的圆心之间的距离
double distance = Math.sqrt((bubbleCX - backCX) * (bubbleCX - backCX) + (bubbleCY - backCY) * (bubbleCY - backCY));
//若两个圆心的距离小于它们的半径差,即可认为处于该点的气泡依然位于仪表盘内
if (distance < (show.back.getWidth() - show.bubble.getWidth()) / 2){
return true;
}
return false;
}
}
完整项目示例代码
效果图: