获取Android设备的方向 ,使用加速度重力传感器

带有g-sensor的Android设备上可通过API获取到设备的运动加速度,应用程序通过一些假设和运算,可以从加速度计算出设备的方向

获取设备运动加速度的基本代码是:

        SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
        sm.registerListener(new SensorEventListener() {

            public void onSensorChanged(SensorEvent event) {
                if (Sensor.TYPE_ACCELEROMETER != event.sensor.getType()) {
                    return;
                }

                float[] values = event.values;
                float ax = values[0];
                float ay = values[1];
                float az = values[2];

                // TODO Have fun with the acceleration components...

            }

            public void onAccuracyChanged(Sensor sensor, int accuracy) {

            }
        }, sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);

SendorEventListener 通过 SendorEvent 回调参数获得当前设备在坐标系x、y、z轴上的加速度分量。SensorEvent 的 api doc 中定义了这里使用的坐标系为:

我暂且称之为“设备坐标系”吧,设备坐标系是固定于设备的,与设备的方向(在世界坐标系中的朝向)无关

精确地说,Sensor Event 所提供的加速度数值,是设备以地球为参照物的加速度减去重力加速度的叠加后的值。我是这样理解的:当以重力加速度g向地面作自由落体运动时,手机处于失重状态,g-sensor以这种状态作为加速度的0;而当手机处于静止状态(相对于地面)时,为了抵御自由落体运动的趋势,它有一个反向(向上)的g的加速度。因此,得出一个结论:当设备处于静止或者匀速运动状态时,它有一个垂直地面向上的g的加速度,这个g投影到设备坐标系的x、y、z轴上,就是SensorEvent 提供给我们的3个分量的数值。在“设备处于静止或者匀速运动状态”的假设的前提下,可以根据SensorEvent所提供的3个加速度分量计算出设备相对于地面的方向

前面所提到的“设备的方向”是一个含糊的说法。这里我们精确地描述设备方向为:以垂直于地面的方向为正方向,用设备坐标系x、y、z轴与正方向轴之间的夹角Ax、Ay、Az来描述设备的方向,如下图所示。可以看出,设备还有一个自由度,即:绕着正方向轴旋转,Ax、Ay、Az不变。但Ax、Ay、Az的约束条件,对于描述设备相对于正方向轴的相对位置已经足够了。如果需要完全约束设备相对于地面的位置,除了正方向轴外,还需要引入另一个参照轴,例如连接地球南、北极的地轴(如果设备上有地磁强度Sensor,则可满足该约束条件)

Ax、Ay、Az的范围为[0, 2*PI)。例如,当Ay=0时,手机y轴竖直向上;Ay=PI时,手机y轴向下;Ay=PI/2时,手机水平、屏幕向上;Ay=3*PI/2时,手机水平、屏幕向下

根据3D矢量代数的法则,可知:

  • Gx=g*cos(Ax)
  • Gy=g*cos(Ay)
  • Gz=g*cos(Az)
  • g^2=Gz^2+Gy^2+Gz^2

因此,根据Gx、Gy、Gz,可以计算出Ax、Ay、Az

在x-y平面上的2D简化

当Ax、Ay确定时,Az有两种可能的值,二者相差PI,确定了设备屏幕的朝向是向上还是向下。大多数情况下,我们只关心Ax、Ay(因为程序UI位于x-y平面?),而忽略Az,例如,Android的屏幕自动旋转功能,不管使用者是低着头看屏幕(屏幕朝上)、还是躺在床上看(屏幕朝下),UI始终是底边最接近地心的方向

那么我们设Gx与Gy的矢量和为g'(即:g在x-y平面上的投影),将计算简化到x-y 2D平面上。记y轴相对于g'的偏角为A,以A来描述设备的方向。以逆时针方向为正,A的范围为[0, 2*PI)

有:

  • g'^2=Gx^2+Gy^2
  • Gy=g'*cos(A)
  • Gx=g'*sin(A)

