четверг, 21 февраля 2013 г.

Работа с камерой

В этой статье посмотрим как работать с камерой в android. Есть два способа получить фото или видео в вашем приложении:
* использование намерений (Intent) для вызова существующего приложения.
* непосредственное обращение к камере.

I. Использование существующего приложения

1. Использование намерений Intent это быстрый способ получить фото или видео, написав при этом минимум кода.
Класс Intent — абстрактное описание выполняемой операции. Мы говорим системе android, какое действие мы хотим выполнить, а ОС сама находит и запускает необходимое приложение и возвращает результат его работы. Для работы с камерой, используются два типа намерений:
* MediaStore.ACTION_IMAGE_CAPTURE - для запроса изображения.
* MediaStore.ACTION_VIDEO_CAPTURE - для запроса видео.

2. После формирования намерения, мы запускаем его с помощью метода void startActivity(Intent i) или void startActivityForResult (Intent i, int requestCode). Первый метод просто вызывает требуемое приложение, а второй метод вызывает приложение и после его завершения вызывает метод onActivityResult (), позволяя обработать результат.

3. Создаем приложение (у меня CameraTest), на активность CameraTestActivity добавляем кнопку (button) и изображение ImageView (image). В классе CameraTestActivity будет всего три метода

public class CameraTestActivity extends Activity {
    
  Button mButton;
  ImageView mImage;
  Uri mUri;
  Context mContext;
    
  private static final int PHOTO_INTENT_REQUEST_CODE = 100;
    
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
        
    mContext = this;
            
    mButton = (Button) findViewById(R.id.button);
    mImage = (ImageView) findViewById(R.id.image);
        
    mButton.setOnClickListener(new View.OnClickListener() {
      public void onClick(View v) {
        mUri = generateFileUri();
        if (mUri == null) {
          Toast.makeText(mContext, "SD card not available", Toast.LENGTH_LONG).show();
          return;
        }

        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, mUri);
        startActivityForResult(intent, PHOTO_INTENT_REQUEST_CODE);
      }
    });
  }

 ...
}
По нажатию на кнопку мы создаем uri, то есть полный путь к файлу, в котором мы хотим сохранить наше изображение с помощью метода generateFileUri() (о нем ниже). Если по каким-то причинам (нет SD карты или у нас нет доступа на запись), то выдаем сообщение и завершаем работу.

Если создание uri прошло хорошо, то создаем намерение, с типом MediaStore.ACTION_IMAGE_CAPTURE. Для передачи данных в Intent используется extras, это набор данных в виде ключ-значение. Мы передаем ему наш созданный uri с ключом MediaStore.EXTRA_OUTPUT. И стартуем намерение.

PHOTO_INTENT_REQUEST_CODE это константа requestCode. Смысл ее в том, что из одной активности мы можем запускать несколько типов намерений с помощью метода startActivityForResult, но после завершения запускаемых приложений вызывается один метод onActivityResult, в который и передается эта константа, позволяющая определить какое именно намерение было выполнено. Значение таких констант выбирается случайным образом.

4. Метод generateFileUri() выглядит так:
private Uri generateFileUri() {

  if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
    return null;

  File path = new File (Environment.getExternalStorageDirectory(), "CameraTest");
  if (! path.exists()){
    if (! path.mkdirs()){
      return null;
    }
  }
            
  String timeStamp = String.valueOf(System.currentTimeMillis());
  File newFile = new File(path.getPath() + File.separator + timeStamp + ".jpg");
  return Uri.fromFile(newFile);
}
Вначале с помощью метода getExternalStorageState() класса Environment, мы получаем состояние SD карты и проверяем, что оно равно константе Environment.MEDIA_MOUNTED, то есть карта есть, примонтирована и работает.

Затем мы проверяем есть ли на карте директория «CameraTest», и если нет, создаем ее.

И в конце создаем файл. В качестве имени я беру текущее время в милисекундах (это что-то типа «136094990508.jpg»).

Отмечу, что при вызове new File (name) сам файл на диске еще не создается.

5. Для работы с SD картой нам нужны соответствующие права, поэтому в файл AndroidManifest.xml необходимо добавить строчку uses-permission
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="mj.android.cameratest"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <application
    ...
    </application>

