Get phone orientation when locked into one orienta

2019-03-16 00:08发布

问题:

This could easily be a duplicate of another question, Im just struggling to figure out what to search for.

My camera app is locked in landscape mode (in the manifest) like this:

android:screenOrientation="landscape"

However, I want to still rotate some UI elements when the device is rotated into portrait (although android will still think its in landscape, but thats on purpose).

So I've tried this to check the orientation

int rotation = this.getWindowManager().getDefaultDisplay()
            .getRotation();
    int degrees = 0;
    switch (rotation) {
        case Surface.ROTATION_0:
            Log.d("Rotation", "0");
            break;
        case Surface.ROTATION_90:
            Log.d("Rotation", "90");
            break;
        case Surface.ROTATION_180:
            Log.d("Rotation", "180");
            break;
        case Surface.ROTATION_270:
            Log.d("Rotation", "270");
            break;
    }

And unfortunately it always returns 90, regardless of how I turn the phone. Is there a more robust way to get orientation, regardless of what Android "thinks" the orientation is?

回答1:

So after I thought about it, I realized I could just implement a similar algorithm as what Android itself uses to figure out the orientation. I do it using the onSenseorChanged callback

public static final int UPSIDE_DOWN = 3;
public static final int LANDSCAPE_RIGHT = 4;
public static final int PORTRAIT = 1;
public static final int LANDSCAPE_LEFT = 2;
public int mOrientationDeg; //last rotation in degrees
public int mOrientationRounded; //last orientation int from above 
private static final int _DATA_X = 0;
private static final int _DATA_Y = 1;
private static final int _DATA_Z = 2;
private int ORIENTATION_UNKNOWN = -1;
@Override
public void onSensorChanged(SensorEvent event) 
{
    Log.d(TAG, "Sensor Changed");
    float[] values = event.values;
    int orientation = ORIENTATION_UNKNOWN;
    float X = -values[_DATA_X];
    float Y = -values[_DATA_Y];
    float Z = -values[_DATA_Z];        
    float magnitude = X*X + Y*Y;
    // Don't trust the angle if the magnitude is small compared to the y value
    if (magnitude * 4 >= Z*Z) {
        float OneEightyOverPi = 57.29577957855f;
        float angle = (float)Math.atan2(-Y, X) * OneEightyOverPi;
        orientation = 90 - (int)Math.round(angle);
        // normalize to 0 - 359 range
        while (orientation >= 360) {
            orientation -= 360;
        } 
        while (orientation < 0) {
            orientation += 360;
        }
    }
    //^^ thanks to google for that code
    //now we must figure out which orientation based on the degrees
    Log.d("Oreination", ""+orientation);
    if (orientation != mOrientationDeg) 
    {
        mOrientationDeg = orientation;
        //figure out actual orientation
        if(orientation == -1){//basically flat

        }
        else if(orientation <= 45 || orientation > 315){//round to 0
            tempOrientRounded = 1;//portrait
        }
        else if(orientation > 45 && orientation <= 135){//round to 90
            tempOrientRounded = 2; //lsleft
        }
        else if(orientation > 135 && orientation <= 225){//round to 180
            tempOrientRounded = 3; //upside down
        }
        else if(orientation > 225 && orientation <= 315){//round to 270
            tempOrientRounded = 4;//lsright
        }

    }

    if(mOrientationRounded != tempOrientRounded){
            //Orientation changed, handle the change here
        mOrientationRounded = tempOrientRounded;

    }
}

It looks more complecated than it is, but just know that it works(I'd say equally well as the system one works). Dont forget to register your sensor change event listener in onResume and onPause for accelerometer



回答2:

For detect orientations I use this for register to sensormanager:

mSensorOrientation = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);      
mSensorManager.registerListener(this, mSensorOrientation, SensorManager.SENSOR_DELAY_NORMAL);

And then this for detect orientations changes, in the comments you can put your own method implementations.

Constants:

public static final int LYING = 0;
public static final int LANDSCAPE_RIGHT = 1;
public static final int PORTRAIT = 2;
public static final int LANDSCAPE_LEFT = 3;



public void onSensorChanged(SensorEvent event) {

Sensor sensorEvent = event.sensor;

if ((sensorEvent.getType() == Sensor.TYPE_ORIENTATION)) {

    float [] eventValues = event.values;

    // current orientation of the phone
    float xAxis = eventValues[1];
    float yAxis = eventValues[2];

    if ((yAxis <= 25) && (yAxis >= -25) && (xAxis >= -160)) {

        if (previousOrientation != PORTRAIT){
            previousOrientation = PORTRAIT;

            // CHANGED TO PORTRAIT
        }

    } else if ((yAxis < -25) && (xAxis >= -20)) {

        if (previousOrientation != LANDSCAPE_RIGHT){
            previousOrientation = LANDSCAPE_RIGHT;

            // CHANGED TO LANDSCAPE RIGHT
        }

    } else if ((yAxis > 25) && (xAxis >= -20)){

        if (previousOrientation != LANDSCAPE_LEFT){
            previousOrientation = LANDSCAPE_LEFT;

            // CHANGED TO LANDSCAPE LEFT
        }
    }
}

}



回答3:

After doing some research and trying some stuff, it work just for me when I set the Sensor as:

