Освой Android играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Программное включение приложения Камера
Делаем фотографии и сохраняем результат. Простой пример
Миниатюра и полноразмерное изображение
Запуск камеры в нужном режиме
Снимаем и кадрируем
Практически все современные телефоны и планшеты снабжаются камерами, что позволяет фотографировать любимых котиков.
Сначала сделаем небольшие приготовления. Есть класс устройств, у которых нет камер, например, электронные ридеры. Чтобы пользователи этих устройств не скачивали зря ваше приложение, которое окажется для них бесполезным, пропишем в манифесте требование наличия камеры.
<uses-feature android:name="android.hardware.camera" android:required="true" />
Если камера в вашем приложении выполняет вспомогательную функцию, а приложение может работать и без неё, то установите значение false в предыдущем коде и проверяйте наличие камеры программно. Если камеры нет, то отключайте возможность съёмок для пользователя.
// Kotlin
val packageManager = packageManager
val isCamera = packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
println(isCamera)
// Java
PackageManager packageManager = getPackageManager();
boolean isCamera = packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
System.out.println(isCamera);
Раньше на старых телефонах можно было программно запустить из своей программы системное приложение "Камера" (в этом случае вам не понадобятся дополнительные разрешения) через намерение.
Intent intent = new Intent();
intent.setAction(Intent.ACTION_CAMERA_BUTTON);
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_CAMERA));
sendOrderedBroadcast(intent, null);
Особого смысла в этом нет, так как результат не возвращается в приложение. Но вполне подходило для быстрого запуска камеры. Впрочем, сейчас этот код может и не сработать.
А вообще у пользователя могут стоять разные приложения, способные фотографировать. Тогда у вас будет появляться диалоговое окно с выбором нужного приложения. Они все имеют в своём составе такую запись в манифесте (для общего развития):
<action android:name="android.media.action.IMAGE_CAPTURE"/>
<category android:name="android.intent.category.DEFAULT"/>
У Гугла есть своя программа Google Камера. Запустим её, зная имя пакета.
Intent intent = getIntent();
intent.setComponent(null);
intent.setPackage("com.google.android.GoogleCamera");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
При вызове метода getIntent() вместо new Intent() приложение запускалось сразу, иначе - выводилось диалоговое окно выбора программы из списка. Также нужно быть уверенным, что программа установлена, в примере нет кода проверки.
Просто включить камеру не слишком интересно. Рассмотрим практичный пример, когда мы программно запустим приложение "Камера", а полученную фотографию сохраним в папке. Сначала сделаем простой вариант, а потом напишем более сложное приложение.
Используйте статическую константу MediaStore.ACTION_IMAGE_CAPTURE для создания намерения, которое потом нужно передать методу startActivityForResult(). Разместите на форме кнопку и ImageView, в который будем помещать полученный снимок. Этот код запускает стандартное приложение камеры. Полученное с камеры изображение можно обработать в методе onActivityResult():
Не включайте в манифест разрешение на работу с камерой, иначе получите крах приложения.
// Kotlin
// Если этот код работает, его написал Александр Климов,
// а если нет, то не знаю, кто его писал.
package ru.alexanderklimov.photo
import android.content.ActivityNotFoundException
import android.content.Intent
import android.graphics.Bitmap
import android.os.Bundle
import android.provider.MediaStore
import android.widget.Button
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private val REQUEST_TAKE_PHOTO = 1
private lateinit var imageView: ImageView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
imageView = findViewById(R.id.imageView)
val button: Button = findViewById(R.id.button)
button.setOnClickListener {
val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
try {
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO)
} catch (e: ActivityNotFoundException) {
e.printStackTrace()
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_TAKE_PHOTO && resultCode == RESULT_OK) {
// Фотка сделана, извлекаем миниатюру картинки
val thumbnailBitmap = data?.extras?.get("data") as Bitmap
imageView.setImageBitmap(thumbnailBitmap)
}
// Другой вариант с применением when
when(requestCode){
REQUEST_TAKE_PHOTO ->{
if(resultCode == Activity.RESULT_OK && data !== null){
imageView.setImageBitmap(data.extras?.get("data") as Bitmap)
}
}
else ->{
Toast.makeText(this, "Wrong request code", Toast.LENGTH_SHORT).show()
}
}
}
}
// Java
private static final int REQUEST_TAKE_PHOTO = 1;
private ImageView imageView;
// в методе onCreate()
imageView = findViewById(R.id.imageView);
// Щелчок кнопки
public void onClick(View v) {
Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
try{
startActivityForResult(takePhotoIntent, REQUEST_TAKE_PHOTO);
}catch (ActivityNotFoundException e){
e.printStackTrace();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_TAKE_PHOTO && resultCode == RESULT_OK) {
// Фотка сделана, извлекаем миниатюру картинки
Bundle extras = data.getExtras();
Bitmap thumbnailBitmap = (Bitmap) extras.get("data");
imageView.setImageBitmap(thumbnailBitmap);
}
}
Данный код запустит приложение, работающее с камерой, позволяя пользователю поменять настройки изображения, что освобождает вас от необходимости создавать своё собственное приложение для этих нужд. Вполне возможно, что у вас будет несколько приложений, умеющих делать фотографии, тогда сначала появится окно выбора программы.
При тестировании примера на своём телефоне я обнаружил небольшую проблему - когда снимок передавался обратно на моё приложение, то оно находилось в альбомном режиме, а потом возвращалось в портретный режим. При этом полученный снимок терялся. Поэтому перед нажатием кнопки я поворачивал телефон в альбомный режим, чтобы пример работал корректно. Возможно следует предусмотреть подобное поведение, например, запретить приложению реагировать на поворот и таким образом избежать перезапуска Activity. У некоторых телефонов такой проблемы нет.
По умолчанию фотография возвращается в виде объекта Bitmap, содержащего миниатюру. Этот объект находится в параметре data, передаваемом в метод onActivityResult(). Чтобы получить миниатюру в виде объекта Bitmap, нужно вызвать метод getParcelableExtra() из намерения, передав ему строковое значение data. В примере использовался упрощённый вариант.
Если вы укажете исходящий путь URI с помощью параметра MediaStore.EXTRA_OUTPUT в запущенном намерении, полноразмерное изображение, снятое камерой, сохранится в заданном месте. В таком случае в метод onActivityResult() не будет передана миниатюра, а итоговое намерение продемонстрирует значение null.
В следующем примере показано, как при создании снимка получать миниатюру или полноценное изображение, используя намерение. Изображение будет сохранено во внешнем хранилище под именем test.jpg.
Пример работает на устройствах до Android 6.0, потом появились различные системные ограничения и нужно писать дополнительный код.
Добавим разрешение на запись файла в хранилище.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
// Если этот код работает, его написал Александр Климов,
// а если нет, то не знаю, кто его писал.
package ru.alexanderklimov.photocamera;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;
import java.io.File;
public class MainActivity extends AppCompatActivity {
private static int TAKE_PICTURE_REQUEST = 1;
private ImageView imageView;
private Uri outputFileUri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = findViewById(R.id.imageView);
}
public void onClick(View view) {
//getThumbnailPicture();
saveFullImage();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == TAKE_PICTURE_REQUEST && resultCode == RESULT_OK) {
// Проверяем, содержит ли результат маленькую картинку
if (data != null) {
if (data.hasExtra("data")) {
Bitmap thumbnailBitmap = data.getParcelableExtra("data");
// Какие-то действия с миниатюрой
imageView.setImageBitmap(thumbnailBitmap);
}
} else {
// Какие-то действия с полноценным изображением,
// сохранённым по адресу outputFileUri
imageView.setImageURI(outputFileUri);
}
}
}
private void getThumbnailPicture() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, TAKE_PICTURE_REQUEST);
}
private void saveFullImage() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
File file = new File(Environment.getExternalStorageDirectory(),
"test.jpg");
outputFileUri = Uri.fromFile(file);
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
startActivityForResult(intent, TAKE_PICTURE_REQUEST);
}
}
В реальных приложениях для создания имён файлов используют текущую дату, чтобы обеспечить уникальность и не затереть существующую фотографию.
Мы можем выбрать намерение, позволяющее включить камеру в нужном режиме: фотосъёмка или видео.
private static final int REQUEST_TAKE_PHOTO = 1;
private static final int REQUEST_TAKE_VIDEO = 2;
// фотосъёмка
public void capturePhoto() {
Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(intent, REQUEST_TAKE_PHOTO);
}
}
// видеосъёмка
public void captureVideo() {
Intent intent = new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(intent, REQUEST_TAKE_VIDEO);
}
}
Рассмотрим ещё один пример с режимом кадрирования. Основная часть кода остаётся прежней. Рекомендую проверять работу с камерой на реальных устройствах, так как многие производители заменяют стандартные методы съёмки своими прошивками и драйверами. В частности, намерение с кадрированием является проблемной, и в интернете многие жалуются на отсутствие поддержки этого способа. Пример для старых устройств до Android 6.0.
Создадим простенький макет из кнопки для запуска камеры и ImageView для вывода кадрированного изображения.
<LinearLayout 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:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<Button
android:id="@+id/buttonCapture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:onClick="onClick"
android:text="Запустить камеру" />
<ImageView
android:id="@+id/picture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/background" />
</LinearLayout>
Для большей красоты сделаем задний фон у ImageView с закруглёнными углами и обводкой. Для этого в атрибуте android:background мы прописали специальный стиль. Создайте папку res/drawable, а в ней файл background.xml следующего содержания:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:dither="true" >
<gradient
android:angle="90"
android:centerColor="#00000000"
android:endColor="#99ffffff"
android:startColor="#99ffffff" />
<padding
android:bottom="10dp"
android:left="10dp"
android:right="10dp"
android:top="10dp" />
<corners android:radius="5dp" />
<stroke
android:width="2dp"
android:color="#ccffffff" />
</shape>
Этот шаг не является обязательным и его можно пропустить.
При нажатии кнопки запускаем приложение Камера и ожидаем результата.
public void onClick(View view) {
try {
// Намерение для запуска камеры
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(captureIntent, CAMERA_REQUEST);
} catch (ActivityNotFoundException e) {
// Выводим сообщение об ошибке
String errorMessage = "Ваше устройство не поддерживает съемку";
Toast toast = Toast
.makeText(this, errorMessage, Toast.LENGTH_SHORT);
toast.show();
}
}
После того, как пользователь сделал нужный кадр, программа Камера возвращает результат обратно в наше приложение. Результат обрабатывается в методе onActivityResult():
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
// Вернулись от приложения Камера
if (requestCode == CAMERA_REQUEST) {
// Получим Uri снимка
picUri = data.getData();
// кадрируем его
performCrop();
}
// Вернулись из операции кадрирования
else if(requestCode == PIC_CROP){
Bundle extras = data.getExtras();
// Получим кадрированное изображение
Bitmap thePic = extras.getParcelable("data");
// передаём его в ImageView
ImageView picView = (ImageView)findViewById(R.id.picture);
picView.setImageBitmap(thePic);
}
}
}
Получив полноразмерное изображение, мы пытаемся откадрировать его. Для этого создадим метод performCrop(), который запускает специальное намерение, предназначенное для этих целей. В успешном случае результат снова возвращается в наше приложение, но уже с другим кодом PIC_CROP. Теперь мы имеем нужное изображение, которое можно вывести на экран.
При кадрировании мы указываем желаемые размеры (код метода ниже). Если указать слишком больше размеры (больше 400), то результат не возвращается. Попробуйте добавить ещё два параметра:
//куда сохраняем
intent.putExtra("output", picUri);
intent.putExtra("outputFormat", "JPEG");
Результат работы приложения, когда запускается намерение кадрирования и итоговый результат. Желательно тренироваться на кошках.
Исходник полностью.
package ru.alexanderklimov.photo;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
public class MainActivity extends Activity {
final int CAMERA_REQUEST = 1;
final int PIC_CROP = 2;
private Uri picUri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setTitle("Съёмка и Кадрирование");
}
public void onClick(View v) {
try {
// Намерение для запуска камеры
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(captureIntent, CAMERA_REQUEST);
} catch (ActivityNotFoundException e) {
// Выводим сообщение об ошибке
String errorMessage = "Ваше устройство не поддерживает съемку";
Toast toast = Toast
.makeText(this, errorMessage, Toast.LENGTH_SHORT);
toast.show();
}
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
// Вернулись от приложения Камера
if (requestCode == CAMERA_REQUEST) {
// Получим Uri снимка
picUri = data.getData();
// кадрируем его
performCrop();
}
// Вернулись из операции кадрирования
else if(requestCode == PIC_CROP){
Bundle extras = data.getExtras();
// Получим кадрированное изображение
Bitmap thePic = extras.getParcelable("data");
// передаём его в ImageView
ImageView picView = (ImageView)findViewById(R.id.picture);
picView.setImageBitmap(thePic);
}
}
}
private void performCrop(){
try {
// Намерение для кадрирования. Не все устройства поддерживают его
Intent cropIntent = new Intent("com.android.camera.action.CROP");
cropIntent.setDataAndType(picUri, "image/*");
cropIntent.putExtra("crop", "true");
cropIntent.putExtra("aspectX", 1);
cropIntent.putExtra("aspectY", 1);
cropIntent.putExtra("outputX", 256);
cropIntent.putExtra("outputY", 256);
cropIntent.putExtra("return-data", true);
startActivityForResult(cropIntent, PIC_CROP);
}
catch(ActivityNotFoundException anfe){
String errorMessage = "Извините, но ваше устройство не поддерживает кадрирование";
Toast toast = Toast.makeText(this, errorMessage, Toast.LENGTH_SHORT);
toast.show();
}
}
}
Снимаем на камеру в Android 7.0+