</manifest>
В метод onActivityResult, кроме requestCode о котором говорилось выше, передается также
resultCode — успешно ли завершило свою работу приложение, или было отменено пользователем.
data — данные, которые возвратило приложение в случае успешного завершения работы.

Если намерение удачно выполнено, то мы передаем в ImageView uri файла для отображения, если нет, выдаем сообщение об ошибке.

Многие приложения с камерой возвращают в extra с ключом «data» само изображение (Bitmap). Эта строчка в листинге закомментирована, потому что происходит это не всегда.

7. Запускаем, все работает. Вызов намерения для съемки видео аналогичен.

II. Собственное приложение для работы с камерой

Теперь посмотрим как непосредственно обратиться к камере и сделать снимок без использования сторонних приложений. Создадим новый проект (у меня JustCapture).

1. В первую очередь добавим соответствующие разрешения в AndroidManifest.xml. Нам необходимо иметь доступ к камере и к записи на SD карту.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="mj.android.justcapture"
  android:versionCode="1"
  android:versionName="1.0" >

  <uses-permission android:name="android.permission.CAMERA" />
  <uses-feature android:name="android.hardware.camera" />
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest>
Кроме двух uses-permission я также добавила uses-feature, это параметр, который для установки приложения требует наличие камеры. Этот параметр необязателен, но устанавливать нашу программу на устройство без камеры смысла все равно нет.

2. Вначале немного теории. Для работы с камерой нам потребуются следующие классы:
Camera — класс для работы с камерой. В нем нас интересуют методы:
int getNumberOfCameras() - возвращает кол-во камер, доступных на устройстве
Camera open(int cameraId) — возвращает экземпляр класса Camera для доступа к камере номер cameraId. Есть также метод Camera open() без параметров, который предоставляет доступ к первой задней камере (если на устройстве есть только фронтальная камера,то метод вернет null)
void setPreviewDisplay(SurfaceHolder holder) — устанавливает экран предварительного просмотра для камеры.
void startPreview() - включается камера и начинает показывать изображение на экран предварительного просмотра
void stopPreview () - остановка предварительного просмотра
void takePicture (Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback postview, Camera.PictureCallback jpeg) — собственно захват изображения. Все четыре параметра являются по сути методами обратного вызова.
- shutter — класс реализующий интерфейс Camera.ShutterCallback с методом onShutter(), который будет вызван сразу после захвата изображения. Сюда можно поместить, например, проигрывание звука затвора.
- raw, postview и jpeg — классы реализующие интерфейс Camera.PictureCallback с методом onPictureTaken(byte[] data, Camera camera), в который передаются необработанные данные (raw), обработанные (postview) или изображение (jpeg). Любой из 4 параметров может быть null.
void release () - отключает камеру и делает ее доступной для других приложений. Важно не забывать вызывать этот метод после окончания работы с камерой.

SurfaceView - элемент управления, предоставляющий область (Surface) для рисования или в нашем случае отображения предварительного просмотра камеры. Доступ к области рисования осуществляется через интерфейс SurfaceHolder, который можно получить методом getHolder() класса SurfaceView. Этот Holder (не знаю как это сказать по русски) мы передаем в метод setPreviewDisplay(), связывая таким образом область просмотра с камерой.
SurfaceHolder.Callback — интерфейс предоставляющий методы для отлавливания и обработки изменения состояния области рисования
- surfaceCreated(SurfaceHolder holder) — создание области
- surfaceChanged(SurfaceHolder holder, int format, int width, int height) — любое изменение области (например, поворот экрана).
- surfaceDestroyed(SurfaceHolder holder) — уничтожение Surface.
Для того, чтобы отображать на экране предпросмотр камеры мы напишем класс-наследник SurfaceView и реализуем у этого класса интерфейс SurfaceHolder.Callback, чтобы правильно инициализировать область при создании и реагировать на повороты экрана.

Camera.CameraInfo — класс позволяющий узнать ориентацию камеры и ее тип (фронтальная или задняя). Для получения используется статический метод Camera.getCameraInfo(int cameraId, Camera.CameraInfo cameraInfo). В который передается id камеры и экземпляр класса CameraInfo в котором будет содержаться информация о камере.
Camera.Parameters — класс устанавливающий параметры съемки, такие как баланс белого, эффекты, вспышку и т. д. Для работы используются методы getParameters() и setParameters() класса Camera.