则:

  • g'=sqrt(Gx^2+Gy^2)
  • A=arccos(Gy/g')

由于arccos函数值范围为[0, PI];而A>PI时,Gx=g'*sin(A)<0,因此,根据Gx的符号分别求A的值为:

  • 当Gx>=0时,A=arccos(Gy/g')
  • 当Gx<0时,A=2*PI-arccos(Gy/g')

注意:由于cos函数曲线关于直线x=n*PI 对称,因此arccos函数的曲线如果在y轴方向[0, 2*PI]范围内补全的话,则关于直线y=PI对称,因此有上面当Gx<0时的算法

考虑应用程序的屏幕旋转

前面计算出了Android设备的“物理屏幕”相对于地面的旋转角度,而应用程序的UI又相对于“物理屏幕”存在0、90、180、270度4种可能的旋转角度,要综合考虑进来。也就是说:

  • UI相对于地面的旋转角度=物理屏幕相对于地面的旋转角度-UI相对于物理屏幕的旋转角度

Android应用获取屏幕旋转角度的方法为:

        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
        int degree= 90 * rotation;
        float rad = (float)Math.PI / 2 * rotation;

Demo

根据上面的算法,我写了一个“不倒翁”的Demo,当设备旋转时,不倒翁始终是站立的。软件市场上不少“水平尺”一类的应用,其实现原理应该是与此相同的

下载Demo源代码

Activity实现了SensorEventListener,并且注册到SensorManager。同时设置屏幕方向固定为LANDSCAPE:

    private GSensitiveView gsView;
    private SensorManager sm;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        super.onCreate(savedInstanceState);

        gsView = new GSensitiveView(this);
        setContentView(gsView);

        sm = (SensorManager) getSystemService(SENSOR_SERVICE);
        sm.registerListener(this, sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);
    }

    @Override
    protected void onDestroy() {
        sm.unregisterListener(this);
        super.onDestroy();
    }

当g-sensor数据变化时的回调如下。这里就是根据我们前面推论的算法计算出UI旋转的角度,并且调用GSensitiveView.setRotation()方法通知View更新

    public void onSensorChanged(SensorEvent event) {
        if (Sensor.TYPE_ACCELEROMETER != event.sensor.getType()) {
            return;
        }

        float[] values = event.values;
        float ax = values[0];
        float ay = values[1];

        double g = Math.sqrt(ax * ax + ay * ay);
        double cos = ay / g;
        if (cos > 1) {
            cos = 1;
        } else if (cos < -1) {
            cos = -1;
        }
        double rad = Math.acos(cos);
        if (ax < 0) {
            rad = 2 * Math.PI - rad;
        }

        int uiRot = getWindowManager().getDefaultDisplay().getRotation();
        double uiRad = Math.PI / 2 * uiRot;
        rad -= uiRad;

        gsView.setRotation(rad);
    }

GSensitiveView是扩展ImageView的自定义类,主要是根据旋转角度绘制图片:

    private static class GSensitiveView extends ImageView {

        private Bitmap image;
        private double rotation;
        private Paint paint;

        public GSensitiveView(Context context) {
            super(context);
            BitmapDrawable drawble = (BitmapDrawable) context.getResources().getDrawable(R.drawable.budaow);
            image = drawble.getBitmap();

            paint = new Paint();
        }

        @Override
        protected void onDraw(Canvas canvas) {
            // super.onDraw(canvas);

            double w = image.getWidth();
            double h = image.getHeight();

            Rect rect = new Rect();
            getDrawingRect(rect);

            int degrees = (int) (180 * rotation / Math.PI);
            canvas.rotate(degrees, rect.width() / 2, rect.height() / 2);
            canvas.drawBitmap(image, //
                    (float) ((rect.width() - w) / 2),//
                    (float) ((rect.height() - h) / 2),//
                    paint);
        }

        public void setRotation(double rad) {
            rotation = rad;
            invalidate();
        }

    }

时间: 2024-11-01 20:27:50

获取Android设备的方向 ,使用加速度重力传感器的相关文章

如何获取Android设备挂载的所有存储器

android系统提供了Environment.getExternalStorageDirectory()接口获得存储器的路径,但是这个接口往往给的结果并不是我们想要的,在某些设备上它返回的是手机内部存储,某些设备它返回的手机外部存储.还有就是某些Android设备支持扩展多个sdcard,这个时候想要获得所有存储器的挂载路径,这个接口是没有办法办到的. 怎么获取Android设备所有存储器的位置呢?或者说获得所有的挂载点 系统提供了一个StorageManager,它有一个方法叫getVolu

获取Android设备唯一标识码

概述 有时需要对用户设备进行标识,所以希望能够得到一个稳定可靠并且唯一的识别码.虽然Android系统中提供了这样设备识别码,但是由于Android系统版本.厂商定制系统中的Bug等限制,稳定性和唯一性并不理想.而通过其他硬件信息标识也因为系统版本.手机硬件等限制存在不同程度的问题. 下面收集了一些"有能力"或"有一定能力"作为设备标识的串码. DEVICE_ID 这是Android系统为开发者提供的用于标识手机设备的串号,也是各种方法中普适性较高的,可以说几乎所有

如何获得Android设备名称(ADB命令详细介绍)

豌豆荚.360手机管家等软件可以获取android设备名称,显示在界面上,如下图: 我们自己如何来获取设备名称 呢?答案如下: 在命令行中输入"adb shell"进入shell之后,再输入"cat /system/build.prop"其实,设备信息,主要是存放在"/system/build.prop"文件,通过"cat"命令就可以查看了. 下面附上ADB命令的详细介绍 adb 介绍 adb的全称为Android Debug

android传感器如何能获取当前设备的运动方向

问题描述 android传感器如何能获取当前设备的运动方向 通过加速度计与陀螺仪如何能知道设备运动的方向啊,求大神,,获取到选旋转加速度与加速度的值以后如何计算啊 解决方案 需要指南针(罗盘)http://blog.csdn.net/octobershiner/article/details/6641942 解决方案二: http://www.jb51.net/article/37710.htm

【ANDROID游戏开发十八】解放手指,利用传感器开发游戏!(本文讲解在SURFACEVIEW中用重力传感器控制圆球的各方向移动)

本站文章均为 李华明Himi 原创,转载务必在明显处注明:  转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/android-game/344.html ----------------------- 『很多童鞋说我的代码运行后,点击home或者back后会程序异常,如果你也这样遇到过,那么你肯定没有仔细读完Himi的博文,第十九篇Himi专门写了关于这些错误的原因和解决方法,这里我在博客都补充说明下,省的童鞋们总疑惑这一块:请点击下面联系进入阅读:

Android获取移动设备IP地址

MainActivity如下: package cn.testip; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.Enumeration; import android.os.Bundle; import android.app.Activity; /** * Demo描述: * 获取移动设备的IP地址 */ pub

Android重力传感器实现滚动的弹球_Android

熟知: 什么是传感器:      所谓传感器能够探测如光.热.温度.重力.方向 等等的功能! Android中提供传感器有哪些:      1.  加速度传感器(重力传感器)      2.  陀螺仪传感器      3.  光传感器      5.  恒定磁场传感器      6.  方向传感器      7.  恒定的压力传感器      8.  接近传感器      9.  温度传感器 一. 问题描述 Android中有多达11种传感器,不同的手机设备支持的传感器类型也不尽相同 1. 重力

Android 重力传感器在游戏开发中的应用_Android

      手势操作可以说是智能手机的一种魅力所在,前两节给大家讲解了两种有趣的手势操作,将它们置于游戏当中,大大提升了游戏的可玩性和趣味性.本节将继续介绍智能手机的另一种神奇之处:传感器.        一.何为传感器        所谓传感器就是能够探测如光.热.温度.重力.方向等等的装置.        二.Android提供了哪些传感器        1.加速度传感器(重力传感器)        2.陀螺仪传感器        3.光传感器        4.恒定磁场传感器       

Android 重力传感器在游戏开发中的应用

手势操作可以说是智能手机的一种魅力所在,前两节给大家讲解了两种有趣的手势操作,将它们置于游戏当中,大大提升了游戏的可玩性和趣味性.本节将继续介绍智能手机的另一种神奇之处:传感器. 一.何为传感器 所谓传感器就是能够探测如光.热.温度.重力.方向等等的装置. 二.Android提供了哪些传感器 1.加速度传感器(重力传感器) 2.陀螺仪传感器 3.光传感器 4.恒定磁场传感器 5.方向传感器 6.恒定的压力传感器 7.接近传感器 8.温度传感器 今天我们给大家介绍的是游戏开发中最最常见的,用到的频