On Android devices with G-sensor, the motion acceleration of the device can be obtained through the API, and the application can calculate the direction of the device from the acceleration by some assumptions and operations.
The basic code for getting the device's motion acceleration is:
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 has fun with the acceleration components ...
}
public void onaccuracychanged (sensor sensor, int accuracy) {
}
}, Sm.getdefaultsensor (Sensor.type_accelerometer), sensormanager.sensor_delay_normal);
Sendoreventlistener the acceleration components of the current device on the x, Y, z axes of the coordinate system via the sendorevent callback parameter. The Sensorevent API Doc defines the coordinate system used here:
I'll call it a "device coordinate system", which is fixed to the device, independent of the direction of the device (in the world coordinate system).
To be precise, the acceleration value provided by the Sensor Event is the superimposed value of the acceleration minus the gravitational acceleration of the device based on the Earth. I understand this: when the gravitational acceleration g to the ground for free fall, the phone is in weightlessness, g-sensor as the acceleration of 0, and when the phone is in a stationary state (relative to the ground), in order to resist the trend of free fall, it has a reverse (upward) G acceleration. Thus, it comes to the conclusion that when the device is in a stationary or uniform motion State, it has a vertical ground upward g acceleration, which is projected to the x, Y, z axes of the device coordinate system, which is the value of the 3 components provided to us by Sensorevent . On the assumption that the device is in a stationary or uniform motion State, the direction of the device relative to the ground can be calculated according to the 3 acceleration components provided by the Sensorevent
The "direction of the equipment" mentioned earlier is a vague statement. Here we accurately describe the device orientation as: In the positive direction perpendicular to the ground, with the angle between the x, Y, Z axis and the positive axis of the device coordinate system ax, Ay, AZ to describe the direction of the device, as shown in. As can be seen, the device also has a degree of freedom, namely: rotation around the positive axis, Ax, Ay, AZ unchanged. However, the constraints of Ax, Ay, and AZ are sufficient to describe the relative position of the device relative to the positive direction axis. If you need to fully constrain the position of the device relative to the ground, in addition to the positive axis, you need to introduce another reference axis, such as the axis of the Earth's south and North Pole (if there is geomagnetic intensity sensor on the device, the constraint can be met)
The range for Ax, Ay, AZ is [0, 2*pi]. For example, when ay=0, the y-axis of the phone is vertical upward; when ay=pi, the y-axis of the phone is down; AY=PI/2, the phone level, screen up, AY=3*PI/2, the phone level, the screen down
According to the law of 3D vector algebra, we know:
- Gx=g*cos (Ax)
- Gy=g*cos (Ay)
- Gz=g*cos (Az)
- G^2=gz^2+gy^2+gz^2
Therefore, according to GX, Gy, Gz, you can calculate the ax, Ay, Az
2D simplification on the X-y plane
When Ax, Ay is determined, AZ has two possible values, a difference of pi, which determines whether the device's screen is facing up or down. In most cases, we only care about AX, Ay (because the program UI is on the X-y plane?). ), while ignoring AZ, for example, the automatic screen rotation of Android, whether the user is looking at the screen with a low head (on the screen), or lying on the bed (screen facing down), the UI is always the closest to the center of the Earth.
Then we set the GX and Gy vectors and g ' (i.e.: G on the X-y projection), simplifying the calculation to the X-y 2D plane. The y-axis is a with respect to the G ' angle, and a is used to describe the direction of the device. In a counterclockwise direction, the range of A is [0, 2*pi]
Yes:
- G ' ^2=gx^2+gy^2
- Gy=g ' *cos (A)
- Gx=g ' *sin (A)
The
- G ' =sqrt (gx^2+gy^2)
- A=arccos (gy/g ')
Since the value range of the Arccos function is [0, PI], and A>pi, Gx=g ' *sin (a) <0, therefore, the value of a is calculated according to the notation of the Gx as:
- When Gx>=0, A=arccos (gy/g ')
- When Gx<0, A=2*pi-arccos (gy/g ')
Note: Because the Cos function curve is symmetric about the line x=n*pi, the curve of the Arccos function, if it is complete within the y-axis direction [0, 2*pi], is symmetric about the line y=pi, so there is an algorithm for the above when gx<0
Consider the application's screen rotation
The previous calculation of the Android device's "physical screen" angle of rotation with respect to the ground, and the application's UI relative to the "physical screen" there are 0, 90, 180, 270 degrees 4 possible rotation angle, should be considered in general. Other words:
- The rotation angle of the UI relative to the ground = the rotation angle of the physical screen relative to the ground-ui the angle of rotation relative to the physical screen
The way Android apps get the screen rotation angle is:
int rotation = Activity.getwindowmanager (). Getdefaultdisplay (). Getrotation ();
int degree= * rotation;
Float rad = (float) math.pi/2 * rotation;
Demo
According to the above algorithm, I wrote a "Tumbler" of the demo, when the device rotates, the tumbler is always standing. In the software market, a lot of "horizontal ruler" type of application, the implementation principle should be the same
Download Demo source code
The activity implements the Sensoreventlistener and registers to Sensormanager. Also set the screen orientation fixed to 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 ();
}
When the g-sensor data changes, the callback is as follows. Here we calculate the angle of the UI rotation based on the algorithm we deduced earlier, and call the Gsensitiveview.setrotation () method to notify the view update
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 is a custom class that extends ImageView, mainly drawing pictures based on the rotation angle:
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) (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 ();
}
}