3. На этом теоретическая часть заканчивается, приступим к коду. На главной activity приложения будет два элемента: экран предварительного просмотра и кнопка по нажатию на которую будет происходить захват изображения. Причем я хочу чтобы просмотр был на весь экран, а кнопка располагалась поверх этого экрана. Для этого в качестве основного layout'a будем использовать FrameLayout. Напишем в main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:id="@+id/layout">

 <ImageButton
  android:id="@+id/capture" 
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:contentDescription="@string/capture"
  android:layout_gravity="bottom|center"
  android:layout_marginBottom="10dp"
  android:src="@drawable/capture"
  android:background="@drawable/empty"
 />
</FrameLayout>
В качестве кнопки я для красоты использую ImageButton. Как видно, никакого экрана просмотра тут нет, его мы добавим программно.

4. В классе JustCaptureActivity пишем
public class JustCaptureActivity extends Activity  {

  ImageButton capture;
  CameraPreview preview;
  Camera mCamera;
  FrameLayout mFrame;
  Context mContext;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    
    mContext = this;
        
    mCamera = openCamera (); //1
    if (mCamera == null) { //2
      Toast.makeText(this, "Opening camera failed", Toast.LENGTH_LONG).show();
      return;
    }
        
    preview = new CameraPreview (this, mCamera); //3
    mFrame = (FrameLayout) findViewById(R.id.layout); //4
    mFrame.addView(preview, 0);
        
    capture = (ImageButton) findViewById(R.id.capture); //5
    capture.setOnClickListener(
      new View.OnClickListener() {
        @Override
        public void onClick(View v) {
          // делаем снимок
        }
      }
     );
  }

  private Camera openCamera() { 
    if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA))
      return null;
        
    Camera cam = null;
    if (Camera.getNumberOfCameras() > 0) {
      try {
        cam = Camera.open(0);
      }
      catch (Exception exc) {
        //
      }
    }
    return cam; 
  }

  @Override
  protected void onPause() { //6
    super.onPause();
    if (mCamera != null){
      mCamera.release();
      mCamera = null;
    }
  }
}
1) В методе openCamera() мы получаем доступ к камере. Вначале мы проверяем, что камера есть на устройстве, хотя мы написали в манифесте, что на устройства без камеры программа ставиться не будет, но все-таки проверим. Затем открываем камеру с кодом «0». Если камер больше 1, то можно спрашивать у пользователя какую камеру он хочет использовать, но мы пока этим заниматься не будем.
2) После вызова openCamera() проверяем, если метод не смог получить доступ к камере и возвратил null, то выводим сообщение и заканчиваем работу
3) Создаем экземпляр CameraPreview, для отображения предварительного просмотра (об этом классе ниже)
4) Добавляем созданный экземпляр CameraPreview в FrameLayout активности. В методе addView() второй параметр обозначает порядковый номер добавляемого элемента. Мы добавляем его с номером 0, чтобы он был ниже кнопки и не перекрывал ее.
5) Создаем обработчик нажатия на кнопку, пока там пусто, захват изображения сделаем позже.
6) Переопределим метод onPause(). Он вызывается каждый раз когда активность закрывается, сворачивается или перекрывается другой активностью. В каждом из этих случаев нам нужно отключить камеру и сделать ее доступной для других приложений. После того как активность вновь будет доступна, будет вызван метод onCreate() где камера вновь будет захвачена.

5. Теперь напишем класс CameraPreview. Как уже говорилось, это будет класс наследник SurfaceView, реализующий интерфейс Callback.
public class CameraPreview extends SurfaceView implements Callback {
    
  SurfaceHolder mHolder;
  Camera mCamera;
  Context mContext;
    
