Введение в разработку приложений для ОС Android

Многооконное приложение

Разбить на страницы
Показывать лекцию целиком

Цель лабораторной работы:

Разработка многооконного приложения, предоставляющего возможности: воспроизведения аудио и видео файлов, создания и отображения фотоснимков.

Задачи лабораторной работы:

  • настроить интерфейс и реализовать логику активности для работы с камерой;
  • настроить интерфейс и реализовать логику активности для воспроизведения аудио и видео;
  • настроить интерфейс и реализовать логику активности для просмотра изображений;
  • настроить интерфейс и реализовать логику главной активности приложения.
  • 12.1 Введение

    Для достижения цели, поставленной в лабораторной работе, сформулируем требования к разрабатываемому приложению, назовем его "Media".

    Приложение предоставляет пользователю возможность выбора рода деятельности:

  • работа с камерой для создания снимков;
  • воспроизведение аудио и видео;
  • просмотр изображений.
  • В приложении предполагается реализовать четыре активности:

  • главная активность, предназначена для выбора рода деятельности, содержит три кнопки, нажатие на каждую кнопку вызывает к жизни соответствующую активность;
  • активность для работы с камерой и создания снимков;
  • активность для воспроизведения аудио и видео;
  • активность для просмотра изображений.
  • 12.2 Создание приложения

    Создадим новое приложение, активность, полученную при создании проекта, оставим с именем MainActivity, она будет главной активностью приложения. Добавим в приложение еще три активности: New->Other...->Android Activity.

    В нашем приложении созданные активности имеют следующие имена:

    CameraActivity для работы с камерой и создания снимков;

    MediaActivity для воспроизведения видео и аудио;

    GalleryActivity для просмотра изображений.

    Далее продолжим работу с этими активностями, настроим интерфейс и реализуем логику для каждой из них. Начнем с активностей, отвечающих за тот или иной вид деятельности, главную активность приведем в порядок в самом конце работы.

    12.3 Настройка интерфейса и реализация логики активности для работы с камерой

    Настроим интерфейс активности для работы с камерой. Нам понадобится окно предварительного просмотра, добавим в окно активности элемент .

    (рис 12.1) Интерфейс активности для работы с камерой

    Реализуем логику активности, в данном случае необходимо при нажатии на кнопку выполнить снимок. Работаем с java файлом, описывающим соответствующий класс активности, CameraActivity.java.

    Объявим следующие поля класса активности:

    private Camera camera; //для проведения всех операций с камерой
    private SurfaceHolder surfaceHolder; //для задания preview
    private SurfaceView preview; //для отображения окна предпросмотра
    private View shotBtn;  //для выполнения снимка (кнопка)
        

    Для начала работы с камерой необходимо ее инициализировать, сделать это лучше в методе onResume() класса активности, для инициализации используется следующая конструкция:

    camera = Camera.open();
        

    После завершения работы с камерой, необходимо ее освободить для других приложений, сделать это лучше в методе onPause() класса активности, для освобождения камеры используется следующая конструкция:

    Camera.release();
        

    Для активности, работающей с камерой, имеет смысл сразу задать расположение экрана следующим образом

    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        

    в противном случае, приходится, например, в методе surfaceCreated() проверять расположение экрана и поворачивать окно предварительного просмотра, что не очень удобно, так как поворот экрана занимает некоторое время.

    Выполним настройку окна предварительного просмотра:

    preview = (SurfaceView) findViewById(R.id.surfaceCamera);
    surfaceHolder = preview.getHolder();
    surfaceHolder.addCallback(new MyCallback());
        

    метод addCallback() используется для отслеживания изменений окна предпросмотра. Параметром этого метода служит экземпляр класса реализующего интерфейс SurfaceHolder.Callback, в нашем случае для реализации этого интерфейса создан внутренний класс активности MyCallback.

    В этом классе необходимо реализовать методы:

    public void surfaceCreated(SurfaceHolder holder);
    public void surfaceChanged(SurfaceHolder holder, int format, 
    int width, int height);
    public void surfaceDestroyed(SurfaceHolder holder);
        

    Мы реализуем метод surfaceCreated(), оставшиеся два метода запишем с пустой реализацией.

    В методе surfaceCreated() зададим окно предварительного просмотра:

    camera.setPreviewDisplay(holder);
        

    Выполним необходимые настройки окна предпросмотра (см. Листинг 12.1).

    Запустим отображение окна предварительного просмотра:

    camera.startPreview();
        

    Пришло время обсудить и реализовать выполнение фотосъемки. Для того, чтобы сделать снимок необходимо вызвать метод takePicture(). Вызов этого метода можно выполнить из метода-обработчика события нажатия кнопки, тогда при нажатии на кнопку сразу будет получена фотография, а можно "растянуть удовольствие" и воспользоваться предварительной автофокусировкой.

    Сначала инициализируем кнопку и добавим слушателя события нажатия в методе onCreate() активности:

    shotBtn = findViewById(R.id.bCameraShot);
    shotBtn.setOnClickListener(new MyViewListener());
        

    В данном случае слушателем является экземпляр внутреннего класса MyViewListener, который реализует интерфейс View.OnClickListener.

    class MyViewListener implements View.OnClickListener{
        @Override
        public void onClick(View v){
            if (v == shotBtn){
                camera.autoFocus(new MyAutoFocusCallback());
            }
        }
    }
        

    В методе onClick() вызываем обработчик автофокуса, слушателем события автофокусировки в данном случае является экземпляр внутреннего класса MyAutoFocusCallback, который реализует интерфейс Camera.AutoFocusCallback.

    class MyAutoFocusCallback implements Camera.AutoFocusCallback{
        @Override
        public void onAutoFocus(boolean paramBoolean, Camera 
                                                 paramCamera){
        if (paramBoolean){
            // если удалось сфокусироваться, делаем снимок
            paramCamera.takePicture(null, null, null, 
                                          new MyPictureCallback());
            } 
        }
    } 
        

    Метод takePicture() имеет четыре параметра:

    Camera.ShutterCallback shutter - вызывается в момент получения изображения с матрицы;
    Camera.PictureCallback raw - программе передаются для обработки raw данные (если поддерживается аппаратно);
    Camera.PictureCallback postview - программе передаются полностью обработанные данные (если поддерживается аппаратно);
    Camera.PictureCallback jpg - программе передается изображение в формате jpg. Здесь может быть организована запись изображения на карту памяти.

    В нашем случае последним параметром является экземпляр класса MyPictureCallback(), который реализует интерфейс Camera.PictureCallback. В этом классе реализован единственный метод (см. Листинг 12.1):

    public void onPictureTaken(byte[] paramArrayOfByte, Camera paramCamera){...}
        

    В листинге 12.1 представлен код активности, реализующей работу с камерой и позволяющей выполнять снимки.

    Чтобы разрешить приложению работать с камерой и сохранять фотографии на карте памяти, в манифест необходимо добавить следующие разрешения:

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

    12.4 Настройка интерфейса и реализация логики активности для воспроизведения аудио и видео

    Настроим интерфейс активности для воспроизведения аудио и видео. Нам понадобится окно предварительного просмотра, добавим в окно активности элемент SurfaceView. Для работы приложения необходимо поле для задания расположения медиа контента для проигрывания, добавим элемент EditText, а также потребуются несколько кнопок, добавим в окно активности следующие кнопки:

    b_Start - для запуска воспроизведения контента с начала, зададим свойству On Click этой кнопки значение onClickStart;
    b_Pause - для приостановки воспроизведения, зададим свойству On Click этой кнопки значение onClick;
    b_Resume - для продолжения воспроизведения с места приостановки, зададим свойству On Click этой кнопки значение onClick;
    b_Stop - для полной остановки воспроизведения, зададим свойству On Click этой кнопки значение onClick.

    Для возможности выбора зацикленного воспроизведения медиа контента добавим в окно активности элемент CheckBox.

    Далее предлагаем настроить интерфейс самостоятельно. В нашем случае активность выглядит так, как показано на рис 12.2.

    (рис 12.2) Интерфейс активности для воспроизведения аудио и видео

    Реализуем логику активности, в данном случае необходимо выполнять следующие действия:

  • при нажатии на кнопку Try to play, запускается воспроизведение с начала контента, расположенного по адресу, указанному в поле ввода, расположенном слева от кнопки;
  • при нажатии на кнопку Pause, воспроизведение контента приостанавливается;
  • при нажатии на кнопку Resume, воспроизведение контента продолжается с места приостановки;
  • при нажатии на кнопку Stop, воспроизведение контента останавливается и может быть возобновлено только с начала, т. е. необходимо снова нажать Try to play.
  • Элемент Loop управляет возможностью повтора воспроизведения.

    Работать будем с файлом MediaActivity.java, описывающим соответствующий класс активности.

    Объявим следующие поля класса активности:

    MediaPlayer mediaPlayer; //управляет воспроизведением
    CheckBox chbLoop;  //управляет режимом повтора
        

    В методе onCreate() активности настроим чек-бокс так, чтобы он включал/выключал режим повтора для плеера.

    chbLoop = (CheckBox) findViewById(R.id.chb_Loop);
    chbLoop.setOnCheckedChangeListener(new 
                              OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView,
                                boolean isChecked) {
            if (mediaPlayer != null)
            mediaPlayer.setLooping(isChecked);
        }
    });
        

    Метод onClickStart() используется для обработки нажатий на кнопку Try to play. В этом метода сначала освобождаем ресурсы текущего проигрывателя, используя вызов метода:

    releaseMP();
        

    Этот метод рекомендуется вызывать по окончанию использования плеера, а также при onPause/onStop, если нет острой необходимости держать объект.

    Реализация метода releaseMP():

    private void releaseMP() {
        if (mediaPlayer != null) {
            try {
                mediaPlayer.release();
                mediaPlayer = null;
            } catch (Exception e) {
              e.printStackTrace();
            }
        }
    }
        

    Продолжим рассмотрение метода onClickStart(). В нем после освобождения ресурсов, занимаемся подготовкой плеера к воспроизведению.

    mediaPlayer = new MediaPlayer();
    mediaPlayer.setDataSource(DATA);
    mediaPlayer.setDisplay(((SurfaceView)
                    findViewById(R.id.surfaceView1)).getHolder());
    mediaPlayer.setOnPreparedListener(this);
    mediaPlayer.prepareAsync();
        

    Создаем новый объект класса MediaPlayer.

    Используем метод setDataSource() для задания источника данных, в качестве параметра передаем строку из поля ввода, сохраненную в переменной DATA.

    Используя метод setDisplay(), задаем экран для воспроизведения, в нашем случае элемент SurfaceView, добавленный в окно активности на этапе формирования интерфейса.

    Метод prepareAsync() выполняет асинхронную подготовку плеера к воспроизведению и когда подготовка будет завершена сообщает об этом слушателю, указанному в методе setPreparedListener(). В нашем случае слушателем является сам класс активности, для этого он объявлен, как класс реализующий интерфейс OnPreparedListener. В случае готовности плеера вызывается метод onPrepared(MediaPlayer mp), объявленный в указанном интерфейсе и реализованный в классе активности, в этом методе выполняется запуск воспроизведения:

    mp.start();
        

    Существует еще метод prepare(), он также выполняет подготовку плеера, но в синхронном режиме. Для прослушивания файлов из интернета, необходимо использовать асинхронный режим, что мы и сделали.

    Последние две строки метода onClickStart():

    mediaPlayer.setLooping(chbLoop.isChecked());
    mediaPlayer.setOnCompletionListener(this);
        

    В первой из них определяется возможность циклического воспроизведения в зависимости от значения чек-бокса.

    Во второй - задается слушатель для получения сообщения о достижении конца воспроизводимого контента. В нашем случае этим слушателем будет сам класс активности для этого он объявлен, как класс реализующий интерфейс OnCompletionListener. Полную версию метода onClickStart() можно найти в листинге 12.2.

    Метод onClick() вызывается при нажатии на любую кнопку: Pause, Resume, Stop. В этом методе выполняется проверка какая кнопка была нажата и после этого выполняются соответствующие действия.

    Если нажата кнопка Pause, выполняется следующая конструкция:

    if (mediaPlayer.isPlaying())   mediaPlayer.pause();
        

    Если нажата кнопка Release, выполняется следующая конструкция:

    if (!mediaPlayer.isPlaying())  mediaPlayer.start();
        

    Если нажата кнопка Stop, выполняется следующая конструкция:

    mediaPlayer.stop();
        

    Имеет смысл обратить внимание на метод активности:

    protected void onDestroy() {
      super.onDestroy();
      releaseMP();
    }
        

    В этом методе обязательно необходимо освободить ресурсы, что и выполняется вызовом метода releaseMP().

    Полный код класса MediaActivity представлен в листинге 12.2.

    12.5 Настройка интерфейса и реализация логики активности для просмотра изображений

    Настроим интерфейс активности для просмотра изображений. Нам потребуется элемент для просмотра изображений, добавим в окно активности элемент ImageView. Добавим информационное поле, т. е. элемент TextView, для отображения информации об общем количестве изображений в папке и номере просматриваемого изображения. Добавим две кнопки, для перемещения от одного изображения к другому вперед и назад.

    Предлагаем настроить интерфейс самостоятельно. В нашем случае активность выглядит так, как показано на рис 12.3. Но это, разумеется, не единственно возможный вариант.

    (рис 12.3) Интерфейс активности для просмотра изображений

    Реализуем логику активности, данная активность ориентирована на просмотр снимков, сделанных в этом же приложении и сохраненных в папке: /sdcard/TrainingMedia/. Кнопка Назад выводит в окно просмотра предыдущий снимок, по отношению к уже отображенному, зададим свойству On Click этой кнопки значение onPrevious. Кнопка Вперед выводит в окно просмотра следующий снимок, по отношению к уже отображенному, зададим свойству On Click этой кнопки значение onNext.

    Работать будем с файлом GalleryActivity.java, описывающим класс соответствующей активности.

    Объявим поля класса активности:

    int currentImage=0; 
    ArrayList<String> images; 
    ImageView imageView;
    TextView nameView;
        

    Настройку основных элементов для вывода изображений на экран выполним в методе onResume() активности, этот метод вызывается каждый раз, перед выводом активности на передний план (см. жизненный цикл активности в лекции 2).

    images=new ArrayList<String>();
        
    imageView=((ImageView)findViewById(R.id.image));
    try{
        File imagesDirectory=new File("/sdcard/TrainingMedia/");
        images=searchImage(imagesDirectory);
        updatePhoto(Uri.parse(images.get(currentImage)));
    }catch(Exception e){
        nameView.setText("Ошибка: Папка '/sdcard/TrainingMedia/' не 
                                                             найдена");
    }
        

    Для получения списка изображений в переменную images используется метод searchImage(), этот метод, используя переданный в него в качестве параметра адрес директории с изображениями, находит файл с расширением .jpg, .png или .jpeg и добавляет его к списку изображений. Метод возвращает список доступных файлов с изображениями. Код метода представлен в листинге 12.3.

    Метод updatePhoto() выполняет обновление счетчика фотографий в информационном поле и выводит на экран изображение, соответствующее переданному в метод URI. Код метода представлен в листинге 12.3.

    В методе onPause() активности выполняется очистка списка изображений и освобождение памяти. Этот метод вызывается каждый раз, как активность теряет фокус ввода. Код метода представлен в листинге 12.3.

    Осталось рассмотреть еще два метода: onPrevious() и onNext().

    Первый метод вызывается, когда нажата кнопка Назад. В нем уменьшается номер текущего изображения и вызывается метод updatePhoto().

    Второй метод вызывается, когда нажата кнопка Вперед. В нем увеличивается номер, текущего изображения и вызывается метод updatePhoto().

    Полный код класса GalleryActivity представлен в листинге 12.3.

    12.6 Настройка интерфейса и реализация логики главной активности приложения

    Настроим интерфейс главной активности приложения. Эта активность содержит три кнопки (Image Button):

    bCamera - для вызова активности, предоставляющей возможности работы с камерой;
    bGallery - для вызова активности, предоставляющей возможности просмотра изображений;
    bMusic - для вызова активности, предоставляющей возможности воспроизведения аудио и видео.

    Предлагаем настроить интерфейс самостоятельно. В нашем случае активность выглядит так, как показано на рис 12.4. Но это, разумеется, не единственно возможный вариант.

    (рис 12.4) Интерфейс главной активности приложения

    Реализуем логику главной активности. В данном случае достаточно настроить обработку событий нажатия на кнопки, таким образом, чтобы при нажатии на кнопку запускалась соответствующая активность. Работаем с java файлом, описывающим класс активности.

  • Создадим переменную btnClick, как реализацию интерфейса-слушателя OnClickListener:
    OnClickListener btnClick=new OnClickListener() {
        @Override
        public void onClick(View v) {
            Click(v.getId());
        }
    };
          
  • Добавим слушателя события нажатия к каждой кнопке активности:
    ((ImageButton)findViewById(R.id.bMusic)).setOnClickListener(btnClick);
    ((ImageButton)findViewById(R.id.bCamera)).setOnClickListener(btnClick);
    ((ImageButton)findViewById(R.id.bGallery)).setOnClickListener(btnClick);
          
  • В методе onClick() слушателя вызываем метод Click(), в котором выполняется запуск соответствующей активности:
    protected void Click(int view){
        Intent intent=null;
        switch (view){
            case R.id.bMusic: intent=new Intent(this,mediaPlayer.class); break;
            case R.id.bGallery: intent=new Intent(this,GalleryActivity.class); break;
            case R.id.bCamera: intent=new Intent(this,CameraActivity.class); break;
            default: break;
        }
        if(intent!=null){
            startActivity(intent);
        }
    }
          
  • В листинге 12.4 представлен код главной активности.

    package com.example.lab5_4_media;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import android.hardware.Camera;
    import android.hardware.Camera.Size;
    import android.os.Bundle;
    import android.app.Activity;
    import android.content.pm.ActivityInfo;
    import android.content.res.Configuration;
    import android.util.Log;
    import android.view.SurfaceHolder;
    import android.view.SurfaceView;
    import android.view.View;
    import android.view.Window;
    import android.view.WindowManager;
    import android.view.ViewGroup.LayoutParams;
    
    public class CameraActivity extends Activity {
      
      private Camera camera;
      private SurfaceHolder surfaceHolder;
      private SurfaceView preview;
      private View shotBtn;
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        // если хотим, чтобы приложение постоянно имело портретную ориентацию
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    
        // если хотим, чтобы приложение было полноэкранным
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    
        // и без заголовка
        requestWindowFeature(Window.FEATURE_NO_TITLE);
    
        setContentView(R.layout.activity_camera);
            
        preview = (SurfaceView) findViewById(R.id.surfaceCamera);
        surfaceHolder = preview.getHolder();
        surfaceHolder.addCallback(new MyCallback(this));
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
            
        shotBtn = findViewById(R.id.bCameraShot);
        shotBtn.setOnClickListener(new MyViewListener());
      }
        
      @Override
      protected void onResume() {
        super.onResume();
        camera = Camera.open();
      }
        
      @Override
      protected void onPause(){
        super.onPause();
        if (camera != null){
          camera.setPreviewCallback(null);
          camera.stopPreview();
          camera.release();
          camera = null;
        }
      }
        
      class MyCallback implements SurfaceHolder.Callback{
          
        Activity host;
          
        MyCallback(Activity act){
          host=act;
        }
          
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, 
        int height){}
        @Override
        public void surfaceCreated(SurfaceHolder holder){
          try {
            camera.setPreviewDisplay(holder);
            camera.setPreviewCallback(new MyPreviewCallback());
          }
          catch (IOException e){
            Log.d("myLogs","Ошибка камеры");
            e.printStackTrace();
          }
          Size previewSize = camera.getParameters().getPreviewSize();
          float aspect = (float) previewSize.width / previewSize.height;
    
          int previewSurfaceWidth = preview.getWidth();
          int previewSurfaceHeight = preview.getHeight();
    
          LayoutParams lp = preview.getLayoutParams();
    
          // здесь корректируем размер отображаемого preview, чтобы не было искажений
    
          if (host.getResources().getConfiguration().orientation != 
                  Configuration.ORIENTATION_LANDSCAPE){
            // портретный вид
            camera.setDisplayOrientation(90);
            lp.height = previewSurfaceHeight;
            lp.width = (int) (previewSurfaceHeight / aspect);
          }
          else {
            // ландшафтный
            camera.setDisplayOrientation(0);
            lp.width = previewSurfaceWidth;
            lp.height = (int) (previewSurfaceWidth / aspect);
          }
          preview.setLayoutParams(lp);
          camera.startPreview();
        }
          
        @Override
        public void surfaceDestroyed(SurfaceHolder holder){}     
      }
        
      class MyViewListener implements View.OnClickListener{
        
        @Override
        public void onClick(View v) {
          if (v == shotBtn) {        
            // либо делаем снимок непосредственно здесь
            // либо включаем обработчик автофокуса
    
            //camera.takePicture(null, null, null, this);
            camera.autoFocus(new MyAutoFocusCallback());
          }
        }
      }
        
      class MyAutoFocusCallback implements Camera.AutoFocusCallback{
    
        @Override
        public void onAutoFocus(boolean paramBoolean, Camera paramCamera){
          if (paramBoolean){
            // если удалось сфокусироваться, делаем снимок
            paramCamera.takePicture(null, null, null, new MyPictureCallback());
          }
        }
      }
        
      class MyPictureCallback implements Camera.PictureCallback{
    
        @Override
        public void onPictureTaken(byte[] paramArrayOfByte, Camera paramCamera){
          // сохраняем полученные jpg в папке /sdcard/CameraExample/
          // имя файла - System.currentTimeMillis()
          try {
            File saveDir = new File("/sdcard/CameraExample/");
            if (!saveDir.exists()) {
              saveDir.mkdirs();
            }
    
            FileOutputStream os = new FileOutputStream(String.format("/sdcard/CameraExample/%d.jpg", System.currentTimeMillis()));
            os.write(paramArrayOfByte);
            os.close();
          }
          catch (Exception e) {}
    
          // после того, как снимок сделан, показ превью отключается. 
          // Необходимо включить его
          paramCamera.startPreview();
        }
      }
        
      class MyPreviewCallback implements Camera.PreviewCallback{
          
        @Override
        public void onPreviewFrame(byte[] paramArrayOfByte, Camera paramCamera) {
          // здесь можно обрабатывать изображение, показываемое в preview
        }
      }
    }
        
    package com.example.lab5_4_media;
    
    import android.media.MediaPlayer;
    import android.media.MediaPlayer.OnCompletionListener;
    import android.media.MediaPlayer.OnPreparedListener;
    import android.os.Bundle;
    import android.view.Gravity;
    import android.view.SurfaceView;
    import android.view.View;
    import android.widget.CheckBox;
    import android.widget.CompoundButton;
    import android.widget.EditText;
    import android.widget.Toast;
    import android.widget.CompoundButton.OnCheckedChangeListener;
    import android.app.Activity;
    
    public class MediaActivity extends Activity implements OnPreparedListener, OnCompletionListener{
      
      MediaPlayer mediaPlayer;
      CheckBox chbLoop;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_media);
                
          chbLoop = (CheckBox) findViewById(R.id.chb_Loop);
        chbLoop.setOnCheckedChangeListener(new OnCheckedChangeListener() {
          @Override
          public void onCheckedChanged(CompoundButton buttonView,
              boolean isChecked) {
            if (mediaPlayer != null)
              mediaPlayer.setLooping(isChecked);
          }
        });
        }
        
        public void onClickStart(View view) {
        releaseMP();
        
        String DATA=((EditText)findViewById(R.id.et_MediaPath)).getText().toString();
        try {
          mediaPlayer = new MediaPlayer();
          mediaPlayer.setDataSource(DATA);
          mediaPlayer.setDisplay(((SurfaceView)
              findViewById(R.id.surfaceView1)).getHolder());
          //mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
          mediaPlayer.setOnPreparedListener(this);
          mediaPlayer.prepareAsync();
        } catch (Exception e) {
          showMessage("Ошибка воспроизведения");
        }
    
        if (mediaPlayer == null)
          return;
        
        mediaPlayer.setLooping(chbLoop.isChecked());
        mediaPlayer.setOnCompletionListener(this);
      }
        
        private void showMessage(String text){
        Toast toast = Toast.makeText(getApplicationContext(), text,
            Toast.LENGTH_SHORT);
        toast.setGravity(Gravity.CENTER, 0, 0);
        toast.show();
      }
        
        private void releaseMP() {
        if (mediaPlayer != null) {
          try {
            mediaPlayer.release();
            mediaPlayer = null;
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
      }
        
        public void onClick(View view) {
        if (mediaPlayer == null)
          return;
        switch (view.getId()) {
        case R.id.b_Pause:
          if (mediaPlayer.isPlaying())
            mediaPlayer.pause();
          break;
        case R.id.b_Resume:
          if (!mediaPlayer.isPlaying())
            mediaPlayer.start();
          break;
        case R.id.b_Stop:
          mediaPlayer.stop();
          break;
        }
      }
        
          @Override
      public void onPrepared(MediaPlayer mp) {
        mp.start();
      }
    
      @Override
      public void onCompletion(MediaPlayer mp) {}
    
      @Override
      protected void onDestroy() {
        super.onDestroy();
        releaseMP();
      }
        
    }
        

    package com.example.lab5_4_media;
    
    import java.io.File;
    import java.util.ArrayList;
    import android.net.Uri;
    import android.os.Bundle;
    import android.app.Activity;
    import android.util.Log;
    import android.view.View;
    import android.widget.ImageView;
    import android.widget.TextView;
    
    public class GalleryActivity extends Activity {
      
      int currentImage=0;
      ArrayList<String> images;
      ImageView imageView;
      TextView nameView;
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_gallery);
      }
      
      @Override
      public void onResume(){
        super.onResume();
        
        currentImage=0;
        
        Log.d("myLogs","onResume cI="+currentImage);
        
        nameView=((TextView)findViewById(R.id.imageName));
        images=new ArrayList<String>();
        
        imageView=((ImageView)findViewById(R.id.image));
        try{
          File imagesDirectory=new File("/sdcard/TrainingMedia/");
          images=searchImage(imagesDirectory);
          updatePhoto(Uri.parse(images.get(currentImage)));
        }catch(Exception e){
          nameView.setText("Ошибка: Папка '/sdcard/TrainingMedia/' не найдена");
          Log.d("myLogs","Ошибка");
        }
      }
      
      @Override
        protected void onPause()
        {
            super.onPause();
            images.clear();
            Log.d("myLogs","onPause cI="+currentImage);
        }
      
      private ArrayList<String> searchImage(File dir){
        ArrayList<String> imagesFinded=new ArrayList<String>();
        for(File f:dir.listFiles()){
          if(!f.isDirectory()){
            String fileExt=getFileExt(f.getAbsolutePath());
            if(fileExt.equals("png") || fileExt.equals("jpg") || fileExt.equals("jpeg")){
              Log.d("myLogs","Файл найден "+f.getAbsolutePath());
              imagesFinded.add(f.getAbsolutePath());
            }
          }
        }
        return imagesFinded;
      }
      
      public static String getFileExt(String filename){
        return filename.substring(filename.lastIndexOf(".") + 1);
      }
      
      public void updatePhoto(Uri uri){
        try{
          nameView.setText((currentImage+1)+"/"+images.size());
          imageView.setImageURI(uri);
        }catch(Exception e){
          nameView.setText("Ошибка загрузки файла");
        }
      }
      
      public void onNext(View v){
        if(currentImage+1<images.size()  images.size()>0){
          currentImage++;
          updatePhoto(Uri.parse(images.get(currentImage)));
        }
      }
      
      public void onPrevious(View v){
        if(currentImage>0  images.size()>0){
          currentImage--;
          updatePhoto(Uri.parse(images.get(currentImage)));
        }
      }
      
    
      
    }
        

    package com.example.lab5_4_media;
    
    import android.os.Bundle;
    import android.app.Activity;
    import android.content.Intent;
    import android.util.Log;
    import android.view.Menu;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.ImageButton;
    
    public class MainActivity extends Activity {
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
          
        OnClickListener btnClick=new OnClickListener() {
          @Override
          public void onClick(View v) {
            Log.d("myLogs",v.getId()+"");
            Click(v.getId());
          }
        };
        ((ImageButton)findViewById(R.id.bMusic)).setOnClickListener(btnClick);
        ((ImageButton)findViewById(R.id.bCamera)).setOnClickListener(btnClick);
        ((ImageButton)findViewById(R.id.bGallery)).setOnClickListener(btnClick);
      }
      protected void Click(int view){
        Intent intent=null;
        Log.d("myLogs",view+"");
        switch (view){
          case R.id.bMusic: intent=new Intent(this,MediaActivity.class);   break;
          case R.id.bGallery: intent=new Intent(this,GalleryActivity.class); break;
          case R.id.bCamera: intent=new Intent(this,CameraActivity.class); break;
          default: break;
        }
        if(intent!=null){
          Log.d("myLogs","Интент = "+intent.toString());
          startActivity(intent);
        }
      }
    }
        
    Страницы:

    Цель лабораторной работы:

    Разработка многооконного приложения, предоставляющего возможности: воспроизведения аудио и видео файлов, создания и отображения фотоснимков.

    Задачи лабораторной работы:

  • настроить интерфейс и реализовать логику активности для работы с камерой;
  • настроить интерфейс и реализовать логику активности для воспроизведения аудио и видео;
  • настроить интерфейс и реализовать логику активности для просмотра изображений;
  • настроить интерфейс и реализовать логику главной активности приложения.
  • 12.1 Введение

    Для достижения цели, поставленной в лабораторной работе, сформулируем требования к разрабатываемому приложению, назовем его "Media".

    Приложение предоставляет пользователю возможность выбора рода деятельности:

  • работа с камерой для создания снимков;
  • воспроизведение аудио и видео;
  • просмотр изображений.
  • В приложении предполагается реализовать четыре активности:

  • главная активность, предназначена для выбора рода деятельности, содержит три кнопки, нажатие на каждую кнопку вызывает к жизни соответствующую активность;
  • активность для работы с камерой и создания снимков;
  • активность для воспроизведения аудио и видео;
  • активность для просмотра изображений.
  • 12.2 Создание приложения

    Создадим новое приложение, активность, полученную при создании проекта, оставим с именем MainActivity, она будет главной активностью приложения. Добавим в приложение еще три активности: New->Other...->Android Activity.

    В нашем приложении созданные активности имеют следующие имена:

    CameraActivity для работы с камерой и создания снимков;

    MediaActivity для воспроизведения видео и аудио;

    GalleryActivity для просмотра изображений.

    Далее продолжим работу с этими активностями, настроим интерфейс и реализуем логику для каждой из них. Начнем с активностей, отвечающих за тот или иной вид деятельности, главную активность приведем в порядок в самом конце работы.

    12.3 Настройка интерфейса и реализация логики активности для работы с камерой

    Настроим интерфейс активности для работы с камерой. Нам понадобится окно предварительного просмотра, добавим в окно активности элемент .

    (рис 12.1) Интерфейс активности для работы с камерой

    Реализуем логику активности, в данном случае необходимо при нажатии на кнопку выполнить снимок. Работаем с java файлом, описывающим соответствующий класс активности, CameraActivity.java.

    Объявим следующие поля класса активности:

    private Camera camera; //для проведения всех операций с камерой
    private SurfaceHolder surfaceHolder; //для задания preview
    private SurfaceView preview; //для отображения окна предпросмотра
    private View shotBtn;  //для выполнения снимка (кнопка)
        

    Для начала работы с камерой необходимо ее инициализировать, сделать это лучше в методе onResume() класса активности, для инициализации используется следующая конструкция:

    camera = Camera.open();
        

    После завершения работы с камерой, необходимо ее освободить для других приложений, сделать это лучше в методе onPause() класса активности, для освобождения камеры используется следующая конструкция:

    Camera.release();
        

    Для активности, работающей с камерой, имеет смысл сразу задать расположение экрана следующим образом

    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        

    в противном случае, приходится, например, в методе surfaceCreated() проверять расположение экрана и поворачивать окно предварительного просмотра, что не очень удобно, так как поворот экрана занимает некоторое время.

    Выполним настройку окна предварительного просмотра:

    preview = (SurfaceView) findViewById(R.id.surfaceCamera);
    surfaceHolder = preview.getHolder();
    surfaceHolder.addCallback(new MyCallback());
        

    метод addCallback() используется для отслеживания изменений окна предпросмотра. Параметром этого метода служит экземпляр класса реализующего интерфейс SurfaceHolder.Callback, в нашем случае для реализации этого интерфейса создан внутренний класс активности MyCallback.

    В этом классе необходимо реализовать методы:

    public void surfaceCreated(SurfaceHolder holder);
    public void surfaceChanged(SurfaceHolder holder, int format, 
    int width, int height);
    public void surfaceDestroyed(SurfaceHolder holder);
        

    Мы реализуем метод surfaceCreated(), оставшиеся два метода запишем с пустой реализацией.

    В методе surfaceCreated() зададим окно предварительного просмотра:

    camera.setPreviewDisplay(holder);
        

    Выполним необходимые настройки окна предпросмотра (см. Листинг 12.1).

    Запустим отображение окна предварительного просмотра:

    camera.startPreview();
        

    Пришло время обсудить и реализовать выполнение фотосъемки. Для того, чтобы сделать снимок необходимо вызвать метод takePicture(). Вызов этого метода можно выполнить из метода-обработчика события нажатия кнопки, тогда при нажатии на кнопку сразу будет получена фотография, а можно "растянуть удовольствие" и воспользоваться предварительной автофокусировкой.

    Сначала инициализируем кнопку и добавим слушателя события нажатия в методе onCreate() активности:

    shotBtn = findViewById(R.id.bCameraShot);
    shotBtn.setOnClickListener(new MyViewListener());
        

    В данном случае слушателем является экземпляр внутреннего класса MyViewListener, который реализует интерфейс View.OnClickListener.

    class MyViewListener implements View.OnClickListener{
        @Override
        public void onClick(View v){
            if (v == shotBtn){
                camera.autoFocus(new MyAutoFocusCallback());
            }
        }
    }
        

    В методе onClick() вызываем обработчик автофокуса, слушателем события автофокусировки в данном случае является экземпляр внутреннего класса MyAutoFocusCallback, который реализует интерфейс Camera.AutoFocusCallback.

    class MyAutoFocusCallback implements Camera.AutoFocusCallback{
        @Override
        public void onAutoFocus(boolean paramBoolean, Camera 
                                                 paramCamera){
        if (paramBoolean){
            // если удалось сфокусироваться, делаем снимок
            paramCamera.takePicture(null, null, null, 
                                          new MyPictureCallback());
            } 
        }
    } 
        

    Метод takePicture() имеет четыре параметра:

    Camera.ShutterCallback shutter - вызывается в момент получения изображения с матрицы;
    Camera.PictureCallback raw - программе передаются для обработки raw данные (если поддерживается аппаратно);
    Camera.PictureCallback postview - программе передаются полностью обработанные данные (если поддерживается аппаратно);
    Camera.PictureCallback jpg - программе передается изображение в формате jpg. Здесь может быть организована запись изображения на карту памяти.

    В нашем случае последним параметром является экземпляр класса MyPictureCallback(), который реализует интерфейс Camera.PictureCallback. В этом классе реализован единственный метод (см. Листинг 12.1):

    public void onPictureTaken(byte[] paramArrayOfByte, Camera paramCamera){...}
        

    В листинге 12.1 представлен код активности, реализующей работу с камерой и позволяющей выполнять снимки.

    Чтобы разрешить приложению работать с камерой и сохранять фотографии на карте памяти, в манифест необходимо добавить следующие разрешения:

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

    12.4 Настройка интерфейса и реализация логики активности для воспроизведения аудио и видео

    Настроим интерфейс активности для воспроизведения аудио и видео. Нам понадобится окно предварительного просмотра, добавим в окно активности элемент SurfaceView. Для работы приложения необходимо поле для задания расположения медиа контента для проигрывания, добавим элемент EditText, а также потребуются несколько кнопок, добавим в окно активности следующие кнопки:

    b_Start - для запуска воспроизведения контента с начала, зададим свойству On Click этой кнопки значение onClickStart;
    b_Pause - для приостановки воспроизведения, зададим свойству On Click этой кнопки значение onClick;
    b_Resume - для продолжения воспроизведения с места приостановки, зададим свойству On Click этой кнопки значение onClick;
    b_Stop - для полной остановки воспроизведения, зададим свойству On Click этой кнопки значение onClick.

    Для возможности выбора зацикленного воспроизведения медиа контента добавим в окно активности элемент CheckBox.

    Далее предлагаем настроить интерфейс самостоятельно. В нашем случае активность выглядит так, как показано на рис 12.2.

    (рис 12.2) Интерфейс активности для воспроизведения аудио и видео

    Реализуем логику активности, в данном случае необходимо выполнять следующие действия:

  • при нажатии на кнопку Try to play, запускается воспроизведение с начала контента, расположенного по адресу, указанному в поле ввода, расположенном слева от кнопки;
  • при нажатии на кнопку Pause, воспроизведение контента приостанавливается;
  • при нажатии на кнопку Resume, воспроизведение контента продолжается с места приостановки;
  • при нажатии на кнопку Stop, воспроизведение контента останавливается и может быть возобновлено только с начала, т. е. необходимо снова нажать Try to play.
  • Элемент Loop управляет возможностью повтора воспроизведения.

    Работать будем с файлом MediaActivity.java, описывающим соответствующий класс активности.

    Объявим следующие поля класса активности:

    MediaPlayer mediaPlayer; //управляет воспроизведением
    CheckBox chbLoop;  //управляет режимом повтора
        

    В методе onCreate() активности настроим чек-бокс так, чтобы он включал/выключал режим повтора для плеера.

    chbLoop = (CheckBox) findViewById(R.id.chb_Loop);
    chbLoop.setOnCheckedChangeListener(new 
                              OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(CompoundButton buttonView,
                                boolean isChecked) {
            if (mediaPlayer != null)
            mediaPlayer.setLooping(isChecked);
        }
    });
        

    Метод onClickStart() используется для обработки нажатий на кнопку Try to play. В этом метода сначала освобождаем ресурсы текущего проигрывателя, используя вызов метода:

    releaseMP();
        

    Этот метод рекомендуется вызывать по окончанию использования плеера, а также при onPause/onStop, если нет острой необходимости держать объект.

    Реализация метода releaseMP():

    private void releaseMP() {
        if (mediaPlayer != null) {
            try {
                mediaPlayer.release();
                mediaPlayer = null;
            } catch (Exception e) {
              e.printStackTrace();
            }
        }
    }
        

    Продолжим рассмотрение метода onClickStart(). В нем после освобождения ресурсов, занимаемся подготовкой плеера к воспроизведению.

    mediaPlayer = new MediaPlayer();
    mediaPlayer.setDataSource(DATA);
    mediaPlayer.setDisplay(((SurfaceView)
                    findViewById(R.id.surfaceView1)).getHolder());
    mediaPlayer.setOnPreparedListener(this);
    mediaPlayer.prepareAsync();
        

    Создаем новый объект класса MediaPlayer.

    Используем метод setDataSource() для задания источника данных, в качестве параметра передаем строку из поля ввода, сохраненную в переменной DATA.

    Используя метод setDisplay(), задаем экран для воспроизведения, в нашем случае элемент SurfaceView, добавленный в окно активности на этапе формирования интерфейса.

    Метод prepareAsync() выполняет асинхронную подготовку плеера к воспроизведению и когда подготовка будет завершена сообщает об этом слушателю, указанному в методе setPreparedListener(). В нашем случае слушателем является сам класс активности, для этого он объявлен, как класс реализующий интерфейс OnPreparedListener. В случае готовности плеера вызывается метод onPrepared(MediaPlayer mp), объявленный в указанном интерфейсе и реализованный в классе активности, в этом методе выполняется запуск воспроизведения:

    mp.start();
        

    Существует еще метод prepare(), он также выполняет подготовку плеера, но в синхронном режиме. Для прослушивания файлов из интернета, необходимо использовать асинхронный режим, что мы и сделали.

    Последние две строки метода onClickStart():

    mediaPlayer.setLooping(chbLoop.isChecked());
    mediaPlayer.setOnCompletionListener(this);
        

    В первой из них определяется возможность циклического воспроизведения в зависимости от значения чек-бокса.

    Во второй - задается слушатель для получения сообщения о достижении конца воспроизводимого контента. В нашем случае этим слушателем будет сам класс активности для этого он объявлен, как класс реализующий интерфейс OnCompletionListener. Полную версию метода onClickStart() можно найти в листинге 12.2.

    Метод onClick() вызывается при нажатии на любую кнопку: Pause, Resume, Stop. В этом методе выполняется проверка какая кнопка была нажата и после этого выполняются соответствующие действия.

    Если нажата кнопка Pause, выполняется следующая конструкция:

    if (mediaPlayer.isPlaying())   mediaPlayer.pause();
        

    Если нажата кнопка Release, выполняется следующая конструкция:

    if (!mediaPlayer.isPlaying())  mediaPlayer.start();
        

    Если нажата кнопка Stop, выполняется следующая конструкция:

    mediaPlayer.stop();
        

    Имеет смысл обратить внимание на метод активности:

    protected void onDestroy() {
      super.onDestroy();
      releaseMP();
    }
        

    В этом методе обязательно необходимо освободить ресурсы, что и выполняется вызовом метода releaseMP().

    Полный код класса MediaActivity представлен в листинге 12.2.

    12.5 Настройка интерфейса и реализация логики активности для просмотра изображений

    Настроим интерфейс активности для просмотра изображений. Нам потребуется элемент для просмотра изображений, добавим в окно активности элемент ImageView. Добавим информационное поле, т. е. элемент TextView, для отображения информации об общем количестве изображений в папке и номере просматриваемого изображения. Добавим две кнопки, для перемещения от одного изображения к другому вперед и назад.

    Предлагаем настроить интерфейс самостоятельно. В нашем случае активность выглядит так, как показано на рис 12.3. Но это, разумеется, не единственно возможный вариант.

    (рис 12.3) Интерфейс активности для просмотра изображений

    Реализуем логику активности, данная активность ориентирована на просмотр снимков, сделанных в этом же приложении и сохраненных в папке: /sdcard/TrainingMedia/. Кнопка Назад выводит в окно просмотра предыдущий снимок, по отношению к уже отображенному, зададим свойству On Click этой кнопки значение onPrevious. Кнопка Вперед выводит в окно просмотра следующий снимок, по отношению к уже отображенному, зададим свойству On Click этой кнопки значение onNext.

    Работать будем с файлом GalleryActivity.java, описывающим класс соответствующей активности.

    Объявим поля класса активности:

    int currentImage=0; 
    ArrayList<String> images; 
    ImageView imageView;
    TextView nameView;
        

    Настройку основных элементов для вывода изображений на экран выполним в методе onResume() активности, этот метод вызывается каждый раз, перед выводом активности на передний план (см. жизненный цикл активности в лекции 2).

    images=new ArrayList<String>();
        
    imageView=((ImageView)findViewById(R.id.image));
    try{
        File imagesDirectory=new File("/sdcard/TrainingMedia/");
        images=searchImage(imagesDirectory);
        updatePhoto(Uri.parse(images.get(currentImage)));
    }catch(Exception e){
        nameView.setText("Ошибка: Папка '/sdcard/TrainingMedia/' не 
                                                             найдена");
    }
        

    Для получения списка изображений в переменную images используется метод searchImage(), этот метод, используя переданный в него в качестве параметра адрес директории с изображениями, находит файл с расширением .jpg, .png или .jpeg и добавляет его к списку изображений. Метод возвращает список доступных файлов с изображениями. Код метода представлен в листинге 12.3.

    Метод updatePhoto() выполняет обновление счетчика фотографий в информационном поле и выводит на экран изображение, соответствующее переданному в метод URI. Код метода представлен в листинге 12.3.

    В методе onPause() активности выполняется очистка списка изображений и освобождение памяти. Этот метод вызывается каждый раз, как активность теряет фокус ввода. Код метода представлен в листинге 12.3.

    Осталось рассмотреть еще два метода: onPrevious() и onNext().

    Первый метод вызывается, когда нажата кнопка Назад. В нем уменьшается номер текущего изображения и вызывается метод updatePhoto().

    Второй метод вызывается, когда нажата кнопка Вперед. В нем увеличивается номер, текущего изображения и вызывается метод updatePhoto().

    Полный код класса GalleryActivity представлен в листинге 12.3.

    12.6 Настройка интерфейса и реализация логики главной активности приложения

    Настроим интерфейс главной активности приложения. Эта активность содержит три кнопки (Image Button):

    bCamera - для вызова активности, предоставляющей возможности работы с камерой;
    bGallery - для вызова активности, предоставляющей возможности просмотра изображений;
    bMusic - для вызова активности, предоставляющей возможности воспроизведения аудио и видео.

    Предлагаем настроить интерфейс самостоятельно. В нашем случае активность выглядит так, как показано на рис 12.4. Но это, разумеется, не единственно возможный вариант.

    (рис 12.4) Интерфейс главной активности приложения

    Реализуем логику главной активности. В данном случае достаточно настроить обработку событий нажатия на кнопки, таким образом, чтобы при нажатии на кнопку запускалась соответствующая активность. Работаем с java файлом, описывающим класс активности.

  • Создадим переменную btnClick, как реализацию интерфейса-слушателя OnClickListener:
    OnClickListener btnClick=new OnClickListener() {
        @Override
        public void onClick(View v) {
            Click(v.getId());
        }
    };
          
  • Добавим слушателя события нажатия к каждой кнопке активности:
    ((ImageButton)findViewById(R.id.bMusic)).setOnClickListener(btnClick);
    ((ImageButton)findViewById(R.id.bCamera)).setOnClickListener(btnClick);
    ((ImageButton)findViewById(R.id.bGallery)).setOnClickListener(btnClick);
          
  • В методе onClick() слушателя вызываем метод Click(), в котором выполняется запуск соответствующей активности:
    protected void Click(int view){
        Intent intent=null;
        switch (view){
            case R.id.bMusic: intent=new Intent(this,mediaPlayer.class); break;
            case R.id.bGallery: intent=new Intent(this,GalleryActivity.class); break;
            case R.id.bCamera: intent=new Intent(this,CameraActivity.class); break;
            default: break;
        }
        if(intent!=null){
            startActivity(intent);
        }
    }
          
  • В листинге 12.4 представлен код главной активности.

    package com.example.lab5_4_media;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import android.hardware.Camera;
    import android.hardware.Camera.Size;
    import android.os.Bundle;
    import android.app.Activity;
    import android.content.pm.ActivityInfo;
    import android.content.res.Configuration;
    import android.util.Log;
    import android.view.SurfaceHolder;
    import android.view.SurfaceView;
    import android.view.View;
    import android.view.Window;
    import android.view.WindowManager;
    import android.view.ViewGroup.LayoutParams;
    
    public class CameraActivity extends Activity {
      
      private Camera camera;
      private SurfaceHolder surfaceHolder;
      private SurfaceView preview;
      private View shotBtn;
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        // если хотим, чтобы приложение постоянно имело портретную ориентацию
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    
        // если хотим, чтобы приложение было полноэкранным
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    
        // и без заголовка
        requestWindowFeature(Window.FEATURE_NO_TITLE);
    
        setContentView(R.layout.activity_camera);
            
        preview = (SurfaceView) findViewById(R.id.surfaceCamera);
        surfaceHolder = preview.getHolder();
        surfaceHolder.addCallback(new MyCallback(this));
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
            
        shotBtn = findViewById(R.id.bCameraShot);
        shotBtn.setOnClickListener(new MyViewListener());
      }
        
      @Override
      protected void onResume() {
        super.onResume();
        camera = Camera.open();
      }
        
      @Override
      protected void onPause(){
        super.onPause();
        if (camera != null){
          camera.setPreviewCallback(null);
          camera.stopPreview();
          camera.release();
          camera = null;
        }
      }
        
      class MyCallback implements SurfaceHolder.Callback{
          
        Activity host;
          
        MyCallback(Activity act){
          host=act;
        }
          
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, 
        int height){}
        @Override
        public void surfaceCreated(SurfaceHolder holder){
          try {
            camera.setPreviewDisplay(holder);
            camera.setPreviewCallback(new MyPreviewCallback());
          }
          catch (IOException e){
            Log.d("myLogs","Ошибка камеры");
            e.printStackTrace();
          }
          Size previewSize = camera.getParameters().getPreviewSize();
          float aspect = (float) previewSize.width / previewSize.height;
    
          int previewSurfaceWidth = preview.getWidth();
          int previewSurfaceHeight = preview.getHeight();
    
          LayoutParams lp = preview.getLayoutParams();
    
          // здесь корректируем размер отображаемого preview, чтобы не было искажений
    
          if (host.getResources().getConfiguration().orientation != 
                  Configuration.ORIENTATION_LANDSCAPE){
            // портретный вид
            camera.setDisplayOrientation(90);
            lp.height = previewSurfaceHeight;
            lp.width = (int) (previewSurfaceHeight / aspect);
          }
          else {
            // ландшафтный
            camera.setDisplayOrientation(0);
            lp.width = previewSurfaceWidth;
            lp.height = (int) (previewSurfaceWidth / aspect);
          }
          preview.setLayoutParams(lp);
          camera.startPreview();
        }
          
        @Override
        public void surfaceDestroyed(SurfaceHolder holder){}     
      }
        
      class MyViewListener implements View.OnClickListener{
        
        @Override
        public void onClick(View v) {
          if (v == shotBtn) {        
            // либо делаем снимок непосредственно здесь
            // либо включаем обработчик автофокуса
    
            //camera.takePicture(null, null, null, this);
            camera.autoFocus(new MyAutoFocusCallback());
          }
        }
      }
        
      class MyAutoFocusCallback implements Camera.AutoFocusCallback{
    
        @Override
        public void onAutoFocus(boolean paramBoolean, Camera paramCamera){
          if (paramBoolean){
            // если удалось сфокусироваться, делаем снимок
            paramCamera.takePicture(null, null, null, new MyPictureCallback());
          }
        }
      }
        
      class MyPictureCallback implements Camera.PictureCallback{
    
        @Override
        public void onPictureTaken(byte[] paramArrayOfByte, Camera paramCamera){
          // сохраняем полученные jpg в папке /sdcard/CameraExample/
          // имя файла - System.currentTimeMillis()
          try {
            File saveDir = new File("/sdcard/CameraExample/");
            if (!saveDir.exists()) {
              saveDir.mkdirs();
            }
    
            FileOutputStream os = new FileOutputStream(String.format("/sdcard/CameraExample/%d.jpg", System.currentTimeMillis()));
            os.write(paramArrayOfByte);
            os.close();
          }
          catch (Exception e) {}
    
          // после того, как снимок сделан, показ превью отключается. 
          // Необходимо включить его
          paramCamera.startPreview();
        }
      }
        
      class MyPreviewCallback implements Camera.PreviewCallback{
          
        @Override
        public void onPreviewFrame(byte[] paramArrayOfByte, Camera paramCamera) {
          // здесь можно обрабатывать изображение, показываемое в preview
        }
      }
    }
        
    package com.example.lab5_4_media;
    
    import android.media.MediaPlayer;
    import android.media.MediaPlayer.OnCompletionListener;
    import android.media.MediaPlayer.OnPreparedListener;
    import android.os.Bundle;
    import android.view.Gravity;
    import android.view.SurfaceView;
    import android.view.View;
    import android.widget.CheckBox;
    import android.widget.CompoundButton;
    import android.widget.EditText;
    import android.widget.Toast;
    import android.widget.CompoundButton.OnCheckedChangeListener;
    import android.app.Activity;
    
    public class MediaActivity extends Activity implements OnPreparedListener, OnCompletionListener{
      
      MediaPlayer mediaPlayer;
      CheckBox chbLoop;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_media);
                
          chbLoop = (CheckBox) findViewById(R.id.chb_Loop);
        chbLoop.setOnCheckedChangeListener(new OnCheckedChangeListener() {
          @Override
          public void onCheckedChanged(CompoundButton buttonView,
              boolean isChecked) {
            if (mediaPlayer != null)
              mediaPlayer.setLooping(isChecked);
          }
        });
        }
        
        public void onClickStart(View view) {
        releaseMP();
        
        String DATA=((EditText)findViewById(R.id.et_MediaPath)).getText().toString();
        try {
          mediaPlayer = new MediaPlayer();
          mediaPlayer.setDataSource(DATA);
          mediaPlayer.setDisplay(((SurfaceView)
              findViewById(R.id.surfaceView1)).getHolder());
          //mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
          mediaPlayer.setOnPreparedListener(this);
          mediaPlayer.prepareAsync();
        } catch (Exception e) {
          showMessage("Ошибка воспроизведения");
        }
    
        if (mediaPlayer == null)
          return;
        
        mediaPlayer.setLooping(chbLoop.isChecked());
        mediaPlayer.setOnCompletionListener(this);
      }
        
        private void showMessage(String text){
        Toast toast = Toast.makeText(getApplicationContext(), text,
            Toast.LENGTH_SHORT);
        toast.setGravity(Gravity.CENTER, 0, 0);
        toast.show();
      }
        
        private void releaseMP() {
        if (mediaPlayer != null) {
          try {
            mediaPlayer.release();
            mediaPlayer = null;
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
      }
        
        public void onClick(View view) {
        if (mediaPlayer == null)
          return;
        switch (view.getId()) {
        case R.id.b_Pause:
          if (mediaPlayer.isPlaying())
            mediaPlayer.pause();
          break;
        case R.id.b_Resume:
          if (!mediaPlayer.isPlaying())
            mediaPlayer.start();
          break;
        case R.id.b_Stop:
          mediaPlayer.stop();
          break;
        }
      }
        
          @Override
      public void onPrepared(MediaPlayer mp) {
        mp.start();
      }
    
      @Override
      public void onCompletion(MediaPlayer mp) {}
    
      @Override
      protected void onDestroy() {
        super.onDestroy();
        releaseMP();
      }
        
    }
        

    package com.example.lab5_4_media;
    
    import java.io.File;
    import java.util.ArrayList;
    import android.net.Uri;
    import android.os.Bundle;
    import android.app.Activity;
    import android.util.Log;
    import android.view.View;
    import android.widget.ImageView;
    import android.widget.TextView;
    
    public class GalleryActivity extends Activity {
      
      int currentImage=0;
      ArrayList<String> images;
      ImageView imageView;
      TextView nameView;
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_gallery);
      }
      
      @Override
      public void onResume(){
        super.onResume();
        
        currentImage=0;
        
        Log.d("myLogs","onResume cI="+currentImage);
        
        nameView=((TextView)findViewById(R.id.imageName));
        images=new ArrayList<String>();
        
        imageView=((ImageView)findViewById(R.id.image));
        try{
          File imagesDirectory=new File("/sdcard/TrainingMedia/");
          images=searchImage(imagesDirectory);
          updatePhoto(Uri.parse(images.get(currentImage)));
        }catch(Exception e){
          nameView.setText("Ошибка: Папка '/sdcard/TrainingMedia/' не найдена");
          Log.d("myLogs","Ошибка");
        }
      }
      
      @Override
        protected void onPause()
        {
            super.onPause();
            images.clear();
            Log.d("myLogs","onPause cI="+currentImage);
        }
      
      private ArrayList<String> searchImage(File dir){
        ArrayList<String> imagesFinded=new ArrayList<String>();
        for(File f:dir.listFiles()){
          if(!f.isDirectory()){
            String fileExt=getFileExt(f.getAbsolutePath());
            if(fileExt.equals("png") || fileExt.equals("jpg") || fileExt.equals("jpeg")){
              Log.d("myLogs","Файл найден "+f.getAbsolutePath());
              imagesFinded.add(f.getAbsolutePath());
            }
          }
        }
        return imagesFinded;
      }
      
      public static String getFileExt(String filename){
        return filename.substring(filename.lastIndexOf(".") + 1);
      }
      
      public void updatePhoto(Uri uri){
        try{
          nameView.setText((currentImage+1)+"/"+images.size());
          imageView.setImageURI(uri);
        }catch(Exception e){
          nameView.setText("Ошибка загрузки файла");
        }
      }
      
      public void onNext(View v){
        if(currentImage+1<images.size()  images.size()>0){
          currentImage++;
          updatePhoto(Uri.parse(images.get(currentImage)));
        }
      }
      
      public void onPrevious(View v){
        if(currentImage>0  images.size()>0){
          currentImage--;
          updatePhoto(Uri.parse(images.get(currentImage)));
        }
      }
      
    
      
    }
        

    package com.example.lab5_4_media;
    
    import android.os.Bundle;
    import android.app.Activity;
    import android.content.Intent;
    import android.util.Log;
    import android.view.Menu;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.ImageButton;
    
    public class MainActivity extends Activity {
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
          
        OnClickListener btnClick=new OnClickListener() {
          @Override
          public void onClick(View v) {
            Log.d("myLogs",v.getId()+"");
            Click(v.getId());
          }
        };
        ((ImageButton)findViewById(R.id.bMusic)).setOnClickListener(btnClick);
        ((ImageButton)findViewById(R.id.bCamera)).setOnClickListener(btnClick);
        ((ImageButton)findViewById(R.id.bGallery)).setOnClickListener(btnClick);
      }
      protected void Click(int view){
        Intent intent=null;
        Log.d("myLogs",view+"");
        switch (view){
          case R.id.bMusic: intent=new Intent(this,MediaActivity.class);   break;
          case R.id.bGallery: intent=new Intent(this,GalleryActivity.class); break;
          case R.id.bCamera: intent=new Intent(this,CameraActivity.class); break;
          default: break;
        }
        if(intent!=null){
          Log.d("myLogs","Интент = "+intent.toString());
          startActivity(intent);
        }
      }
    }
        
    Вернуться к учебному плану