28

Ok, así que tengo una clase que se extiende SurfaceView y anulacámara androide

surfaceChanged - sólo llama startPreview
surfaceCreated - se abre la cámara, edita params *, establece surfaceHolder
surfaceDestroyed - solicita stopPreview, cámara de liberación

todo esto funciona muy bien, porque cuando la orientación es Vertical:

de surfaceCreated *

m_camera = Camera.open(); 
Camera.Parameters p = m_camera.getParameters(); 

if (getResources().getConfiguration().orientation != 
    Configuration.ORIENTATION_LANDSCAPE) 
{ 
    p.set("orientation", "portrait"); 

    // CameraApi is a wrapper to check for backwards compatibility 
    if (CameraApi.isSetRotationSupported()) 
    { 
     CameraApi.setRotation(p, 90); 
    } 
} 

Sin embargo, cada vez que cambia la orientación, llama a Camera.open() ... que como ya sabrá, es una operación bastante costosa, haciendo que las transiciones no sean tan suaves.

Cuando fuerzo la orientación hacia el paisaje, la vista previa es excelente. Crear solo se llama una vez que funciona porque la vista previa está en el paisaje; la cámara es siempre lo que el usuario ve. Sin embargo, necesito una forma de establecer la orientación de la imagen real tomada cuando estoy en retrato. Sin embargo, cuando fuerzo el paisaje, la superficie nunca se vuelve a crear y los parámetros nunca se configuran cuando la cámara se sostiene en vertical.

Entonces, ¿cómo puedo hacer uno de los siguientes (exclusivamente)?

  1. aferrarse a m_camera entre OnDestroy y onCreate cuando los cambios de orientación para que la transición sea suave

  2. paisaje Fuerza y detectar cambios de orientación de otra manera ... que gira la imagen final snaped si se mantiene en retrato

Además, si estoy fuera de la base, ¿alguien puede indicarme una dirección mejor? Gracias.

+2

+1 También estoy interesado en esto. La aplicación predeterminada de la cámara de google lo realiza de forma hermosa: no recrea la actividad, pero los botones y la vista previa de la última imagen se rotan muy bien para que coincida con la orientación horizontal/vertical. Btw, _p.set ("orientation", "portrait") _ es, en mi entender, un uso oculto de API y no es oficialmente compatible, ¿no? – Audrius

+0

No creo que haga nada, jajaja. Mi método preferido sería forzar el paisaje. El problema es que necesitaría de alguna manera detectar la orientación de otra manera porque entonces la cámara no se recrearía. –

+0

Ah, veo lo que tienes en mente. Entonces forzarías la actividad de la cámara a un paisaje y luego, dependiendo de la orientación * real *, solo rotarías una imagen, ¿verdad? [Este] (http://android-er.blogspot.com/2010/08/orientationeventlistener-detect.html) podría ayudarlo. No es una mala idea, podría ir a implementarlo yo mismo (-. – Audrius

Respuesta

57

La manera en que yo implementé:

private Camera mCamera; 
private OrientationEventListener mOrientationEventListener; 
private int mOrientation = -1; 

private static final int ORIENTATION_PORTRAIT_NORMAL = 1; 
private static final int ORIENTATION_PORTRAIT_INVERTED = 2; 
private static final int ORIENTATION_LANDSCAPE_NORMAL = 3; 
private static final int ORIENTATION_LANDSCAPE_INVERTED = 4; 

@Override 
public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    // force Landscape layout 
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR | ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 
    /* 
    Your other initialization code here 
    */ 
} 