  public CameraPreview(Context context, Camera camera) { //1
    super(context);
    mContext = context;
    mCamera = camera;
    mHolder = getHolder(); //2
    mHolder.addCallback(this); //3
        
    mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); //4
  }

  @Override
  public void surfaceCreated(SurfaceHolder holder) { //5
    try {
      mCamera.setPreviewDisplay(holder);
      mCamera.startPreview();
    }
    catch (IOException e) {
      Toast.makeText(mContext, "Camera preview failed", Toast.LENGTH_LONG).show();
    }
  }
    
  // 6
  @Override
  public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

  }

  @Override
  public void surfaceDestroyed(SurfaceHolder holder) {

  }
}
1) В конструкторе получаем экземпляр класса Camera для доступа к камере. Этот экземпляр уже должен быть проинициализирован, потому что тут никаких проверок мы не делаем.
2) Получаем SurfaceHolder этого SurfaceView, для управления отображением.
3) Устанавливаем методы обратного вызова. Наш класс реализует интерфейс Callback, так что его и передаем.
4) Эта строчка нужна только для версии android меньше 3.0,
5) Метод surfaceCreated() будет вызван при первой отрисовке элемента на активности. В нем мы устанавливаем для камеры экран для предпросмотра и включаем камеру
6) Метод surfaceChanged() вызывается при любом изменении элемента, например при изменении размера или ориентации экрана. Метод surfaceDestroyed() вызывается при уничтожении элемента, то есть при закрытии активности (или просто удалении его с активности). Эти методы пока оставим пустыми.

6. Запустим наше приложение.
1
(Извиняюсь за темный снимок)

Камера включается и мы видим предпросмотр. Однако если повернуть телефон, мы увидим, что препросмотр отображается совсем не так как ожидается, он не поворачивается. Как мы помним при повороте экрана(и изменении размера области просмотра) вызывается метод surfaceChanged(), напишем в нем обработку поворота. При изменении SurfaceView необходимо вначале остановить вывод в него предпросмотра камеры, а после настройки вновь запустить.
Напишем в классе CameraPreview
public class CameraPreview extends SurfaceView implements Callback {
    
    ...    
  @Override
  public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        
    if (mHolder.getSurface() == null)
      return;

    mCamera.stopPreview();

    setCameraDisplayOrientation();
        
    try {
      mCamera.setPreviewDisplay(mHolder);
      mCamera.startPreview();
    } 
    catch (IOException e) {
      Toast.makeText(mContext, "Camera preview failed", Toast.LENGTH_LONG).show();
    }
  }

  public void setCameraDisplayOrientation() {        
    if (mCamera == null)
      return;             

    Camera.CameraInfo info = new Camera.CameraInfo(); // 1
    Camera.getCameraInfo(0, info);

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

    int degrees = 0;

    switch (rotation) { //3
      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; //4
    if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
      result = (info.orientation + degrees) % 360;
      result = (360 - result) % 360;
    }
    else {
      result = (info.orientation - degrees + 360) % 360;
    }

    mCamera.setDisplayOrientation(result); //5
    
    Camera.Parameters parameters = mCamera.getParameters(); //6
    int rotate = (degrees + 270) % 360;
    parameters.setRotation(rotate);
    mCamera.setParameters(parameters);
  }
}
1) Получаем информацию о камере. Мы можем узнать ее ориентацию и тип (фронтальная / задняя). Я беру информацию о камере с кодом 0, поэтому если в п.4(1) давался выбор камеры пользователю, это надо учитывать здесь.
2) Получаем ориентацию экрана. Результат возвращается не в градусах, а в виде констант.
3) Переводим эти константы в градусы.
4) Рассчитываем на сколько нам надо повернуть экран просмотра. Эти магические заклинания были взяты в официальной документации, так что поверим на слово ))
5) Устанавливаем угол поворота экрана просмотра.
6) Получаем параметры камеры. Рассчитываем и устанавливаем поворот камеры.

Зачем нужно два раза устанавливать поворот камеры причем разными цифрами?
В первом случае мы устанавливаем на сколько надо повернуть экран просмотра чтобы картинка отображалась правильно.
Во втором случае мы указываем как нужно повернуть сохраненное изображение. Эта цифра равна (ориентация экрана минус 90 градусов). Чтобы не заморачиваться с отрицательными числами, я прибавляю 270 и беру остаток от деления на 360.
Второй поворот часто не делают, жестко устанавливая в AndroidManifest.xml ориентацию экрана для активности.
android:screenOrientation="landscape"
7. Запускаем еще раз. Теперь можно крутить девайс и изображение будет поворачивать следом. Последний метод в классе CameraPreview — surfaceDestroyed(), который вызывается при уничтожении области просмотра мы оставим пустым.

