Создадим приложение с использованием камеры и сохранением фотографий в галерею.
Обязательно добавьте в файл Manifest следующие разрешения:
<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
Зависимости Gradle
Откройте файл build.gradle и добавьте зависимости CameraX:
// Базовая библиотека CameraX, использующая реализацию camera2
val camerax_version = "1.4.0-rc01"
// Следующая строка необязательна, так как основная библиотека включается косвенно в camera-camera2
implementation("androidx.camera:camera-core:${camerax_version}")
implementation("androidx.camera:camera-camera2:${camerax_version}")
// Если вы хотите дополнительно использовать библиотеку жизненного цикла CameraX
implementation("androidx.camera:camera-lifecycle:${camerax_version}")
// Если вы хотите дополнительно использовать библиотеку CameraX VideoCapture
implementation("androidx.camera:camera-video:${camerax_version}")
// Если вы хотите дополнительно использовать класс CameraX View implementation("androidx.camera:camera-view:${camerax_version}")
// Если вы хотите дополнительно добавить интеграцию CameraX ML Kit Vision implementation("androidx.camera:camera-mlkit-vision:${camerax_version}")
// Если вы хотите дополнительно использовать библиотеку расширений CameraX
implementation("androidx.camera:camera-extensions:${camerax_version}")
Будем использовать ViewBinding, поэтому включите его следующим образом (в конце блока android{}):
buildFeatures {
viewBinding true
}
Сверстайте экран с кнопкой и просмотром камеры
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.camera.view.PreviewView
android:id="@+id/view_finder"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</androidx.camera.view.PreviewView>
<Button
android:id="@+id/take_photo_button"
android:layout_width="114dp"
android:layout_height="134dp"
android:text="Take photo"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/view_finder"
app:layout_constraintStart_toStartOf="@+id/view_finder"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
Создайте Launcher для удобного получения результатов от Activity (нам нужно получить разрешение на использование камеры). Если все разрешения получены, запускается камера; если нет — выводится сообщение об ошибке.
private val launcher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()){ map ->
if (map.values.all { it }){
startCamera()
}else{
Toast.makeText(this, "Permission not Granted", Toast.LENGTH_SHORT).show()
}
}
Создайте метод для проверки наличия необходимых разрешений. Если все разрешения получены, выводится сообщение, иначе запрашиваются недостающие разрешения
private fun checkPermissions(){
val isAllGranted = REQUEST_PERMISSION.all { permission ->
ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED}
if (isAllGranted){
Toast.makeText(this, "permission is Granted", Toast.LENGTH_SHORT).show()
}else{
launcher.launch(REQUEST_PERMISSION)
}
}
Создайте companion object, где будет список разрешений, которые требуется запросить у пользователя. Включает разрешение на камеру и, для старых версий Android, разрешение на запись данных на внешнее хранилище.
companion object{
private val REQUEST_PERMISSION: Array<String> = buildList {
add(Manifest.permission.CAMERA)
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P){
add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
}.toTypedArray()
}
После получения разрешений можно приступить к запуску камеры. Создадим метод, который отвечает за запуск камеры. Он создает объекты Preview
и ImageCapture
, затем привязывает их к жизненному циклу активности, используя заднюю камеру по умолчанию.
private fun startCamera(){
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build()
preview.setSurfaceProvider(binding.viewFinder.surfaceProvider)
imageCapture = ImageCapture.Builder().build()
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
this,
CameraSelector.DEFAULT_BACK_CAMERA,
preview,
imageCapture
)
}, executor)
}
Осталось только сделать фотографию и сохранить ее. Создаем метод, который инициирует процесс съемки фотографии. Создаются параметры для сохранения изображения, а затем вызывается метод takePicture
для съемки с сохранением изображения в галерею устройства.
private fun takePhoto() {
val imageCapture = imageCapture?: return
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, name)
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
}
val outputOptions = ImageCapture.OutputFileOptions
.Builder(
contentResolver,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
contentValues
)
.build()
imageCapture.takePicture(
outputOptions,
executor,
object : ImageCapture.OnImageSavedCallback{
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
Toast.makeText(this@MainActivity, "Photo saved on: ${outputFileResults.savedUri}", Toast.LENGTH_SHORT).show()
}
override fun onError(exception: ImageCaptureException) {
Toast.makeText(this@MainActivity, "Photo failed: ${exception.message}", Toast.LENGTH_SHORT).show()
exception.printStackTrace()
}
}
)
}
Весь код Activity:
//для формата даты и времени, чтобы создать имя файла для сохраняемой фотографии
private const val FILE_NAME_FORMAT = "yyyy-MM-dd-HH-mm-ss"
class MainActivity : AppCompatActivity() {
private var imageCapture: ImageCapture? = null //для захвата изображения
private lateinit var executor: Executor //будет хранить исполнитель для выполнения задач в основном потоке
private lateinit var binding: ActivityMainBinding
//Имя файла для сохраняемой фотографии, сформированное на основе текущего времени
private val name = SimpleDateFormat(FILE_NAME_FORMAT, Locale.US).format(System.currentTimeMillis())
private val launcher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()){ map ->
if (map.values.all { it }){
startCamera()
}else{
Toast.makeText(this, "Permission not Granted", Toast.LENGTH_SHORT).show()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
executor = ContextCompat.getMainExecutor(this)
binding.takePhotoButton.setOnClickListener { takePhoto() }
checkPermissions()
}
private fun takePhoto() {
val imageCapture = imageCapture?: return
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, name)
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
}
val outputOptions = ImageCapture.OutputFileOptions
.Builder(
contentResolver,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
contentValues
)
.build()
imageCapture.takePicture(
outputOptions,
executor,
object : ImageCapture.OnImageSavedCallback{
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
Toast.makeText(this@MainActivity, "Photo saved on: ${outputFileResults.savedUri}", Toast.LENGTH_SHORT).show()
}
override fun onError(exception: ImageCaptureException) {
Toast.makeText(this@MainActivity, "Photo failed: ${exception.message}", Toast.LENGTH_SHORT).show()
exception.printStackTrace()
}
}
)
}
//для запуска камеры
private fun startCamera(){
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build()
preview.setSurfaceProvider(binding.viewFinder.surfaceProvider)
imageCapture = ImageCapture.Builder().build()
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
this,
CameraSelector.DEFAULT_BACK_CAMERA,
preview,
imageCapture
)
}, executor)
}
private fun checkPermissions(){
val isAllGranted = REQUEST_PERMISSION.all { permission ->
ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED}
if (isAllGranted){
Toast.makeText(this, "permission is Granted", Toast.LENGTH_SHORT).show()
}else{
launcher.launch(REQUEST_PERMISSION)
}
}
companion object{
private val REQUEST_PERMISSION: Array<String> = buildList {
add(Manifest.permission.CAMERA)
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P){
add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
}.toTypedArray()
}
}
Простое приложение с использованием камеры и сохранением фотографий готово. Советую более подробно изучить документацию CameraX, а далее уже бежать селфиться и снимать видеоблоги уже в своем собственном приложении. Всем удачи!