1. 程式人生 > >獲取Android裝置的方向 -- gsensor

獲取Android裝置的方向 -- gsensor

http://www.cnblogs.com/bpasser/archive/2011/10/17/2214517.html

帶有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,當裝置旋轉時,不倒翁始終是站立的。軟體市場上不少“水平尺”一類的應用,其實現原理應該是與此相同的

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();
        }

    }

相關推薦

獲取Android裝置方向 -- gsensor

http://www.cnblogs.com/bpasser/archive/2011/10/17/2214517.html 帶有g-sensor的Android裝置上可通過API獲取到裝置的運動加速度,應用程式通過一些假設和運算,可以從加速度計算出裝置的方向 獲取裝

獲取Android裝置方向 ,使用加速度重力感測器

帶有g-sensor的Android裝置上可通過API獲取到裝置的運動加速度,應用程式通過一些假設和運算,可以從加速度計算出裝置的方向 獲取裝置運動加速度的基本程式碼是: SensorManager sm = (SensorManager) contex

[方法提煉] 獲取Android裝置序列號方法

通過這個方法可以檢測裝置是否連線成功,如果有一臺或者多臺裝置,可以將所有裝置序列號全部輸出 1 # -*- coding:utf-8 -*- 2 import os 3 4 def attachDeviceList(): 5 device_list = [] 6 cmd_

Android開發 - 獲取Android裝置的唯一標識碼(Android 6.0或更高)

在我們的APP開發中,通常需要獲取到裝置的唯一標識。在Android6.0之前,有很多方法我們可以方便獲取到硬體的唯一標識,但是在Android6.0之後,Android系統大幅限制了我們獲取裝置的硬體資訊。 Android6.0之前的方法(已過時) DEVICE_ID通getSyste

adb獲取android裝置資訊

import subprocess #ADB執行命令列,包括一些常用命令 class ADBShell: def __init__(self, adb_path=""): self.adb_path = adb_path def invoke(self,cmd):

獲取Android裝置唯一標識碼

唯一標識碼這東西在網路應用中非常有用,例如檢測是否重複註冊之類的。import android.provider.Settings.Secure;private String android_id = Secure.getString(getContext().getContentResolver(),

如何獲取Android裝置唯一識別碼

如果我們想唯一識別一臺Android裝置,就需要獲取裝置的唯一識別碼。怎麼獲取呢? 下面是Android裝置一些可供選擇的唯一識別碼及其適用範圍: IMEI (International Mobile Equipment Identity Number,國際移動裝置識別碼) 用於在手機網路中識別

獲取Android裝置的唯一識別碼|裝置號|序號|UUID

如何獲取一個能唯一標識每臺Android裝置的序號? 這個問題有很多答案,但是他們中的大部分只在某些情況下有效。 根據測試: 所有的裝置都可以返回一個 TelephonyManager.getDeviceId() 所有的GSM裝置 (測試裝置都裝載有SIM卡) 可以

獲取Android裝置的唯一標識碼(Android 6.0或更高)

在我們的APP開發中,通常需要獲取到裝置的唯一標識。在Android6.0之前,有很多方法我們可以方便獲取到硬體的唯一標識,但是在Android6.0之後,Android系統大幅限制了我們獲取裝置的硬體資訊。 Android6.0之前的方法(已過時) D

Android之如何獲取Android裝置的唯一識別碼筆記

因為需要在專案中需要獲得一個穩定、可靠的裝置唯一識別碼,因此搜了一些網上的資料。今天我們將介紹幾種方式。        1. DEVICE_ID 假設我們確實需要用到真實裝置的標識,可能就需要用到DEVICE_ID。在以前,我們的Android裝置是手機,這個 DE

穩定獲取Android裝置唯一碼(UUID)的解決方案

最近做的一個專案中需要用到Android裝置唯一碼(UUID)來標識一臺裝置, Android中裝置唯一碼有很多,如:MAC地址、IMEI號(DeviceId)、IMSI號、ANDROID_ID、序列號(SerialNumber)等, 但並不是所有裝置上都能穩定獲取到這些值。 最後專案中採用的是MAC地址

獲取Android裝置常規引數資訊(SN,IMEI)及定製資訊

因使用定製Android系統的RFID巴槍 RFID巴槍註冊,使用的是外包裝的序列號,在進入APP需要本地與註冊資訊作校驗.校驗通過才允許使用APP. 1.常規獲取Android裝置資訊. 獲取IMEI號 Android裝置可以通過設定介面、撥號*#0

獲取Android裝置資訊

在開發的過程中有可能根據需要顯示一些裝置的狀態或系統資訊。下面是收集到的一些資訊 一、獲取當前電量 每當電量改變時系統都會廣播發Intent.ACTION_BATTERY_CHANGED訊息出來,應用程式只需要註冊並建立一個接收器就能獲取當前的電量 建立一個廣播接收器類Ba

獲取Android裝置的唯一識別碼

 1、所有的裝置都可以返回一個 TelephonyManager.getDeviceId() 2、所有的GSM裝置 (測試裝置都裝載有SIM卡) 可以返回一個TelephonyManager.getSimSerialNumber() 3、所有的CDMA 裝置對於

[Android][獲取Android裝置唯一標識]

1.落筆緣由 最近需要獲取能夠標誌Android裝置的唯一標識,但是由於Android系統版本不同或者root等諸多原因,造成有些裝置標識為NULL或者標識相同的問題,在網上搜索了相關資料,總結一下各種情況。 2.程式碼 import android.

獲取Android裝置的唯一識別符號

最近做的一個需求,客戶要求賬號最多繫結三臺裝置。我之所以說是唯一識別符號而不是獲取Android裝置的IMEI是因為IMEI並不是唯一的解決方案,也不一定是最優解,具體還要看需求。 IMEI 先說一下最常用的IMEI,android系統中通常用下面這段程式碼

獲取 Android 裝置的唯一標識碼

這個問題有很多答案,但是他們中的大部分只在某些情況下有效。 根據測試: 所有的裝置都可以返回一個 TelephonyManager.getDeviceId() 所有的GSM裝置 (測試裝置都裝載有SIM卡) 可以返回一個TelephonyManager.getSim

獲取Android裝置的唯一識別碼UUID

如何獲取一個能唯一標識每臺Android裝置的序號? 這個問題有很多答案,但是他們中的大部分只在某些情況下有效。 根據測試: 所有的裝置都可以返回一個 TelephonyManager.getDeviceId() 所有的GSM裝置 (測試裝置都裝載有

獲取Android裝置電池電量狀態

方法一:註冊廣播 這個方法網上一搜一大堆,我也貼出部分程式碼 IntentFilter filter2 = new IntentFilter(); filter2.addAction(Inten

UIDevice的orientation方法,獲取ios裝置方向不準確問題

使用 UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation方法,獲取當前裝置的方向,第一次使用的時候,總是返回0 可以使用 下邊這個方法獲取,  UIInterfaceOri