8. Теперь перейдем к сохранению нашего изображения
По кнопке Capture необходимо сохранить фотографию, делается это с помощью метода takePicture()
capture.setOnClickListener(
  new View.OnClickListener() {
    @Override
    public void onClick(View v) {
      mCamera.takePicture(null, null, null, mPictureCallback);
    }
  }
);
Первые три параметра передаем null (это действие сразу после нажатия, сохранение raw и postview), а последний параметр — класс реализующий PictureCallback с переопределенным методом onPictureTaken() в который передается сохраненная картинка в формате jpeg.

Сам mPictureCallback выглядит так
private PictureCallback mPictureCallback = new PictureCallback() {

  @Override
  public void onPictureTaken(byte[] data, Camera camera) {

    Uri pictureFile = generateFile(); //1
    try {
      savePhotoInFile (data, pictureFile); //2
      Toast.makeText(mContext, "Save file: " + pictureFile, Toast.LENGTH_LONG).show();
    }
    catch (Exception e) {
      Toast.makeText(mContext, "Error: can't save file", Toast.LENGTH_LONG).show();
    }
    mCamera.startPreview(); //3
  }
};
1) Генерируем имя для нового файла (это функция была описана в предущей программе)
2) Сохраняем файл и сообщаем пользователю получилось ли это
3) После захвата изображения предпросмотр останавливается, его надо стартануть заново.

Методы generateFile() и savePhotoInFile() должны быть понятны
private Uri generateFile() {
  if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
    return null;

  File path = new File (Environment.getExternalStorageDirectory(), "CameraTest");
  if (! path.exists()){
    if (! path.mkdirs()){
      return null;
    }
  }
            
  String timeStamp = String.valueOf(System.currentTimeMillis());
  File newFile = new File(path.getPath() + File.separator + timeStamp + ".jpg");
  return Uri.fromFile(newFile);
}


private void savePhotoInFile(byte[] data, Uri pictureFile) throws Exception {
    
  if (pictureFile == null)
    throw new Exception();

  OutputStream os = getContentResolver().openOutputStream(pictureFile);
  os.write(data);
  os.close();
}
9. Запускаем. Теперь можно нажать на кнопку и наша фотография сохраниться на SD карте.

10. В предыдущем примере мы объявляли намерения и система android запускала приложение для работы с камерой. Чем наше приложение хуже? Сделаем чтобы оно тоже откликалось на такие намерения.
Делается это в несколько строчек - добавим нужный intent-filter в AndroidManifest.xml
<activity
  android:name="mj.android.justcapture.JustCaptureActivity"
  android:label="@string/app_name" >
  <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
  </intent-filter>
  <intent-filter>
    <action android:name="android.media.action.IMAGE_CAPTURE" />
    <category android:name="android.intent.category.DEFAULT" />
  </intent-filter>
</activity>
11. Запустим наше предыдущее приложение CameraTest, теперь система предлагает нам выбор
2
12. Казалось бы работа сделана, но при запуске мы обнаружим, что при захвате изображения наше приложение просто сохраняет фотографию и заново включает предпросмотр. Значит, нам надо предусмотреть два поведения программы после захвата изображения. Если программа была просто открыта, то она должна вести себя как сейчас (то есть сохранять изображение и продолжать работу). Если же она была вызвана по намерению IMAGE_CAPTURE, она должна спрашивать пользователя нравится ли ему картинка и если да, возвращала ее в активность из которого была вызвана.

13. Для начала добавим в main.xml еще две кнопки (листинг в сокращенном виде, кнопки имеют те же параметры что и первая кнопка, обе расположены в LinearLayout)
<FrameLayout>
  <ImageButton android:id="@+id/capture" />
    
  <LinearLayout android:id="@+id/buttons" android:visibility="invisible" >
    <ImageButton android:id="@+id/recapture" />
    <ImageButton android:id="@+id/finish" />
  </LinearLayout>

