获取Android设备的方向,Sensor和SensorManager实现手机旋转角度

http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1009/425.html

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

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

  1. SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
  2. sm.registerListener(new SensorEventListener() {
  3. public void onSensorChanged(SensorEvent event) {
  4. if (Sensor.TYPE_ACCELEROMETER != event.sensor.getType()) {
  5. return;
  6. }
  7. float[] values = event.values;
  8. float ax = values[0];
  9. float ay = values[1];
  10. float az = values[2];
  11. // TODO Have fun with the acceleration components...
  12. }
  13. public void onAccuracyChanged(Sensor sensor, int accuracy) {
  14. }
  15. }, 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应用获取屏幕旋转角度的方法为:

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

Demo

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

下载Demo源代码

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

  1. private GSensitiveView gsView;
  2. private SensorManager sm;
  3. @Override
  4. public void onCreate(Bundle savedInstanceState) {
  5. setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
  6. super.onCreate(savedInstanceState);
  7. gsView = new GSensitiveView(this);
  8. setContentView(gsView);
  9. sm = (SensorManager) getSystemService(SENSOR_SERVICE);
  10. sm.registerListener(this, sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);
  11. }
  12. @Override
  13. protected void onDestroy() {
  14. sm.unregisterListener(this);
  15. super.onDestroy();
  16. }

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

  1. public void onSensorChanged(SensorEvent event) {
  2. if (Sensor.TYPE_ACCELEROMETER != event.sensor.getType()) {
  3. return;
  4. }
  5. float[] values = event.values;
  6. float ax = values[0];
  7. float ay = values[1];
  8. double g = Math.sqrt(ax * ax + ay * ay);
  9. double cos = ay / g;
  10. if (cos > 1) {
  11. cos = 1;
  12. } else if (cos < -1) {
  13. cos = -1;
  14. }
  15. double rad = Math.acos(cos);
  16. if (ax < 0) {
  17. rad = 2 * Math.PI - rad;
  18. }
  19. int uiRot = getWindowManager().getDefaultDisplay().getRotation();
  20. double uiRad = Math.PI / 2 * uiRot;
  21. rad -= uiRad;
  22. gsView.setRotation(rad);
  23. }

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

  1. private static class GSensitiveView extends ImageView {
  2. private Bitmap image;
  3. private double rotation;
  4. private Paint paint;
  5. public GSensitiveView(Context context) {
  6. super(context);
  7. BitmapDrawable drawble = (BitmapDrawable) context.getResources().getDrawable(R.drawable.budaow);
  8. image = drawble.getBitmap();
  9. paint = new Paint();
  10. }
  11. @Override
  12. protected void onDraw(Canvas canvas) {
  13. // super.onDraw(canvas);
  14. double w = image.getWidth();
  15. double h = image.getHeight();
  16. Rect rect = new Rect();
  17. getDrawingRect(rect);
  18. int degrees = (int) (180 * rotation / Math.PI);
  19. canvas.rotate(degrees, rect.width() / 2, rect.height() / 2);
  20. canvas.drawBitmap(image, //
  21. (float) ((rect.width() - w) / 2),//
  22. (float) ((rect.height() - h) / 2),//
  23. paint);
  24. }
  25. public void setRotation(double rad) {
  26. rotation = rad;
  27. invalidate();
  28. }
  29. }
时间: 2024-08-26 06:02:55

获取Android设备的方向,Sensor和SensorManager实现手机旋转角度的相关文章

获取Android设备的方向

带有g-sensor的Android设备上可通过API获取到设备的运动加速度,应用程序通过一些假设和运算,可以从加速度计算出设备的方向 获取设备运动加速度的基本代码是: SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); sm.registerListener(new SensorEventListener() { public void onSensorChanged(Sens

获取Android设备WIFI的MAC地址 “MAC地址”

需要指出的是:wifi状态和wifi AP状态是互斥的状态:也就是一旦发现WIFI AP打开,WIFI是不能被打开的. 获取Android设备的WIFI MAC地址,首先需要将设备中的WIFI个人热点(AP)关闭:WIFI状态和WIFI AP状态是互斥的两种状态.也就是说:在WIFI AP打开的状态下,WIFI是不能被正常打开的. android系统获取MAC地址的多种方式遍历. 方法一:使用NetworkInterface 方法二: private static String getIpAnd

Shell下获取Android设备信息

使用adb.exe shell进入shell界面,然后通过下列命令即可获取需要的信息 :) 1 // getprop命令可获取很多信息哟 2 // 1.获取厂商名称 3 getprop ro.product.brand 4 // 2.设备型号 5 getprop ro.product.model 6 // 3.安卓版本 7 getprop ro.build.version.release 8 // 4.网卡名称 9 wifi.interface 10 // 5.Google glass系统版本

获取android设备wifi连接状态

本文将介绍如何获取android设备wifi连接状态! 添加访问权限(AndroidManifest.xml文件里) <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> Java代码(MainActivity.java文件) package com.example.androidtest; import android.net.ConnectivityManager; impo

一款Android设备上的智能路由器软件:手机服务站

现在智能电视和盒子的配置越来越高,体验越来越好,那么我们除了用它看看电视电影,打打游戏外,还能干什么呢?它占据着客厅的重要位置,是不是可以做点其他的事情? 例如: 1.用它代替无线路由器给我们的手持设备或笔记本共享网络可以吗? 2.能不能把它做成服务器,用来保存一些不方便或者不需要上传到网络云盘里的文件呢? 3.再或者我想建立一个私人的网站,记录家里的点点滴滴,这些,都可以吗? 4.就算以上都可以做,那我管理起来会不会不方便? 所以,在此向各位推荐一个Android软件来解决以上几个问题,而且还

获取Android设备唯一标识码

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

一起学android之如何获取Android设备的唯一识别码笔记(21)

因为需要在项目中需要获得一个稳定.可靠的设备唯一识别码,因此搜了一些网上的资料.今天我们将介绍几种方式. 1. DEVICE_ID 假设我们确实需要用到真实设备的标识,可能就需要用到DEVICE_ID.在以前,我们的Android设备是手机,这个 DEVICE_ID可以同通过TelephonyManager.getDeviceId()获取,它根据不同的手机设备返回IMEI,MEID或者ESN 码,但它在使用的过程中会遇到很多问题: 非手机设备: 如果只带有Wifi的设备或者音乐播放器没有通话的硬

Android开发 - 获取Android设备的唯一标识码(Android 6.0或更高)

在我们的APP开发中,通常需要获取到设备的唯一标识.在Android6.0之前,有很多方法我们可以方便获取到硬件的唯一标识,但是在Android6.0之后,Android系统大幅限制了我们获取设备的硬件信息. Android6.0之前的方法(已过时) DEVICE_ID通getSystemService(Context.TELEPHONY_SERVICE).getDeviceId()获取,但是6.0之后必须申请READ_PHONE_STATE,并且获取到的这个值在不同的厂商和设备中并不可靠. M

如何获取 Android 设备的CPU核数、时钟频率以及内存大小

因项目需要,分析了一下 Facebook 的开源项目 - Device Year Class. Device Year Class 的主要功能是根据 CPU核数.时钟频率 以及 内存大小 对设备进行分级.代码很简单,只包含两个类: DeviceInfo -> 获取设备参数, YearClass -> 根据参数进行分级. 下表是 Facebook 公司提供的分级标准,其中 Year 栏表示分级结果. Year Cores Clock RAM 2008 1 528MHz 192MB 2009 n/