@Override 
protected void onResume() { 
    super.onResume(); 

    if (mOrientationEventListener == null) {    
     mOrientationEventListener = new OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL) { 

      @Override 
      public void onOrientationChanged(int orientation) { 

       // determine our orientation based on sensor response 
       int lastOrientation = mOrientation; 

       if (orientation >= 315 || orientation < 45) { 
        if (mOrientation != ORIENTATION_PORTRAIT_NORMAL) {       
         mOrientation = ORIENTATION_PORTRAIT_NORMAL; 
        } 
       } 
       else if (orientation < 315 && orientation >= 225) { 
        if (mOrientation != ORIENTATION_LANDSCAPE_NORMAL) { 
         mOrientation = ORIENTATION_LANDSCAPE_NORMAL; 
        }      
       } 
       else if (orientation < 225 && orientation >= 135) { 
        if (mOrientation != ORIENTATION_PORTRAIT_INVERTED) { 
         mOrientation = ORIENTATION_PORTRAIT_INVERTED; 
        }      
       } 
       else { // orientation <135 && orientation > 45 
        if (mOrientation != ORIENTATION_LANDSCAPE_INVERTED) { 
         mOrientation = ORIENTATION_LANDSCAPE_INVERTED; 
        }      
       } 

       if (lastOrientation != mOrientation) { 
        changeRotation(mOrientation, lastOrientation); 
       } 
      } 
     }; 
    } 
    if (mOrientationEventListener.canDetectOrientation()) { 
     mOrientationEventListener.enable(); 
    } 
} 

@Override protected void onPause() { 
    super.onPause(); 
    mOrientationEventListener.disable(); 
} 

/** 
* Performs required action to accommodate new orientation 
* @param orientation 
* @param lastOrientation 
*/ 
private void changeRotation(int orientation, int lastOrientation) { 
    switch (orientation) { 
     case ORIENTATION_PORTRAIT_NORMAL: 
      mSnapButton.setImageDrawable(getRotatedImage(android.R.drawable.ic_menu_camera, 270)); 
      mBackButton.setImageDrawable(getRotatedImage(android.R.drawable.ic_menu_revert, 270)); 
      Log.v("CameraActivity", "Orientation = 90"); 
      break; 
     case ORIENTATION_LANDSCAPE_NORMAL: 
      mSnapButton.setImageResource(android.R.drawable.ic_menu_camera); 
      mBackButton.setImageResource(android.R.drawable.ic_menu_revert); 
      Log.v("CameraActivity", "Orientation = 0"); 
      break; 
     case ORIENTATION_PORTRAIT_INVERTED: 
      mSnapButton.setImageDrawable(getRotatedImage(android.R.drawable.ic_menu_camera, 90)); 
      mBackButton.setImageDrawable(getRotatedImage(android.R.drawable.ic_menu_revert, 90)); 
      Log.v("CameraActivity", "Orientation = 270"); 
      break; 
     case ORIENTATION_LANDSCAPE_INVERTED: 
      mSnapButton.setImageDrawable(getRotatedImage(android.R.drawable.ic_menu_camera, 180)); 
      mBackButton.setImageDrawable(getRotatedImage(android.R.drawable.ic_menu_revert, 180));  
      Log.v("CameraActivity", "Orientation = 180"); 
      break; 
    } 
} 

    /** 
* Rotates given Drawable 
* @param drawableId Drawable Id to rotate 
* @param degrees  Rotate drawable by Degrees 
* @return    Rotated Drawable 
*/ 
private Drawable getRotatedImage(int drawableId, int degrees) { 
    Bitmap original = BitmapFactory.decodeResource(getResources(), drawableId); 
    Matrix matrix = new Matrix(); 
    matrix.postRotate(degrees); 

    Bitmap rotated = Bitmap.createBitmap(original, 0, 0, original.getWidth(), original.getHeight(), matrix, true); 
    return new BitmapDrawable(rotated); 
} 

Y luego, en su conjunto PictureCallback metadatos para indicar el nivel de rotación:

private Camera.PictureCallback mJpegCallback = new Camera.PictureCallback() { 

    @Override 
    public void onPictureTaken(byte[] data, Camera camera) { 
     try { 
      // Populate image metadata 

      ContentValues image = new ContentValues(); 
      // additional picture metadata 
      image.put(Media.DISPLAY_NAME, [picture name]); 
      image.put(Media.MIME_TYPE, "image/jpg"); 
      image.put(Media.TITLE, [picture title]); 
      image.put(Media.DESCRIPTION, [picture description]); 
      image.put(Media.DATE_ADDED, [some time]); 
      image.put(Media.DATE_TAKEN, [some time]); 
      image.put(Media.DATE_MODIFIED, [some time]); 

      // do not rotate image, just put rotation info in 
      switch (mOrientation) { 
       case ORIENTATION_PORTRAIT_NORMAL: 
        image.put(Media.ORIENTATION, 90); 
        break; 
       case ORIENTATION_LANDSCAPE_NORMAL: 
        image.put(Media.ORIENTATION, 0); 
        break; 
       case ORIENTATION_PORTRAIT_INVERTED: 
        image.put(Media.ORIENTATION, 270); 
        break; 
       case ORIENTATION_LANDSCAPE_INVERTED: 
        image.put(Media.ORIENTATION, 180); 
        break; 
      } 

      // store the picture 
      Uri uri = getContentResolver().insert(
        Media.EXTERNAL_CONTENT_URI, image); 

      try { 
       Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, 
         data.length); 
       OutputStream out = getContentResolver().openOutputStream(
         uri); 
       boolean success = bitmap.compress(
         Bitmap.CompressFormat.JPEG, 75, out); 
       out.close(); 
       if (!success) { 
        finish(); // image output failed without any error, 
           // silently finish 
       } 

      } catch (Exception e) { 
       e.printStackTrace(); 
       // handle exceptions 
      } 

      mResultIntent = new Intent(); 
      mResultIntent.setData(uri); 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 

     finish(); 
    } 
}; 

espero que ayude.

ACTUALIZACIÓN Ahora, cuando aparecen dispositivos basados ​​en el paisaje, se requiere una comprobación adicional en OrientationEventListener.

Display display = ((WindowManager)getSystemService(WINDOW_SERVICE)).getDefaultDisplay();           
if (display.getOrientation() == Surface.ROTATION_0) { 
    // landscape oriented devices 
} else { 
    // portrait oriented device 
} 

código completo (un poco desperdicio por LC, pero fácilmente demuestra el enfoque)

@Override 
public void onOrientationChanged(int orientation) { 

    // determine our orientation based on sensor response 
    int lastOrientation = mOrientation; 

    Display display = ((WindowManager)getSystemService(WINDOW_SERVICE)).getDefaultDisplay();           

    if (display.getOrientation() == Surface.ROTATION_0) { // landscape oriented devices 
     if (orientation >= 315 || orientation < 45) { 
      if (mOrientation != ORIENTATION_LANDSCAPE_NORMAL) {       
       mOrientation = ORIENTATION_LANDSCAPE_NORMAL; 
      } 
     } else if (orientation < 315 && orientation >= 225) { 
      if (mOrientation != ORIENTATION_PORTRAIT_INVERTED) { 
       mOrientation = ORIENTATION_PORTRAIT_INVERTED; 
      }      
     } else if (orientation < 225 && orientation >= 135) { 
      if (mOrientation != ORIENTATION_LANDSCAPE_INVERTED) { 
       mOrientation = ORIENTATION_LANDSCAPE_INVERTED; 
      }      
     } else if (orientation <135 && orientation > 45) { 
      if (mOrientation != ORIENTATION_PORTRAIT_NORMAL) { 
       mOrientation = ORIENTATION_PORTRAIT_NORMAL; 
      }      
     }      
    } else { // portrait oriented devices 
     if (orientation >= 315 || orientation < 45) { 
      if (mOrientation != ORIENTATION_PORTRAIT_NORMAL) {       
       mOrientation = ORIENTATION_PORTRAIT_NORMAL; 
      } 
     } else if (orientation < 315 && orientation >= 225) { 
      if (mOrientation != ORIENTATION_LANDSCAPE_NORMAL) { 
       mOrientation = ORIENTATION_LANDSCAPE_NORMAL; 
      }      
     } else if (orientation < 225 && orientation >= 135) { 
      if (mOrientation != ORIENTATION_PORTRAIT_INVERTED) { 
       mOrientation = ORIENTATION_PORTRAIT_INVERTED; 
      }      
     } else if (orientation <135 && orientation > 45) { 
      if (mOrientation != ORIENTATION_LANDSCAPE_INVERTED) { 
       mOrientation = ORIENTATION_LANDSCAPE_INVERTED; 
      }      
     } 
    } 

    if (lastOrientation != mOrientation) { 
     changeRotation(mOrientation, lastOrientation); 
    } 
} 
+1