</FrameLayout>
Полный вариант main.xml можно посмотреть в исходниках (ссылка как всегда внизу). Отмечу, что LinearLayout с двумя кнопками и кнопка capture находятся в одном месте экрана и никогда не будут показываться одновременно. Вначале работы программы мы скрываем LinearLayout. Когда нам понадобиться запросить у пользователя подтверждение сохранения, кнопку capture спрячем, а две кнопки покажем.
Добавим для этого два метода в класс JustCaptureActivity
private void showConfirm() {
  capture.setVisibility(View.INVISIBLE);
  mButtons.setVisibility(View.VISIBLE);
}
    
private void hideConfirm() {
  mButtons.setVisibility(View.INVISIBLE);
  capture.setVisibility(View.VISIBLE);
}
14. В методе onCreate() сохраним намерение по котором было вызвано наше приложение
public class JustCaptureActivity extends Activity  {

  Intent intent;
  byte[] pictureData;
  ... 

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    intent = getIntent();
    ...
  }
}
А в методе onPictureTaken() перед сохранением необходимо проверить было ли наше приложение вызвано по намерению IMAGE_CAPTURE
public void onPictureTaken(byte[] data, Camera camera) {
            
  if (intent.getAction().equals (MediaStore.ACTION_IMAGE_CAPTURE)) {
    pictureData = data;
    showConfirm ();
  }
  else {
    // то что раньше было в методе
  }
}
Если приложение было вызвано по намерению IMAGE_CAPTURE, сохраняем в переменную картинку и показываем кнопки подтверждения.
Кстати, если приложение было просто открыто (из меню), intent.getAction() будет равно «android.intent.action.MAIN»

15. Осталось обработать нажатие на эти две кнопки. По кнопке recapture (отменить и сделать еще один снимок)
recapture.setOnClickListener(
  new View.OnClickListener() {
    @Override
    public void onClick(View v) {
      hideConfirm();
      mCamera.startPreview();
      pictureData = null;
    }
  }
);
Тут все просто. Скрываем кнопки подтверждения, показываем кнопку capture, стартуем камеру и стираем сохраненное в переменной изображение

По кнопке finish (сохранить и выйти)
save.setOnClickListener(
  new View.OnClickListener() {
    @Override
    public void onClick(View v) {
      Uri pictureFile;
                        
      if (intent.hasExtra(MediaStore.EXTRA_OUTPUT)) // 1
        pictureFile = (Uri)intent.getExtras().getParcelable(MediaStore.EXTRA_OUTPUT);
      else
        pictureFile = generateFile();
                        
      try {
      savePhotoInFile (pictureData, pictureFile); //2
      intent.putExtra("data", pictureData); //3
      intent.setData(pictureFile);
      setResult(RESULT_OK, intent);
    } catch (Exception e) {
      setResult(2, intent); //4
    }
    mCamera.release();
    finish();            
    }
  }
);
1) Проверяем передовался ли в намерение в extras путь для сохранения изображения. Если нет,то генерим этот путь
2) Сохраняем изображение
3) Если сохранение прошло успешно (а иначе мы провалимся в catch), то кладем саму картинку в extras с ключом «data», а путь к картинке как данные намерения. Указываем, что намерение завершилось успешно (RESULT_OK)
4) Если сохранить не получилось, то никакие данные в намерение мы не сохраняем и указываем, что намерение завершилось с ошибкой. Есть две константы RESULT_OK (ее значение -1) и RESULT_CANCELED (значение 0) для обозначения успешного завершения программы и отмены программы (например ее закрытие без каких-либо действий). У нас все-таки не отмена, поэтому используем просто любое число отличное от 0 и -1.

16. Все, можно запускать :) Я также проверяла работу JustCapture на других программах вызывающих приложение камеры (Вконтакте и Catch). У меня все работает.

Все вышеописанное я проверяла на планшете с версией android 4.1 и одной фронтальной камерой. Поэтому могут быть ошибки связанные с более ранней версией ОС или с использованием задней камеры. Если обнаружится, пишите в комментарии.

Исходники
- Вызов существующего приложения CameraTest.zip
- Собственное приложение JustCapture.zip

2 комментария:

  1. Скажите, а можно как нибудь передавать в превью измененное изображение? Как это реализовать?

    ОтветитьУдалить
  2. как в вашем примере сделать жесткую привязку только к фронтальной камере, чтобы всегда включалась только она?

    ОтветитьУдалить