mSensorOrientation = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

Sensor.TYPE_ORIENTATION is deprecated and gave me bad results according to some example codes from different people for retrieving orientation. I don´t really know if it's ok or not, but it worked for me.



回答4:

A translation of @panavtec answer to API 23, using this as a reference

class MyActivity extends Activity implements SensorEventListener {

    private SensorManager sensorManager;
    private float[] lastMagFields = new float[3];;
    private float[] lastAccels = new float[3];;
    private float[] rotationMatrix = new float[16];
    private float[] orientation = new float[4];

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
    }

    protected void onResume() {
        super.onResume();
        sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_GAME);
        sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);
    }

    protected void onPause() {
        super.onPause();
        sensorManager.unregisterListener(this);
    }

    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }

    public void onSensorChanged(SensorEvent event) {
        switch (event.sensor.getType()) {
            case Sensor.TYPE_ACCELEROMETER:
                System.arraycopy(event.values, 0, lastAccels, 0, 3);
                break;
            case Sensor.TYPE_MAGNETIC_FIELD:
                System.arraycopy(event.values, 0, lastMagFields, 0, 3);
                break;
            default:
                return;
        }

        if (SensorManager.getRotationMatrix(rotationMatrix, null, lastAccels, lastMagFields)) {
            SensorManager.getOrientation(rotationMatrix, orientation);

            float xAxis = (float) Math.toDegrees(orientation[1]);
            float yAxis = (float) Math.toDegrees(orientation[2]);

            int orientation = Configuration.ORIENTATION_UNDEFINED;
            if ((yAxis <= 25) && (yAxis >= -25) && (xAxis >= -160)) {
                Log.d(TAG, "Portrait");
                orientation = Configuration.ORIENTATION_PORTRAIT;
            } else if ((yAxis < -25) && (xAxis >= -20)) {
                Log.d(TAG, "Landscape Right");
                orientation = Configuration.ORIENTATION_LANDSCAPE;
            } else if ((yAxis > 25) && (xAxis >= -20)){
                orientation = Configuration.ORIENTATION_LANDSCAPE;
                Log.d(TAG, "Landscape Left");
            }
        }
    }
}


回答5:

This is a slightly modified Kotlin version of @Jameo answer. I needed the degrees to calculate the camera rotation in an Activity with the locked orientation. Please upvote him also.

private var rotationDeg: Int = 0
private var rotationRoundedClockwise: Int = 0

override fun onSensorChanged(event: SensorEvent) {
    Timber.d("Sensor Changed")
    val newRotationDeg = calculateNewRotationDegree(event)
    //^^ thanks to google for that code
    // now we must figure out which orientation based on the degrees
    Timber.d("rotation: $newRotationDeg")
    if (newRotationDeg != rotationDeg) {
        rotationDeg = newRotationDeg
        rotationRoundedClockwise = calculateRoundedRotation(newRotationDeg)
    }
    Timber.d("rotationRoundedClockwise: $rotationRoundedClockwise")
}

private val X_AXIS_INDEX = 0
private val Y_AXIS_INDEX = 1
private val Z_AXIS_AXIS = 2
private val ORIENTATION_UNKNOWN = -1

private fun calculateRoundedRotation(newRotationDeg: Int): Int {
    return if (newRotationDeg <= 45 || newRotationDeg > 315) { // round to 0
        0  // portrait
    } else if (newRotationDeg in 46..135) { // round to 90
        90  // clockwise landscape
    } else if (newRotationDeg in 136..225) { // round to 180
        180  // upside down portrait
    } else if (newRotationDeg in 226..315) { // round to 270
        270  // anticlockwise landscape
    } else {
        0
    }
}

private fun calculateNewRotationDegree(event: SensorEvent): Int {
    val values = event.values
    var newRotationDeg = ORIENTATION_UNKNOWN
    val X = -values[X_AXIS_INDEX]
    val Y = -values[Y_AXIS_INDEX]
    val Z = -values[Z_AXIS_AXIS]
    val magnitude = X * X + Y * Y
    // Don't trust the angle if the magnitude is small compared to the y value
    if (magnitude * 4 >= Z * Z) {
        val ONE_EIGHTY_OVER_PI = 57.29577957855f
        val angle = Math.atan2((-Y).toDouble(), X.toDouble()).toFloat() * ONE_EIGHTY_OVER_PI
        newRotationDeg = 90 - Math.round(angle)
        // normalize to 0 - 359 range
        while (newRotationDeg >= 360) {
            newRotationDeg -= 360
        }
        while (newRotationDeg < 0) {
            newRotationDeg += 360
        }
    }
    return newRotationDeg
}

private fun getCameraRotation(): Int {
    return when (rotationRoundedClockwise) {
        0 -> 90
        90 -> 180
        180 -> 270
        270 -> 0
        else -> 90
    }
}

Here is how to listen for events.

override fun onCreate() {
    super.onCreate()
    (activity?.getSystemService(SENSOR_SERVICE) as? SensorManager)?.let {
            sensorManager = it
        }
}

override fun onResume() {
    super.onResume()

    sensorManager.registerListener(this,
            sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
            SensorManager.SENSOR_DELAY_NORMAL)
}

override fun onPause() {
    super.onPause()

    sensorManager.unregisterListener(this)
}