Terminé usando la actividad de la cámara por defecto, sin embargo, esto se ve bien, puedo volver a esto en otro momento. Gracias. –

+2

Bienvenido. Honestamente, implementé mi propia actividad de cámara debido a todas las inconsistencias con la actividad predeterminada de la cámara en diferentes teléfonos Android y no me sentía bien haciendo todo tipo de hacks para rodearlos. – Audrius

+0

@Audrius Creé la cámara personalizada simple con botones pero necesito girar la cámara en 4 lados del dispositivo ... ¿cómo hacerlo? –

17

Ha considerado el uso del método estándar de los thats proporcionada en la documentación del API, que se puede llamar en surfaceChanged? Puede guardar los grados en una variable global para utilizarlos más adelante al guardar la imagen. También podría hacer un simple verificador nulo en la variable de su cámara, para que no lo vuelva a crear en surfaceCreated.

public void setCameraDisplayOrientation() 
{   
    if (mCamera == null) 
    { 
     Log.d(TAG,"setCameraDisplayOrientation - camera null"); 
     return;    
    } 

    Camera.CameraInfo info = new Camera.CameraInfo(); 
    Camera.getCameraInfo(CAM_ID, info); 

    WindowManager winManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 
    int rotation = winManager.getDefaultDisplay().getRotation(); 

    int degrees = 0; 

    switch (rotation) 
    { 
     case Surface.ROTATION_0: degrees = 0; break; 
     case Surface.ROTATION_90: degrees = 90; break; 
     case Surface.ROTATION_180: degrees = 180; break; 
     case Surface.ROTATION_270: degrees = 270; break; 
    } 

    int result; 
    if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) 
    { 
     result = (info.orientation + degrees) % 360; 
     result = (360 - result) % 360; // compensate the mirror 
    } else { // back-facing 
     result = (info.orientation - degrees + 360) % 360; 
    } 
    mCamera.setDisplayOrientation(result); 
} 
+0

Si va a implementarlo, tenga en cuenta que CAM_ID es una variable global, que configuré en otro lugar. – OriginalCliche

+2

El código anterior no me funciona .. probado en ** Samsung S2 **. – swiftBoy

+0

¿Dónde tenemos que llamar setCameraDisplayOrientation() en surfaceCreaeted/surfaceChanged o onResume – Nepster

2

Como ha visto por las otras respuestas, este código se vuelve muy complicado. Es posible que desee para investigar el uso de una biblioteca para ayudarle a proporcionar esta característica, por ejemplo, CWAC-cámara es compatible con OS 2.3 y arriba (con suerte se puede soltar OS 2.1 y OS 2.2 apoyo ahora):
https://github.com/commonsguy/cwac-camera

CWAC-cámara admite el bloqueo de la vista previa de la cámara al paisaje, y rotará automáticamente las imágenes en la orientación de corrección por usted. Busque el project issues si desea conocer todos los problemas específicos del dispositivo que deben resolverse, qué IMO son más razones para intentar usar una biblioteca en lugar de mantener todo este código y probarse usted mismo.