Repository: robertlevonyan/camerax-demo
Branch: master
Commit: 5792be7ffdb2
Files: 79
Total size: 155.6 KB
Directory structure:
gitextract_1un5hc0l/
├── .gitignore
├── LICENSE
├── README.md
└── camerax-demo/
├── .gitignore
├── .idea/
│ ├── .gitignore
│ └── codeStyles/
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── app/
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── kotlin/
│ │ └── com/
│ │ └── robertlevonyan/
│ │ └── demo/
│ │ └── camerax/
│ │ ├── CameraXApplication.kt
│ │ ├── MainActivity.kt
│ │ ├── adapter/
│ │ │ ├── Media.kt
│ │ │ ├── MediaAdapter.kt
│ │ │ └── MediaDiffCallback.kt
│ │ ├── analyzer/
│ │ │ └── LuminosityAnalyzer.kt
│ │ ├── enums/
│ │ │ └── CameraTimer.kt
│ │ ├── fragments/
│ │ │ ├── BaseFragment.kt
│ │ │ ├── CameraFragment.kt
│ │ │ ├── PreviewFragment.kt
│ │ │ └── VideoFragment.kt
│ │ └── utils/
│ │ ├── Extensions.kt
│ │ ├── MainExecutor.kt
│ │ ├── SharedPrefsManager.kt
│ │ ├── SwipeGestureDetector.kt
│ │ └── ThreadExecutor.kt
│ └── res/
│ ├── anim/
│ │ ├── slide_in.xml
│ │ ├── slide_in_pop.xml
│ │ ├── slide_out.xml
│ │ └── slide_out_pop.xml
│ ├── drawable/
│ │ ├── bg_button_round.xml
│ │ ├── bg_options.xml
│ │ ├── ic_arrow_back.xml
│ │ ├── ic_delete.xml
│ │ ├── ic_edit.xml
│ │ ├── ic_exposure.xml
│ │ ├── ic_flash_auto.xml
│ │ ├── ic_flash_off.xml
│ │ ├── ic_flash_on.xml
│ │ ├── ic_grid_off.xml
│ │ ├── ic_grid_on.xml
│ │ ├── ic_hdr_off.xml
│ │ ├── ic_hdr_on.xml
│ │ ├── ic_launcher_foreground.xml
│ │ ├── ic_no_picture.xml
│ │ ├── ic_outline_camera_enhance.xml
│ │ ├── ic_outline_camera_front.xml
│ │ ├── ic_outline_camera_rear.xml
│ │ ├── ic_play.xml
│ │ ├── ic_share.xml
│ │ ├── ic_take_picture.xml
│ │ ├── ic_take_video.xml
│ │ ├── ic_timer_10.xml
│ │ ├── ic_timer_3.xml
│ │ └── ic_timer_off.xml
│ ├── layout/
│ │ ├── activity_main.xml
│ │ ├── fragment_camera.xml
│ │ ├── fragment_preview.xml
│ │ ├── fragment_video.xml
│ │ └── item_picture.xml
│ ├── layout-land/
│ │ ├── fragment_camera.xml
│ │ └── fragment_video.xml
│ ├── menu/
│ │ └── menu_main.xml
│ ├── mipmap-anydpi-v26/
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ ├── navigation/
│ │ └── nav_graph.xml
│ ├── values/
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── xml/
│ └── provider_paths.xml
├── build.gradle.kts
├── gradle/
│ ├── libs.versions.toml
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Built application files
*.apk
*.ap_
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/assetWizardSettings.xml
.idea/dictionaries
.idea/libraries
.idea/caches
# Keystore files
# Uncomment the following line if you do not want to check your keystore files in.
#*.jks
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
# Google Services (e.g. APIs or Firebase)
google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 Robert
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# <img src="https://github.com/robertlevonyan/camerax-demo/blob/master/camerax-demo/app/src/main/ic_launcher-web.png" width="50" height="50" /> Camera X Demo
|A demo camera application created with Android Jetpack CameraX API|<img src="https://github.com/robertlevonyan/camerax-demo/blob/master/media/camerax.png" width="450" />|
|----------------------------------------------------------------------------------------------|-----------|
[](https://android-arsenal.com/api?level=21)
### UI Structure and features
| <p align="start">1. Take photo </p><p align="start"> 2. Switch camera </p><p align="start"> 3. Open gallery </p><p align="start"> 4. Select timer </p><p align="start"> 5. Toggle grid </p><p align="start"> 6. Select flashlight mode </p><p align="start"> 7. Toggle HDR (if device supports)</p><p align="start">8. Record video </p>|<img src="https://github.com/robertlevonyan/camerax-demo/blob/master/media/c1.jpg" width="250" />|<img src="https://github.com/robertlevonyan/camerax-demo/blob/master/media/v1.jpg" width="250" />|
|-----------------------------------|---------------------------------------------|---------------------------------------------------|
||<h2 align="center">Some Screenshots</h2>||
|<img src="https://github.com/robertlevonyan/camerax-demo/blob/master/media/c2.jpg" width="250" />|<img src="https://github.com/robertlevonyan/camerax-demo/blob/master/media/c3.jpg" width="250" /> |<img src="https://github.com/robertlevonyan/camerax-demo/blob/master/media/c4.jpg" width="250" />
## How to switch between photo and video
<img src="https://github.com/robertlevonyan/CameraXDemo/blob/master/media/switch.gif" width="500" />
## Contact
- **Email**: me@robertlevonyan.com
- **Website**: https://robertlevonyan.com/
- **Medium**: https://medium.com/@RobertLevonyan
- **Twitter**: https://twitter.com/@RobertLevonyan
- **Facebook**: https://facebook.com/robert.levonyan
- **Google Play**: https://play.google.com/store/apps/dev?id=5477562049350283357
<a href="https://www.buymeacoffee.com/robertlevonyan">
<img src="https://github.com/robertlevonyan/camerax-demo/blob/master/media/coffee.jpeg" width="300" />
</a>
## Licence
```
MIT License
Copyright (c) 2019 Robert
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
================================================
FILE: camerax-demo/.gitignore
================================================
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
/gradlew
/gradlew.bat
/.idea/compiler.xml
/.idea/gradle.xml
/.idea/jarRepositories.xml
/.idea/misc.xml
/.idea/vcs.xml
/.idea/
/.idea/codeStyles/
/.idea/.gitignore
================================================
FILE: camerax-demo/.idea/.gitignore
================================================
# Default ignored files
/shelf/
/workspace.xml
================================================
FILE: camerax-demo/.idea/codeStyles/Project.xml
================================================
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="RIGHT_MARGIN" value="140" />
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>
================================================
FILE: camerax-demo/.idea/codeStyles/codeStyleConfig.xml
================================================
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>
================================================
FILE: camerax-demo/app/.gitignore
================================================
/build
================================================
FILE: camerax-demo/app/build.gradle.kts
================================================
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.android.nav.safeargs)
}
android {
compileSdk = 35
defaultConfig {
applicationId = "com.robertlevonyan.demo.camerax"
minSdk = 21
targetSdk = 35
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
getByName("release") {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
viewBinding = true
}
namespace = "com.robertlevonyan.demo.camerax"
}
dependencies {
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.material)
implementation(libs.androidx.appcompat)
implementation(libs.core.ktx)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.camera.core)
implementation(libs.androidx.camera.camera2)
implementation(libs.androidx.camera.lifecycle)
implementation(libs.androidx.camera.extensions)
implementation(libs.androidx.camera.view)
implementation(libs.androidx.fragment.ktx)
implementation(libs.androidx.navigation.fragment.ktx)
implementation(libs.androidx.navigation.ui.ktx)
implementation(libs.lifecycle.runtime.ktx)
implementation(libs.androidx.viewpager2)
implementation(libs.coil)
implementation(libs.coil.video)
}
================================================
FILE: camerax-demo/app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: camerax-demo/app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />-->
<!-- <uses-permission-->
<!-- android:name="android.permission.WRITE_EXTERNAL_STORAGE"-->
<!-- tools:ignore="ScopedStorage" />-->
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<application
android:name=".CameraXApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.robertlevonyan.demo.camerax.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
</manifest>
================================================
FILE: camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/CameraXApplication.kt
================================================
package com.robertlevonyan.demo.camerax
import android.app.Application
import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.decode.VideoFrameDecoder
class CameraXApplication: Application(), ImageLoaderFactory {
override fun newImageLoader(): ImageLoader = ImageLoader.Builder(this)
.components {
add(VideoFrameDecoder.Factory())
}
.build()
}
================================================
FILE: camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/MainActivity.kt
================================================
package com.robertlevonyan.demo.camerax
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
================================================
FILE: camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/adapter/Media.kt
================================================
package com.robertlevonyan.demo.camerax.adapter
import android.net.Uri
import java.util.*
data class Media(
val uri: Uri,
val isVideo: Boolean,
val date: Long,
)
================================================
FILE: camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/adapter/MediaAdapter.kt
================================================
package com.robertlevonyan.demo.camerax.adapter
import android.net.Uri
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import coil.load
import com.robertlevonyan.demo.camerax.R
import com.robertlevonyan.demo.camerax.utils.layoutInflater
/**
* This is an adapter to preview taken photos or videos
* */
class MediaAdapter(
private val onItemClick: (Boolean, Uri) -> Unit,
private val onDeleteClick: (Boolean, Uri) -> Unit,
) : ListAdapter<Media, MediaAdapter.PicturesViewHolder>(MediaDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
PicturesViewHolder(parent.context.layoutInflater.inflate(R.layout.item_picture, parent, false))
override fun onBindViewHolder(holder: PicturesViewHolder, position: Int) {
holder.bind(getItem(position))
}
fun shareImage(currentPage: Int, action: (Media) -> Unit) {
if (currentPage < itemCount) {
action(getItem(currentPage))
}
}
fun deleteImage(currentPage: Int) {
if (currentPage < itemCount) {
val media = getItem(currentPage)
val allMedia = currentList.toMutableList()
allMedia.removeAt(currentPage)
submitList(allMedia)
onDeleteClick(allMedia.size == 0, media.uri)
}
}
inner class PicturesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val imagePreview: ImageView = itemView.findViewById(R.id.imagePreview)
private val imagePlay: ImageView = itemView.findViewById(R.id.imagePlay)
fun bind(item: Media) {
imagePlay.visibility = if (item.isVideo) View.VISIBLE else View.GONE
imagePreview.load(item.uri)
imagePreview.setOnClickListener { onItemClick(item.isVideo, item.uri) }
}
}
}
================================================
FILE: camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/adapter/MediaDiffCallback.kt
================================================
package com.robertlevonyan.demo.camerax.adapter
import androidx.recyclerview.widget.DiffUtil
class MediaDiffCallback : DiffUtil.ItemCallback<Media>() {
override fun areItemsTheSame(oldItem: Media, newItem: Media): Boolean = oldItem.uri == newItem.uri
override fun areContentsTheSame(oldItem: Media, newItem: Media): Boolean = oldItem == newItem
}
================================================
FILE: camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/analyzer/LuminosityAnalyzer.kt
================================================
package com.robertlevonyan.demo.camerax.analyzer
import android.util.Log
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import java.nio.ByteBuffer
import java.util.concurrent.TimeUnit
class LuminosityAnalyzer: ImageAnalysis.Analyzer {
private var lastAnalyzedTimestamp = 0L
private fun ByteBuffer.toByteArray(): ByteArray {
rewind() // Rewind the buffer to zero
val data = ByteArray(remaining())
get(data) // Copy the buffer into a byte array
return data // Return the byte array
}
override fun analyze(image: ImageProxy) {
val currentTimestamp = System.currentTimeMillis()
// Calculate the average luma no more often than every second
if (currentTimestamp - lastAnalyzedTimestamp >= TimeUnit.SECONDS.toMillis(1)) {
// Since format in ImageAnalysis is YUV, image.planes[0]
// contains the Y (luminance) plane
val buffer = image.planes[0].buffer
// Extract image data from callback object
val data = buffer.toByteArray()
// Convert the data into an array of pixel values
val pixels = data.map { it.toInt() and 0xFF }
// Compute average luminance for the image
val luma = pixels.average()
// Log the new luma value
Log.e("CameraXDemo", "Average luminosity: $luma")
// Update timestamp of last analyzed frame
lastAnalyzedTimestamp = currentTimestamp
}
}
}
================================================
FILE: camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/enums/CameraTimer.kt
================================================
package com.robertlevonyan.demo.camerax.enums
enum class CameraTimer {
OFF,
S3,
S10
}
================================================
FILE: camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/fragments/BaseFragment.kt
================================================
package com.robertlevonyan.demo.camerax.fragments
import android.Manifest
import android.content.ContentUris
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
import com.google.android.material.snackbar.Snackbar
import com.robertlevonyan.demo.camerax.R
import com.robertlevonyan.demo.camerax.adapter.Media
import java.io.File
/**Parent class of all the fragments in this project*/
abstract class BaseFragment<B : ViewBinding>(private val fragmentLayout: Int) : Fragment() {
/**
* Generic ViewBinding of the subclasses
* */
abstract val binding: B
// The Folder location where all the files will be stored
protected val outputDirectory: String by lazy {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
"${Environment.DIRECTORY_DCIM}/CameraXDemo/"
} else {
"${requireContext().getExternalFilesDir(Environment.DIRECTORY_DCIM)}/CameraXDemo/"
}
}
// The permissions we need for the app to work properly
private val permissions = mutableListOf(
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO,
// Manifest.permission.READ_EXTERNAL_STORAGE,
).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
add(Manifest.permission.ACCESS_MEDIA_LOCATION)
}
}
private val permissionRequest = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
if (permissions.all { it.value }) {
onPermissionGranted()
} else {
view?.let { v ->
Snackbar.make(v, R.string.message_no_permissions, Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.label_ok) { ActivityCompat.finishAffinity(requireActivity()) }
.show()
}
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
// Adding an option to handle the back press in fragment
requireActivity().onBackPressedDispatcher.addCallback(
viewLifecycleOwner,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
onBackPressed()
}
})
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (allPermissionsGranted()) {
onPermissionGranted()
} else {
permissionRequest.launch(permissions.toTypedArray())
}
}
protected fun getMedia(): List<Media> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
getMediaQPlus()
} else {
getMediaQMinus()
}.reversed()
private fun getMediaQPlus(): List<Media> {
val items = mutableListOf<Media>()
val contentResolver = requireContext().applicationContext.contentResolver
contentResolver.query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
arrayOf(
MediaStore.Video.Media._ID,
MediaStore.Video.Media.RELATIVE_PATH,
MediaStore.Video.Media.DATE_TAKEN,
),
null,
null,
"${MediaStore.Video.Media.DISPLAY_NAME} ASC"
)?.use { cursor ->
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
val pathColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.RELATIVE_PATH)
val dateColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATE_TAKEN)
while (cursor.moveToNext()) {
val id = cursor.getLong(idColumn)
val path = cursor.getString(pathColumn)
val date = cursor.getLong(dateColumn)
val contentUri: Uri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id)
if (path == outputDirectory) {
items.add(Media(contentUri, true, date))
}
}
}
contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
arrayOf(
MediaStore.Images.Media._ID,
MediaStore.Images.Media.RELATIVE_PATH,
MediaStore.Images.Media.DATE_TAKEN,
),
null,
null,
"${MediaStore.Images.Media.DISPLAY_NAME} ASC"
)?.use { cursor ->
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val pathColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.RELATIVE_PATH)
val dateColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN)
while (cursor.moveToNext()) {
val id = cursor.getLong(idColumn)
val path = cursor.getString(pathColumn)
val date = cursor.getLong(dateColumn)
val contentUri: Uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
if (path == outputDirectory) {
items.add(Media(contentUri, false, date))
}
}
}
return items
}
private fun getMediaQMinus(): List<Media> {
val items = mutableListOf<Media>()
File(outputDirectory).listFiles()?.forEach {
val authority = requireContext().applicationContext.packageName + ".provider"
val mediaUri = FileProvider.getUriForFile(requireContext(), authority, it)
items.add(Media(mediaUri, it.extension == "mp4", it.lastModified()))
}
return items
}
/**
* Check for the permissions
*/
protected fun allPermissionsGranted() = permissions.all {
ContextCompat.checkSelfPermission(requireContext(), it) == PackageManager.PERMISSION_GRANTED
}
/**
* A function which will be called after the permission check
* */
open fun onPermissionGranted() = Unit
/**
* An abstract function which will be called on the Back button press
* */
abstract fun onBackPressed()
}
================================================
FILE: camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/fragments/CameraFragment.kt
================================================
package com.robertlevonyan.demo.camerax.fragments
import android.annotation.SuppressLint
import android.content.ContentValues
import android.content.Context
import android.content.res.Configuration
import android.hardware.display.DisplayManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.HandlerThread
import android.provider.MediaStore
import android.util.DisplayMetrics
import android.util.Log
import android.view.GestureDetector
import android.view.View
import android.widget.Toast
import androidx.camera.core.*
import androidx.camera.core.ImageCapture.*
import androidx.camera.extensions.ExtensionMode
import androidx.camera.extensions.ExtensionsManager
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.core.content.ContextCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.lifecycleScope
import androidx.navigation.Navigation
import coil.load
import coil.request.ErrorResult
import coil.request.ImageRequest
import coil.transform.CircleCropTransformation
import com.robertlevonyan.demo.camerax.R
import com.robertlevonyan.demo.camerax.analyzer.LuminosityAnalyzer
import com.robertlevonyan.demo.camerax.databinding.FragmentCameraBinding
import com.robertlevonyan.demo.camerax.enums.CameraTimer
import com.robertlevonyan.demo.camerax.utils.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.io.File
import java.util.concurrent.ExecutionException
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.properties.Delegates
class CameraFragment : BaseFragment<FragmentCameraBinding>(R.layout.fragment_camera) {
// An instance for display manager to get display change callbacks
private val displayManager by lazy { requireContext().getSystemService(Context.DISPLAY_SERVICE) as DisplayManager }
// An instance of a helper function to work with Shared Preferences
private val prefs by lazy { SharedPrefsManager.newInstance(requireContext()) }
private var preview: Preview? = null
private var cameraProvider: ProcessCameraProvider? = null
private var imageCapture: ImageCapture? = null
private var imageAnalyzer: ImageAnalysis? = null
// A lazy instance of the current fragment's view binding
override val binding: FragmentCameraBinding by lazy { FragmentCameraBinding.inflate(layoutInflater) }
private var displayId = -1
// Selector showing which camera is selected (front or back)
private var lensFacing = CameraSelector.DEFAULT_BACK_CAMERA
private var hdrCameraSelector: CameraSelector? = null
// Selector showing which flash mode is selected (on, off or auto)
private var flashMode by Delegates.observable(FLASH_MODE_OFF) { _, _, new ->
binding.btnFlash.setImageResource(
when (new) {
FLASH_MODE_ON -> R.drawable.ic_flash_on
FLASH_MODE_AUTO -> R.drawable.ic_flash_auto
else -> R.drawable.ic_flash_off
}
)
}
// Selector showing is grid enabled or not
private var hasGrid = false
// Selector showing is hdr enabled or not (will work, only if device's camera supports hdr on hardware level)
private var hasHdr = false
// Selector showing is there any selected timer and it's value (3s or 10s)
private var selectedTimer = CameraTimer.OFF
/**
* A display listener for orientation changes that do not trigger a configuration
* change, for example if we choose to override config change in manifest or for 180-degree
* orientation changes.
*/
private val displayListener = object : DisplayManager.DisplayListener {
override fun onDisplayAdded(displayId: Int) = Unit
override fun onDisplayRemoved(displayId: Int) = Unit
@SuppressLint("UnsafeExperimentalUsageError", "UnsafeOptInUsageError")
override fun onDisplayChanged(displayId: Int) = view?.let { view ->
if (displayId == this@CameraFragment.displayId) {
preview?.targetRotation = view.display.rotation
imageCapture?.targetRotation = view.display.rotation
imageAnalyzer?.targetRotation = view.display.rotation
}
} ?: Unit
}
@SuppressLint("ClickableViewAccessibility")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
flashMode = prefs.getInt(KEY_FLASH, FLASH_MODE_OFF)
hasGrid = prefs.getBoolean(KEY_GRID, false)
hasHdr = prefs.getBoolean(KEY_HDR, false)
initViews()
displayManager.registerDisplayListener(displayListener, null)
binding.run {
viewFinder.addOnAttachStateChangeListener(object :
View.OnAttachStateChangeListener {
override fun onViewDetachedFromWindow(v: View) =
displayManager.registerDisplayListener(displayListener, null)
override fun onViewAttachedToWindow(v: View) =
displayManager.unregisterDisplayListener(displayListener)
})
btnTakePicture.setOnClickListener { takePicture() }
btnGallery.setOnClickListener { openPreview() }
btnSwitchCamera.setOnClickListener { toggleCamera() }
btnTimer.setOnClickListener { selectTimer() }
btnGrid.setOnClickListener { toggleGrid() }
btnFlash.setOnClickListener { selectFlash() }
btnHdr.setOnClickListener { toggleHdr() }
btnTimerOff.setOnClickListener { closeTimerAndSelect(CameraTimer.OFF) }
btnTimer3.setOnClickListener { closeTimerAndSelect(CameraTimer.S3) }
btnTimer10.setOnClickListener { closeTimerAndSelect(CameraTimer.S10) }
btnFlashOff.setOnClickListener { closeFlashAndSelect(FLASH_MODE_OFF) }
btnFlashOn.setOnClickListener { closeFlashAndSelect(FLASH_MODE_ON) }
btnFlashAuto.setOnClickListener { closeFlashAndSelect(FLASH_MODE_AUTO) }
btnExposure.setOnClickListener { flExposure.visibility = View.VISIBLE }
flExposure.setOnClickListener { flExposure.visibility = View.GONE }
// This swipe gesture adds a fun gesture to switch between video and photo
val swipeGestures = SwipeGestureDetector().apply {
setSwipeCallback(right = {
Navigation.findNavController(view).navigate(R.id.action_camera_to_video)
})
}
val gestureDetectorCompat = GestureDetector(requireContext(), swipeGestures)
viewFinder.setOnTouchListener { _, motionEvent ->
if (gestureDetectorCompat.onTouchEvent(motionEvent)) return@setOnTouchListener false
return@setOnTouchListener true
}
}
}
/**
* Create some initial states
* */
private fun initViews() {
binding.btnGrid.setImageResource(if (hasGrid) R.drawable.ic_grid_on else R.drawable.ic_grid_off)
binding.groupGridLines.visibility = if (hasGrid) View.VISIBLE else View.GONE
adjustInsets()
}
/**
* This methods adds all necessary margins to some views based on window insets and screen orientation
* */
private fun adjustInsets() {
activity?.window?.fitSystemWindows()
binding.btnTakePicture.onWindowInsets { view, windowInsets ->
if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
view.bottomMargin =
windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
} else {
view.endMargin = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).right
}
}
binding.btnTimer.onWindowInsets { view, windowInsets ->
view.topMargin = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).top
}
binding.llTimerOptions.onWindowInsets { view, windowInsets ->
if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
view.topPadding =
windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).top
} else {
view.startPadding =
windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).left
}
}
binding.llFlashOptions.onWindowInsets { view, windowInsets ->
if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
view.topPadding =
windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).top
} else {
view.startPadding =
windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).left
}
}
}
/**
* Change the facing of camera
* toggleButton() function is an Extension function made to animate button rotation
* */
@SuppressLint("RestrictedApi")
fun toggleCamera() = binding.btnSwitchCamera.toggleButton(
flag = lensFacing == CameraSelector.DEFAULT_BACK_CAMERA,
rotationAngle = 180f,
firstIcon = R.drawable.ic_outline_camera_rear,
secondIcon = R.drawable.ic_outline_camera_front,
) {
lensFacing = if (it) {
CameraSelector.DEFAULT_BACK_CAMERA
} else {
CameraSelector.DEFAULT_FRONT_CAMERA
}
startCamera()
}
/**
* Navigate to PreviewFragment
* */
private fun openPreview() {
if (getMedia().isEmpty()) return
view?.let { Navigation.findNavController(it).navigate(R.id.action_camera_to_preview) }
}
/**
* Show timer selection menu by circular reveal animation.
* circularReveal() function is an Extension function which is adding the circular reveal
* */
private fun selectTimer() = binding.llTimerOptions.circularReveal(binding.btnTimer)
/**
* This function is called from XML view via Data Binding to select a timer
* possible values are OFF, S3 or S10
* circularClose() function is an Extension function which is adding circular close
* */
private fun closeTimerAndSelect(timer: CameraTimer) =
binding.llTimerOptions.circularClose(binding.btnTimer) {
selectedTimer = timer
binding.btnTimer.setImageResource(
when (timer) {
CameraTimer.S3 -> R.drawable.ic_timer_3
CameraTimer.S10 -> R.drawable.ic_timer_10
CameraTimer.OFF -> R.drawable.ic_timer_off
}
)
}
/**
* Show flashlight selection menu by circular reveal animation.
* circularReveal() function is an Extension function which is adding the circular reveal
* */
private fun selectFlash() = binding.llFlashOptions.circularReveal(binding.btnFlash)
/**
* This function is called from XML view via Data Binding to select a FlashMode
* possible values are ON, OFF or AUTO
* circularClose() function is an Extension function which is adding circular close
* */
private fun closeFlashAndSelect(@FlashMode flash: Int) =
binding.llFlashOptions.circularClose(binding.btnFlash) {
flashMode = flash
binding.btnFlash.setImageResource(
when (flash) {
FLASH_MODE_ON -> R.drawable.ic_flash_on
FLASH_MODE_OFF -> R.drawable.ic_flash_off
else -> R.drawable.ic_flash_auto
}
)
imageCapture?.flashMode = flashMode
prefs.putInt(KEY_FLASH, flashMode)
}
/**
* Turns on or off the grid on the screen
* */
private fun toggleGrid() {
binding.btnGrid.toggleButton(
flag = hasGrid,
rotationAngle = 180f,
firstIcon = R.drawable.ic_grid_off,
secondIcon = R.drawable.ic_grid_on,
) { flag ->
hasGrid = flag
prefs.putBoolean(KEY_GRID, flag)
binding.groupGridLines.visibility = if (flag) View.VISIBLE else View.GONE
}
}
/**
* Turns on or off the HDR if available
* */
private fun toggleHdr() {
binding.btnHdr.toggleButton(
flag = hasHdr,
rotationAngle = 360f,
firstIcon = R.drawable.ic_hdr_off,
secondIcon = R.drawable.ic_hdr_on,
) { flag ->
hasHdr = flag
prefs.putBoolean(KEY_HDR, flag)
startCamera()
}
}
override fun onPermissionGranted() {
// Each time apps is coming to foreground the need permission check is being processed
binding.viewFinder.let { vf ->
vf.post {
// Setting current display ID
displayId = vf.display.displayId
startCamera()
lifecycleScope.launch(Dispatchers.IO) {
// Do on IO Dispatcher
setLastPictureThumbnail()
}
}
}
}
private fun setLastPictureThumbnail() = binding.btnGallery.post {
getMedia().firstOrNull() // check if there are any photos or videos in the app directory
?.let { setGalleryThumbnail(it.uri) } // preview the last one
?: binding.btnGallery.setImageResource(R.drawable.ic_no_picture) // or the default placeholder
}
/**
* Unbinds all the lifecycles from CameraX, then creates new with new parameters
* */
private fun startCamera() {
// This is the CameraX PreviewView where the camera will be rendered
val viewFinder = binding.viewFinder
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener({
try {
cameraProvider = cameraProviderFuture.get()
} catch (e: InterruptedException) {
Toast.makeText(requireContext(), "Error starting camera", Toast.LENGTH_SHORT).show()
return@addListener
} catch (e: ExecutionException) {
Toast.makeText(requireContext(), "Error starting camera", Toast.LENGTH_SHORT).show()
return@addListener
}
// The display information
val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it) }
// The ratio for the output image and preview
val aspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels)
// The display rotation
val rotation = viewFinder.display.rotation
val localCameraProvider = cameraProvider
?: throw IllegalStateException("Camera initialization failed.")
// The Configuration of camera preview
preview = Preview.Builder()
.setTargetAspectRatio(aspectRatio) // set the camera aspect ratio
.setTargetRotation(rotation) // set the camera rotation
.build()
// The Configuration of image capture
imageCapture = Builder()
.setCaptureMode(CAPTURE_MODE_MAXIMIZE_QUALITY) // setting to have pictures with highest quality possible (may be slow)
.setFlashMode(flashMode) // set capture flash
.setTargetAspectRatio(aspectRatio) // set the capture aspect ratio
.setTargetRotation(rotation) // set the capture rotation
.build()
checkForHdrExtensionAvailability()
// The Configuration of image analyzing
imageAnalyzer = ImageAnalysis.Builder()
.setTargetAspectRatio(aspectRatio) // set the analyzer aspect ratio
.setTargetRotation(rotation) // set the analyzer rotation
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) // in our analysis, we care about the latest image
.build()
.also { setLuminosityAnalyzer(it) }
// Unbind the use-cases before rebinding them
localCameraProvider.unbindAll()
// Bind all use cases to the camera with lifecycle
bindToLifecycle(localCameraProvider, viewFinder)
}, ContextCompat.getMainExecutor(requireContext()))
}
private fun checkForHdrExtensionAvailability() {
// Create a Vendor Extension for HDR
val extensionsManagerFuture = ExtensionsManager.getInstanceAsync(
requireContext(), cameraProvider ?: return,
)
extensionsManagerFuture.addListener(
{
val extensionsManager = extensionsManagerFuture.get() ?: return@addListener
val cameraProvider = cameraProvider ?: return@addListener
val isAvailable = extensionsManager.isExtensionAvailable(lensFacing, ExtensionMode.HDR)
// check for any extension availability
println("AUTO " + extensionsManager.isExtensionAvailable(lensFacing, ExtensionMode.AUTO))
println("HDR " + extensionsManager.isExtensionAvailable(lensFacing, ExtensionMode.HDR))
println("FACE RETOUCH " + extensionsManager.isExtensionAvailable(lensFacing, ExtensionMode.FACE_RETOUCH))
println("BOKEH " + extensionsManager.isExtensionAvailable(lensFacing, ExtensionMode.BOKEH))
println("NIGHT " + extensionsManager.isExtensionAvailable(lensFacing, ExtensionMode.NIGHT))
println("NONE " + extensionsManager.isExtensionAvailable(lensFacing, ExtensionMode.NONE))
// Check if the extension is available on the device
if (!isAvailable) {
// If not, hide the HDR button
binding.btnHdr.visibility = View.GONE
} else if (hasHdr) {
// If yes, turn on if the HDR is turned on by the user
binding.btnHdr.visibility = View.VISIBLE
hdrCameraSelector =
extensionsManager.getExtensionEnabledCameraSelector(lensFacing, ExtensionMode.HDR)
}
},
ContextCompat.getMainExecutor(requireContext())
)
}
private fun setLuminosityAnalyzer(imageAnalysis: ImageAnalysis) {
// Use a worker thread for image analysis to prevent glitches
val analyzerThread = HandlerThread("LuminosityAnalysis").apply { start() }
imageAnalysis.setAnalyzer(
ThreadExecutor(Handler(analyzerThread.looper)),
LuminosityAnalyzer()
)
}
private fun bindToLifecycle(localCameraProvider: ProcessCameraProvider, viewFinder: PreviewView) {
try {
localCameraProvider.bindToLifecycle(
viewLifecycleOwner, // current lifecycle owner
hdrCameraSelector ?: lensFacing, // either front or back facing
preview, // camera preview use case
imageCapture, // image capture use case
imageAnalyzer, // image analyzer use case
).run {
// Init camera exposure control
cameraInfo.exposureState.run {
val lower = exposureCompensationRange.lower
val upper = exposureCompensationRange.upper
binding.sliderExposure.run {
valueFrom = lower.toFloat()
valueTo = upper.toFloat()
stepSize = 1f
value = exposureCompensationIndex.toFloat()
addOnChangeListener { _, value, _ ->
cameraControl.setExposureCompensationIndex(value.toInt())
}
}
}
}
// Attach the viewfinder's surface provider to preview use case
preview?.setSurfaceProvider(viewFinder.surfaceProvider)
} catch (e: Exception) {
Log.e(TAG, "Failed to bind use cases", e)
}
}
/**
* Detecting the most suitable aspect ratio for current dimensions
*
* @param width - preview width
* @param height - preview height
* @return suitable aspect ratio
*/
private fun aspectRatio(width: Int, height: Int): Int {
val previewRatio = max(width, height).toDouble() / min(width, height)
if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)) {
return AspectRatio.RATIO_4_3
}
return AspectRatio.RATIO_16_9
}
@Suppress("NON_EXHAUSTIVE_WHEN")
private fun takePicture() = lifecycleScope.launch(Dispatchers.Main) {
// Show a timer based on user selection
when (selectedTimer) {
CameraTimer.S3 -> for (i in 3 downTo 1) {
binding.tvCountDown.text = i.toString()
delay(1000)
}
CameraTimer.S10 -> for (i in 10 downTo 1) {
binding.tvCountDown.text = i.toString()
delay(1000)
}
CameraTimer.OFF -> {}
}
binding.tvCountDown.text = ""
captureImage()
}
private fun captureImage() {
val localImageCapture = imageCapture ?: throw IllegalStateException("Camera initialization failed.")
// Setup image capture metadata
val metadata = Metadata().apply {
// Mirror image when using the front camera
isReversedHorizontal = lensFacing == CameraSelector.DEFAULT_FRONT_CAMERA
}
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
// Options fot the output image file
val outputOptions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, System.currentTimeMillis())
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
put(MediaStore.MediaColumns.RELATIVE_PATH, outputDirectory)
}
val contentResolver = requireContext().contentResolver
// Create the output uri
val contentUri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
OutputFileOptions.Builder(contentResolver, contentUri, contentValues)
} else {
File(outputDirectory).mkdirs()
val file = File(outputDirectory, "${System.currentTimeMillis()}.jpg")
OutputFileOptions.Builder(file)
}.setMetadata(metadata).build()
localImageCapture.takePicture(
outputOptions, // the options needed for the final image
requireContext().mainExecutor(), // the executor, on which the task will run
object : OnImageSavedCallback { // the callback, about the result of capture process
override fun onImageSaved(outputFileResults: OutputFileResults) {
// This function is called if capture is successfully completed
outputFileResults.savedUri
?.let { uri ->
setGalleryThumbnail(uri)
Log.d(TAG, "Photo saved in $uri")
}
?: setLastPictureThumbnail()
}
override fun onError(exception: ImageCaptureException) {
// This function is called if there is an errors during capture process
val msg = "Photo capture failed: ${exception.message}"
Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show()
Log.e(TAG, msg)
exception.printStackTrace()
}
}
)
}
private fun setGalleryThumbnail(savedUri: Uri?) = binding.btnGallery.load(savedUri) {
placeholder(R.drawable.ic_no_picture)
transformations(CircleCropTransformation())
listener(object : ImageRequest.Listener {
override fun onError(request: ImageRequest, result: ErrorResult) {
super.onError(request, result)
binding.btnGallery.load(savedUri) {
placeholder(R.drawable.ic_no_picture)
transformations(CircleCropTransformation())
// fetcher(VideoFrameUriFetcher(requireContext()))
}
}
})
}
override fun onDestroyView() {
super.onDestroyView()
displayManager.unregisterDisplayListener(displayListener)
}
override fun onBackPressed() = when {
binding.llTimerOptions.visibility == View.VISIBLE -> binding.llTimerOptions.circularClose(binding.btnTimer)
binding.llFlashOptions.visibility == View.VISIBLE -> binding.llFlashOptions.circularClose(binding.btnFlash)
else -> requireActivity().finish()
}
companion object {
private const val TAG = "CameraXDemo"
const val KEY_FLASH = "sPrefFlashCamera"
const val KEY_GRID = "sPrefGridCamera"
const val KEY_HDR = "sPrefHDR"
private const val RATIO_4_3_VALUE = 4.0 / 3.0 // aspect ratio 4x3
private const val RATIO_16_9_VALUE = 16.0 / 9.0 // aspect ratio 16x9
}
}
================================================
FILE: camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/fragments/PreviewFragment.kt
================================================
package com.robertlevonyan.demo.camerax.fragments
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.core.view.WindowInsetsCompat
import androidx.navigation.Navigation
import com.robertlevonyan.demo.camerax.R
import com.robertlevonyan.demo.camerax.adapter.MediaAdapter
import com.robertlevonyan.demo.camerax.databinding.FragmentPreviewBinding
import com.robertlevonyan.demo.camerax.utils.*
class PreviewFragment : BaseFragment<FragmentPreviewBinding>(R.layout.fragment_preview) {
private val mediaAdapter = MediaAdapter(
onItemClick = { isVideo, uri ->
if (!isVideo) {
val visibility = if (binding.groupPreviewActions.visibility == View.VISIBLE) View.GONE else View.VISIBLE
binding.groupPreviewActions.visibility = visibility
} else {
val play = Intent(Intent.ACTION_VIEW, uri).apply { setDataAndType(uri, "video/mp4") }
startActivity(play)
}
},
onDeleteClick = { isEmpty, uri ->
if (isEmpty) onBackPressed()
val resolver = requireContext().applicationContext.contentResolver
resolver.delete(uri, null, null)
},
)
private var currentPage = 0
override val binding: FragmentPreviewBinding by lazy { FragmentPreviewBinding.inflate(layoutInflater) }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adjustInsets()
// Check for the permissions and show files
if (allPermissionsGranted()) {
binding.pagerPhotos.apply {
adapter = mediaAdapter.apply { submitList(getMedia()) }
onPageSelected { page -> currentPage = page }
}
}
binding.btnBack.setOnClickListener { onBackPressed() }
binding.btnShare.setOnClickListener { shareImage() }
binding.btnDelete.setOnClickListener { deleteImage() }
}
/**
* This methods adds all necessary margins to some views based on window insets and screen orientation
* */
private fun adjustInsets() {
activity?.window?.fitSystemWindows()
binding.btnBack.onWindowInsets { view, windowInsets ->
view.topMargin = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).top
}
binding.btnShare.onWindowInsets { view, windowInsets ->
view.bottomMargin = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
}
}
private fun shareImage() {
mediaAdapter.shareImage(currentPage) { share(it) }
}
private fun deleteImage() {
mediaAdapter.deleteImage(currentPage)
}
override fun onBackPressed() {
view?.let { Navigation.findNavController(it).popBackStack() }
}
}
================================================
FILE: camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/fragments/VideoFragment.kt
================================================
package com.robertlevonyan.demo.camerax.fragments
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.ContentValues
import android.content.Context
import android.content.res.Configuration
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraMetadata
import android.hardware.display.DisplayManager
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.util.DisplayMetrics
import android.util.Log
import android.view.GestureDetector
import android.view.View
import android.widget.Toast
import androidx.camera.camera2.interop.Camera2CameraInfo
import androidx.camera.camera2.interop.ExperimentalCamera2Interop
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.video.*
import androidx.core.animation.doOnCancel
import androidx.core.content.ContextCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.lifecycleScope
import androidx.navigation.Navigation
import coil.load
import coil.request.ErrorResult
import coil.request.ImageRequest
import coil.transform.CircleCropTransformation
import com.robertlevonyan.demo.camerax.R
import com.robertlevonyan.demo.camerax.databinding.FragmentVideoBinding
import com.robertlevonyan.demo.camerax.utils.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.properties.Delegates
@ExperimentalCamera2Interop
@SuppressLint("RestrictedApi")
class VideoFragment : BaseFragment<FragmentVideoBinding>(R.layout.fragment_video) {
// An instance for display manager to get display change callbacks
private val displayManager by lazy { requireContext().getSystemService(Context.DISPLAY_SERVICE) as DisplayManager }
// An instance of a helper function to work with Shared Preferences
private val prefs by lazy { SharedPrefsManager.newInstance(requireContext()) }
private var camera: Camera? = null
private var cameraProvider: ProcessCameraProvider? = null
private var preview: Preview? = null
private var videoCapture: VideoCapture<Recorder>? = null
private var displayId = -1
// Selector showing which camera is selected (front or back)
private var lensFacing = CameraSelector.DEFAULT_BACK_CAMERA
// Selector showing which flash mode is selected (on, off or auto)
private var flashMode by Delegates.observable(ImageCapture.FLASH_MODE_OFF) { _, _, new ->
binding.btnFlash.setImageResource(
when (new) {
ImageCapture.FLASH_MODE_ON -> R.drawable.ic_flash_on
ImageCapture.FLASH_MODE_AUTO -> R.drawable.ic_flash_auto
else -> R.drawable.ic_flash_off
}
)
}
// Selector showing is grid enabled or not
private var hasGrid = false
// Selector showing is flash enabled or not
private var isTorchOn = false
// Selector showing is recording currently active
private var isRecording = false
private val animateRecord by lazy {
ObjectAnimator.ofFloat(binding.btnRecordVideo, View.ALPHA, 1f, 0.5f).apply {
repeatMode = ObjectAnimator.REVERSE
repeatCount = ObjectAnimator.INFINITE
doOnCancel { binding.btnRecordVideo.alpha = 1f }
}
}
// A lazy instance of the current fragment's view binding
override val binding: FragmentVideoBinding by lazy { FragmentVideoBinding.inflate(layoutInflater) }
/**
* A display listener for orientation changes that do not trigger a configuration
* change, for example if we choose to override config change in manifest or for 180-degree
* orientation changes.
*/
private val displayListener = object : DisplayManager.DisplayListener {
override fun onDisplayAdded(displayId: Int) = Unit
override fun onDisplayRemoved(displayId: Int) = Unit
@SuppressLint("UnsafeExperimentalUsageError", "UnsafeOptInUsageError")
override fun onDisplayChanged(displayId: Int) = view?.let { view ->
if (displayId == this@VideoFragment.displayId) {
preview?.targetRotation = view.display.rotation
videoCapture?.targetRotation = view.display.rotation
}
} ?: Unit
}
@SuppressLint("ClickableViewAccessibility")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
hasGrid = prefs.getBoolean(KEY_GRID, false)
initViews()
displayManager.registerDisplayListener(displayListener, null)
binding.run {
viewFinder.addOnAttachStateChangeListener(object :
View.OnAttachStateChangeListener {
override fun onViewDetachedFromWindow(v: View) =
displayManager.registerDisplayListener(displayListener, null)
override fun onViewAttachedToWindow(v: View) =
displayManager.unregisterDisplayListener(displayListener)
})
binding.btnRecordVideo.setOnClickListener { recordVideo() }
btnGallery.setOnClickListener { openPreview() }
btnSwitchCamera.setOnClickListener { toggleCamera() }
btnGrid.setOnClickListener { toggleGrid() }
btnFlash.setOnClickListener { toggleFlash() }
// This swipe gesture adds a fun gesture to switch between video and photo
val swipeGestures = SwipeGestureDetector().apply {
setSwipeCallback(left = {
Navigation.findNavController(view).navigate(R.id.action_video_to_camera)
})
}
val gestureDetectorCompat = GestureDetector(requireContext(), swipeGestures)
viewFinder.setOnTouchListener { _, motionEvent ->
if (gestureDetectorCompat.onTouchEvent(motionEvent)) return@setOnTouchListener false
return@setOnTouchListener true
}
}
}
/**
* Create some initial states
* */
private fun initViews() {
binding.btnGrid.setImageResource(if (hasGrid) R.drawable.ic_grid_on else R.drawable.ic_grid_off)
binding.groupGridLines.visibility = if (hasGrid) View.VISIBLE else View.GONE
adjustInsets()
}
/**
* This methods adds all necessary margins to some views based on window insets and screen orientation
* */
private fun adjustInsets() {
activity?.window?.fitSystemWindows()
binding.btnRecordVideo.onWindowInsets { view, windowInsets ->
if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
view.bottomMargin = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
} else {
view.endMargin = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).right
}
}
binding.btnFlash.onWindowInsets { view, windowInsets ->
view.topMargin = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()).top
}
}
/**
* Change the facing of camera
* toggleButton() function is an Extension function made to animate button rotation
* */
private fun toggleCamera() = binding.btnSwitchCamera.toggleButton(
flag = lensFacing == CameraSelector.DEFAULT_BACK_CAMERA,
rotationAngle = 180f,
firstIcon = R.drawable.ic_outline_camera_rear,
secondIcon = R.drawable.ic_outline_camera_front,
) {
lensFacing = if (it) {
CameraSelector.DEFAULT_BACK_CAMERA
} else {
CameraSelector.DEFAULT_FRONT_CAMERA
}
startCamera()
}
/**
* Unbinds all the lifecycles from CameraX, then creates new with new parameters
* */
private fun startCamera() {
// This is the Texture View where the camera will be rendered
val viewFinder = binding.viewFinder
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener({
cameraProvider = cameraProviderFuture.get()
// The display information
val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it) }
// The ratio for the output image and preview
val aspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels)
// The display rotation
val rotation = viewFinder.display.rotation
val localCameraProvider = cameraProvider
?: throw IllegalStateException("Camera initialization failed.")
// The Configuration of camera preview
preview = Preview.Builder()
.setTargetAspectRatio(aspectRatio) // set the camera aspect ratio
.setTargetRotation(rotation) // set the camera rotation
.build()
val cameraInfo = localCameraProvider.availableCameraInfos.filter {
Camera2CameraInfo
.from(it)
.getCameraCharacteristic(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_BACK
}
val supportedQualities = QualitySelector.getSupportedQualities(cameraInfo[0])
val qualitySelector = QualitySelector.fromOrderedList(
listOf(Quality.UHD, Quality.FHD, Quality.HD, Quality.SD),
FallbackStrategy.lowerQualityOrHigherThan(Quality.SD)
)
val recorder = Recorder.Builder()
.setExecutor(ContextCompat.getMainExecutor(requireContext())).setQualitySelector(qualitySelector)
.build()
videoCapture = VideoCapture.withOutput(recorder)
localCameraProvider.unbindAll() // unbind the use-cases before rebinding them
try {
// Bind all use cases to the camera with lifecycle
camera = localCameraProvider.bindToLifecycle(
viewLifecycleOwner, // current lifecycle owner
lensFacing, // either front or back facing
preview, // camera preview use case
videoCapture, // video capture use case
)
// Attach the viewfinder's surface provider to preview use case
preview?.setSurfaceProvider(viewFinder.surfaceProvider)
} catch (e: Exception) {
Log.e(TAG, "Failed to bind use cases", e)
}
}, ContextCompat.getMainExecutor(requireContext()))
}
/**
* Detecting the most suitable aspect ratio for current dimensions
*
* @param width - preview width
* @param height - preview height
* @return suitable aspect ratio
*/
private fun aspectRatio(width: Int, height: Int): Int {
val previewRatio = max(width, height).toDouble() / min(width, height)
if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)) {
return AspectRatio.RATIO_4_3
}
return AspectRatio.RATIO_16_9
}
/**
* Navigate to PreviewFragment
* */
private fun openPreview() {
view?.let { Navigation.findNavController(it).navigate(R.id.action_video_to_preview) }
}
var recording: Recording? = null
@SuppressLint("MissingPermission")
private fun recordVideo() {
if (recording != null) {
animateRecord.cancel()
recording?.stop()
}
val name = "CameraX-recording-${System.currentTimeMillis()}.mp4"
val contentValues = ContentValues().apply {
put(MediaStore.Video.Media.DISPLAY_NAME, name)
}
val mediaStoreOutput = MediaStoreOutputOptions.Builder(
requireContext().contentResolver,
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
)
.setContentValues(contentValues)
.build()
recording = videoCapture?.output
?.prepareRecording(requireContext(), mediaStoreOutput)
?.withAudioEnabled()
?.start(ContextCompat.getMainExecutor(requireContext())) { event ->
when (event) {
is VideoRecordEvent.Start -> {
animateRecord.start()
}
is VideoRecordEvent.Finalize -> {
if (!event.hasError()) {
val msg = "Video capture succeeded: " +
"${event.outputResults.outputUri}"
Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT)
.show()
Log.d(TAG, msg)
} else {
recording?.close()
recording = null
Log.e(TAG, "Video capture ends with error: " +
"${event.error}")
}
}
}
}
isRecording = !isRecording
}
/**
* Turns on or off the grid on the screen
* */
private fun toggleGrid() = binding.btnGrid.toggleButton(
flag = hasGrid,
rotationAngle = 180f,
firstIcon = R.drawable.ic_grid_off,
secondIcon = R.drawable.ic_grid_on
) { flag ->
hasGrid = flag
prefs.putBoolean(KEY_GRID, flag)
binding.groupGridLines.visibility = if (flag) View.VISIBLE else View.GONE
}
/**
* Turns on or off the flashlight
* */
private fun toggleFlash() = binding.btnFlash.toggleButton(
flag = flashMode == ImageCapture.FLASH_MODE_ON,
rotationAngle = 360f,
firstIcon = R.drawable.ic_flash_off,
secondIcon = R.drawable.ic_flash_on
) { flag ->
isTorchOn = flag
flashMode = if (flag) ImageCapture.FLASH_MODE_ON else ImageCapture.FLASH_MODE_OFF
camera?.cameraControl?.enableTorch(flag)
}
override fun onPermissionGranted() {
// Each time apps is coming to foreground the need permission check is being processed
binding.viewFinder.let { vf ->
vf.post {
// Setting current display ID
displayId = vf.display.displayId
startCamera()
lifecycleScope.launch(Dispatchers.IO) {
// Do on IO Dispatcher
setLastPictureThumbnail()
}
camera?.cameraControl?.enableTorch(isTorchOn)
}
}
}
private fun setLastPictureThumbnail() = binding.btnGallery.post {
getMedia().firstOrNull() // check if there are any photos or videos in the app directory
?.let { setGalleryThumbnail(it.uri) } // preview the last one
?: binding.btnGallery.setImageResource(R.drawable.ic_no_picture) // or the default placeholder
}
private fun setGalleryThumbnail(savedUri: Uri?) = binding.btnGallery.let { btnGallery ->
// Do the work on view's thread, this is needed, because the function is called in a Coroutine Scope's IO Dispatcher
btnGallery.post {
btnGallery.load(savedUri) {
placeholder(R.drawable.ic_no_picture)
transformations(CircleCropTransformation())
listener(object : ImageRequest.Listener {
override fun onError(request: ImageRequest, result: ErrorResult) {
super.onError(request, result)
binding.btnGallery.load(savedUri) {
placeholder(R.drawable.ic_no_picture)
transformations(CircleCropTransformation())
// fetcher(VideoFrameUriFetcher(requireContext()))
}
}
})
}
}
}
override fun onBackPressed() = requireActivity().finish()
override fun onStop() {
super.onStop()
camera?.cameraControl?.enableTorch(false)
}
companion object {
private const val TAG = "CameraXDemo"
const val KEY_GRID = "sPrefGridVideo"
private const val RATIO_4_3_VALUE = 4.0 / 3.0 // aspect ratio 4x3
private const val RATIO_16_9_VALUE = 16.0 / 9.0 // aspect ratio 16x9
}
}
================================================
FILE: camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/utils/Extensions.kt
================================================
package com.robertlevonyan.demo.camerax.utils
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.Build
import android.view.*
import android.view.View.*
import android.widget.ImageButton
import androidx.annotation.DrawableRes
import androidx.core.animation.doOnEnd
import androidx.core.animation.doOnStart
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
import androidx.viewpager2.widget.ViewPager2
import com.robertlevonyan.demo.camerax.adapter.Media
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.concurrent.Executor
fun ImageButton.toggleButton(
flag: Boolean, rotationAngle: Float, @DrawableRes firstIcon: Int, @DrawableRes secondIcon: Int,
action: (Boolean) -> Unit
) {
if (flag) {
if (rotationY == 0f) rotationY = rotationAngle
animate().rotationY(0f).apply {
setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
action(!flag)
}
})
}.duration = 200
GlobalScope.launch(Dispatchers.Main) {
delay(100)
setImageResource(firstIcon)
}
} else {
if (rotationY == rotationAngle) rotationY = 0f
animate().rotationY(rotationAngle).apply {
setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
action(!flag)
}
})
}.duration = 200
GlobalScope.launch(Dispatchers.Main) {
delay(100)
setImageResource(secondIcon)
}
}
}
fun ViewGroup.circularReveal(button: ImageButton) {
ViewAnimationUtils.createCircularReveal(
this,
button.x.toInt() + button.width / 2,
button.y.toInt() + button.height / 2,
0f,
if (button.context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) this.width.toFloat() else this.height.toFloat()
).apply {
duration = 500
doOnStart { visibility = VISIBLE }
}.start()
}
fun ViewGroup.circularClose(button: ImageButton, action: () -> Unit = {}) {
ViewAnimationUtils.createCircularReveal(
this,
button.x.toInt() + button.width / 2,
button.y.toInt() + button.height / 2,
if (button.context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) this.width.toFloat() else this.height.toFloat(),
0f
).apply {
duration = 500
doOnStart { action() }
doOnEnd { visibility = GONE }
}.start()
}
fun View.onWindowInsets(action: (View, WindowInsetsCompat) -> Unit) {
ViewCompat.requestApplyInsets(this)
ViewCompat.setOnApplyWindowInsetsListener(this) { v, insets ->
action(v, insets)
insets
}
}
fun Window.fitSystemWindows() {
WindowCompat.setDecorFitsSystemWindows(this, false)
}
fun Fragment.share(media: Media, title: String = "Share with...") {
val share = Intent(Intent.ACTION_SEND)
share.type = "image/*"
share.putExtra(Intent.EXTRA_STREAM, media.uri)
startActivity(Intent.createChooser(share, title))
}
fun ViewPager2.onPageSelected(action: (Int) -> Unit) {
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
action(position)
}
})
}
fun Context.mainExecutor(): Executor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
mainExecutor
} else {
MainExecutor()
}
val Context.layoutInflater: LayoutInflater
get() = LayoutInflater.from(this)
var View.topMargin: Int
get() = (this.layoutParams as ViewGroup.MarginLayoutParams).topMargin
set(value) {
updateLayoutParams<ViewGroup.MarginLayoutParams> { topMargin = value }
}
var View.topPadding: Int
get() = paddingTop
set(value) {
updateLayoutParams { setPaddingRelative(paddingStart, value, paddingEnd, paddingBottom) }
}
var View.bottomMargin: Int
get() = (this.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin
set(value) {
updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin = value }
}
var View.endMargin: Int
get() = (this.layoutParams as ViewGroup.MarginLayoutParams).marginEnd
set(value) {
updateLayoutParams<ViewGroup.MarginLayoutParams> { marginEnd = value }
}
var View.startMargin: Int
get() = (this.layoutParams as ViewGroup.MarginLayoutParams).marginStart
set(value) {
updateLayoutParams<ViewGroup.MarginLayoutParams> { marginStart = value }
}
var View.startPadding: Int
get() = paddingStart
set(value) {
updateLayoutParams { setPaddingRelative(value, paddingTop, paddingEnd, paddingBottom) }
}
================================================
FILE: camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/utils/MainExecutor.kt
================================================
package com.robertlevonyan.demo.camerax.utils
import android.os.Handler
import android.os.Looper
class MainExecutor : ThreadExecutor(Handler(Looper.getMainLooper())) {
override fun execute(runnable: Runnable) {
handler.post(runnable)
}
}
================================================
FILE: camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/utils/SharedPrefsManager.kt
================================================
package com.robertlevonyan.demo.camerax.utils
import android.content.Context
class SharedPrefsManager private constructor(private val context: Context) {
private val preferences = context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE)
companion object {
private const val PREFERENCES = "sPrefs"
@Synchronized
fun newInstance(context: Context) = SharedPrefsManager(context)
}
fun putBoolean(key: String, value: Boolean) = preferences.edit().putBoolean(key, value).apply()
fun putInt(key: String, value: Int) = preferences.edit().putInt(key, value).apply()
fun getBoolean(key: String, defValue: Boolean) = preferences.getBoolean(key, defValue)
fun getInt(key: String, defValue: Int) = preferences.getInt(key, defValue)
}
================================================
FILE: camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/utils/SwipeGestureDetector.kt
================================================
package com.robertlevonyan.demo.camerax.utils
import android.view.GestureDetector
import android.view.MotionEvent
import kotlin.math.abs
class SwipeGestureDetector : GestureDetector.SimpleOnGestureListener() {
companion object {
private const val MIN_SWIPE_DISTANCE_X = 100
}
var swipeCallback: SwipeCallback? = null
override fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
e1 ?: return super.onFling(e1, e2, velocityX, velocityY)
val deltaX = e1.x - e2.x
val deltaXAbs = abs(deltaX)
if (deltaXAbs >= MIN_SWIPE_DISTANCE_X) {
if (deltaX > 0) {
swipeCallback?.onLeftSwipe()
} else {
swipeCallback?.onRightSwipe()
}
}
return true
}
interface SwipeCallback {
fun onLeftSwipe()
fun onRightSwipe()
}
fun setSwipeCallback(left: ()-> Unit = {}, right: ()-> Unit = {}) {
swipeCallback = object : SwipeCallback {
override fun onLeftSwipe() {
left()
}
override fun onRightSwipe() {
right()
}
}
}
}
================================================
FILE: camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/utils/ThreadExecutor.kt
================================================
package com.robertlevonyan.demo.camerax.utils
import android.os.Handler
import java.util.concurrent.Executor
open class ThreadExecutor(protected val handler: Handler) : Executor {
override fun execute(runnable: Runnable) {
handler.post(runnable)
}
}
================================================
FILE: camerax-demo/app/src/main/res/anim/slide_in.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:fromXDelta="100%"
android:toXDelta="0%" />
================================================
FILE: camerax-demo/app/src/main/res/anim/slide_in_pop.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:fromXDelta="-100%"
android:toXDelta="0%" />
================================================
FILE: camerax-demo/app/src/main/res/anim/slide_out.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:fromXDelta="0%"
android:toXDelta="-100%" />
================================================
FILE: camerax-demo/app/src/main/res/anim/slide_out_pop.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:fromXDelta="0%"
android:toXDelta="100%" />
================================================
FILE: camerax-demo/app/src/main/res/drawable/bg_button_round.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/colorButtonGreyBg"/>
</shape>
================================================
FILE: camerax-demo/app/src/main/res/drawable/bg_options.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="16dp" />
<solid android:color="@color/colorPrimaryDark" />
</shape>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_arrow_back.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@android:color/white"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z" />
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_delete.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@android:color/white"
android:pathData="M9,3V4H4V6H5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V6H20V4H15V3H9M7,6H17V19H7V6M9,8V17H11V8H9M13,8V17H15V8H13Z"/>
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_edit.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@android:color/white"
android:pathData="M14.06,9L15,9.94L5.92,19H5V18.08L14.06,9M17.66,3C17.41,3 17.15,3.1 16.96,3.29L15.13,5.12L18.88,8.87L20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18.17,3.09 17.92,3 17.66,3M14.06,6.19L3,17.25V21H6.75L17.81,9.94L14.06,6.19Z"/>
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_exposure.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM17.59,5L5,17.59L5,5h12.59zM6.41,19L19,6.41L19,19L6.41,19zM6,7h5v1.5L6,8.5zM16,12.5h-1.5v2h-2L12.5,16h2v2L16,18v-2h2v-1.5h-2z" />
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_flash_auto.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M16.85,7.65L18,4L19.15,7.65M19,2H17L13.8,11H15.7L16.4,9H19.6L20.3,11H22.2M3,2V14H6V23L13,11H9L13,2H3Z" />
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_flash_off.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M17,10H13L17,2H7V4.18L15.46,12.64M3.27,3L2,4.27L7,9.27V13H10V22L13.58,15.86L17.73,20L19,18.73L3.27,3Z" />
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_flash_on.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M7,2V13H10V22L17,10H13L17,2H7Z" />
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_grid_off.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M0,2.77L1.28,1.5L22.5,22.72L21.23,24L19.23,22H4C2.92,22 2,21.1 2,20V4.77L0,2.77M10,4V7.68L8,5.68V4H6.32L4.32,2H20A2,2 0 0,1 22,4V19.7L20,17.7V16H18.32L16.32,14H20V10H16V13.68L14,11.68V10H12.32L10.32,8H14V4H10M16,4V8H20V4H16M16,20H17.23L16,18.77V20M4,8H5.23L4,6.77V8M10,14H11.23L10,12.77V14M14,20V16.77L13.23,16H10V20H14M8,20V16H4V20H8M8,14V10.77L7.23,10H4V14H8Z" />
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_grid_on.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M10,4V8H14V4H10M16,4V8H20V4H16M16,10V14H20V10H16M16,16V20H20V16H16M14,20V16H10V20H14M8,20V16H4V20H8M8,14V10H4V14H8M8,8V4H4V8H8M10,14H14V10H10V14M4,2H20A2,2 0 0,1 22,4V20A2,2 0 0,1 20,22H4C2.92,22 2,21.1 2,20V4A2,2 0 0,1 4,2Z" />
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_hdr_off.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M17.5,15V13H18.6L19.5,15H21L20.1,12.9C20.6,12.7 21,12.1 21,11.5V10.5C21,9.7 20.3,9 19.5,9H16V13.9L17.1,15H17.5M17.5,10.5H19.5V11.5H17.5V10.5M13,10.5V10.9L14.5,12.4V10.5C14.5,9.7 13.8,9 13,9H11.1L12.6,10.5H13M9.5,9.5L2.5,2.5L1.4,3.5L6.9,9H6.5V11H4.5V9H3V15H4.5V12.5H6.5V15H8V10.1L9.5,11.6V15H12.9L20.5,22.6L21.6,21.5L9.5,9.5Z" />
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_hdr_on.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M21,11.5V10.5C21,9.7 20.3,9 19.5,9H16V15H17.5V13H18.6L19.5,15H21L20.1,12.9C20.6,12.6 21,12.1 21,11.5M19.5,11.5H17.5V10.5H19.5V11.5M6.5,11H4.5V9H3V15H4.5V12.5H6.5V15H8V9H6.5V11M13,9H9.5V15H13C13.8,15 14.5,14.3 14.5,13.5V10.5C14.5,9.7 13.8,9 13,9M13,13.5H11V10.5H13V13.5Z" />
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_launcher_foreground.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="50.303032"
android:viewportHeight="50.303032">
<group android:translateX="8.551516"
android:translateY="8.551516">
<path
android:pathData="M18.5,19l-1.9,0.7L14.7,19l0.7,1.9l-0.7,1.9l1.9,-0.7l1.9,0.7L17.8,21L18.5,19M23.9,13.6H21l-1.7,-1.8h-5.5l-1.7,1.8H9.3c-1,0 -1.8,0.8 -1.8,1.8v11c0,1 0.8,1.8 1.8,1.8h14.7c1,0 1.8,-0.8 1.8,-1.8v-11C25.8,14.4 24.9,13.6 23.9,13.6M23.9,26.5H9.3v-11H13l0.6,-0.6l1.1,-1.2h3.9l1.1,1.2l0.6,0.6h3.8V26.5M16.6,16.4c-2.6,0 -4.6,2 -4.6,4.6s2,4.6 4.6,4.6s4.6,-2 4.6,-4.6S19.2,16.4 16.6,16.4M16.6,23.7c-1.6,0 -2.8,-1.2 -2.8,-2.8s1.2,-2.8 2.8,-2.8s2.8,1.2 2.8,2.8S18.2,23.7 16.6,23.7z"
android:fillColor="#1A1A18"/>
<path
android:pathData="M18.6,19l-2,0.8l-2,-0.8l0.8,2l-0.8,2l2,-0.8l2,0.8l-0.8,-2L18.6,19"
android:fillColor="#E7A942"/>
<path
android:pathData="M33.2,7.9H11.8C10.3,7.9 9,6.6 9,5.1v0c0,-1.5 1.2,-2.8 2.8,-2.8h21.4V7.9z"
android:fillColor="#E7A942"/>
<path
android:pathData="M12.7,3.6h1c0.3,0 0.6,0.1 0.8,0.2c0.2,0.1 0.4,0.3 0.5,0.5c0.1,0.2 0.2,0.5 0.2,0.7S15.1,5.6 15,5.8c-0.1,0.2 -0.3,0.4 -0.5,0.5c-0.2,0.1 -0.5,0.2 -0.8,0.2h-1V3.6zM13.7,6c0.3,0 0.5,-0.1 0.7,-0.2c0.2,-0.2 0.2,-0.4 0.2,-0.7c0,-0.3 -0.1,-0.5 -0.2,-0.7c-0.2,-0.2 -0.4,-0.2 -0.7,-0.2h-0.4V6H13.7z"
android:fillColor="#1A1A18"/>
<path
android:pathData="M16,6.4c-0.2,-0.1 -0.3,-0.2 -0.4,-0.4s-0.1,-0.3 -0.1,-0.6c0,-0.2 0,-0.4 0.1,-0.5c0.1,-0.2 0.2,-0.3 0.4,-0.4c0.2,-0.1 0.3,-0.1 0.5,-0.1c0.2,0 0.4,0 0.5,0.1c0.2,0.1 0.3,0.2 0.3,0.4c0.1,0.2 0.1,0.3 0.1,0.5c0,0.1 0,0.1 0,0.2h-1.5c0,0.2 0.1,0.3 0.2,0.4s0.2,0.1 0.4,0.1c0.1,0 0.2,0 0.3,-0.1C16.9,5.9 17,5.9 17,5.8L17.4,6c-0.2,0.4 -0.5,0.5 -0.9,0.5C16.3,6.5 16.1,6.5 16,6.4zM17,5.2c0,-0.1 0,-0.1 -0.1,-0.2s-0.1,-0.1 -0.2,-0.2c-0.1,0 -0.2,-0.1 -0.3,-0.1c-0.1,0 -0.2,0 -0.3,0.1C16.1,5 16,5.1 16,5.2H17z"
android:fillColor="#1A1A18"/>
<path
android:pathData="M17.9,4.4h0.5v0.3h0c0.1,-0.1 0.1,-0.2 0.3,-0.2c0.1,-0.1 0.2,-0.1 0.4,-0.1c0.1,0 0.3,0 0.4,0.1c0.1,0.1 0.2,0.2 0.2,0.3c0.1,-0.1 0.2,-0.2 0.3,-0.3c0.1,-0.1 0.3,-0.1 0.4,-0.1c0.2,0 0.4,0.1 0.5,0.2C21,4.7 21,4.9 21,5.2v1.3h-0.5V5.3c0,-0.1 0,-0.2 -0.1,-0.3c-0.1,-0.1 -0.1,-0.1 -0.3,-0.1c-0.1,0 -0.2,0.1 -0.3,0.2s-0.1,0.3 -0.1,0.4v1h-0.5V5.3c0,-0.1 0,-0.2 -0.1,-0.3C19,4.9 19,4.9 18.8,4.9c-0.1,0 -0.2,0.1 -0.3,0.2c-0.1,0.1 -0.1,0.3 -0.1,0.4v1h-0.5V4.4z"
android:fillColor="#1A1A18"/>
<path
android:pathData="M21.9,6.4c-0.2,-0.1 -0.3,-0.2 -0.4,-0.4s-0.1,-0.3 -0.1,-0.6c0,-0.2 0,-0.4 0.1,-0.6c0.1,-0.2 0.2,-0.3 0.4,-0.4s0.4,-0.1 0.6,-0.1c0.2,0 0.4,0 0.6,0.1s0.3,0.2 0.4,0.4c0.1,0.2 0.1,0.3 0.1,0.6c0,0.2 0,0.4 -0.1,0.6s-0.2,0.3 -0.4,0.4s-0.4,0.1 -0.6,0.1C22.3,6.5 22.1,6.5 21.9,6.4zM22.8,6c0.1,0 0.2,-0.1 0.2,-0.2c0.1,-0.1 0.1,-0.2 0.1,-0.3c0,-0.1 0,-0.2 -0.1,-0.3S22.9,5 22.8,4.9c-0.1,0 -0.2,-0.1 -0.3,-0.1c-0.1,0 -0.2,0 -0.3,0.1C22.1,5 22.1,5 22,5.1s-0.1,0.2 -0.1,0.3s0,0.2 0.1,0.3c0.1,0.1 0.1,0.2 0.2,0.2C22.3,6 22.4,6 22.5,6C22.6,6 22.7,6 22.8,6z"
android:fillColor="#1A1A18"/>
</group>
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_no_picture.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="197.76"
android:viewportHeight="197.76">
<path
android:fillColor="@color/colorButtonGreyBg"
android:pathData="M98.88,98.88m-98.88,0a98.88,98.88 0,1 1,197.76 0a98.88,98.88 0,1 1,-197.76 0" />
<path
android:pathData="M69,72.74L80.21,72.74l7.47,-7.47h22.4L117.54,72.74h11.21a7.47,7.47 0,0 1,7.47 7.47v44.81a7.47,7.47 0,0 1,-7.47 7.47L69,132.49A7.47,7.47 0,0 1,61.54 125.02L61.54,80.21a7.47,7.47 0,0 1,7.47 -7.47m34,16.62L95.33,99.54l5.79,7.73 -3.25,2.47L88.68,97.54 76.47,113.82h44.82Z"
android:strokeWidth="7"
android:strokeColor="@android:color/white" />
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_outline_camera_enhance.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,10l-0.94,2.06L9,13l2.06,0.94L12,16l0.94,-2.06L15,13l-2.06,-0.94zM20,5h-3.17L15,3L9,3L7.17,5L4,5c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,7c0,-1.1 -0.9,-2 -2,-2zM20,19L4,19L4,7h4.05l0.59,-0.65L9.88,5h4.24l1.24,1.35 0.59,0.65L20,7v12zM12,8c-2.76,0 -5,2.24 -5,5s2.24,5 5,5 5,-2.24 5,-5 -2.24,-5 -5,-5zM12,16c-1.65,0 -3,-1.35 -3,-3s1.35,-3 3,-3 3,1.35 3,3 -1.35,3 -3,3z"/>
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_outline_camera_front.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M5,20v2h5v2l3,-3 -3,-3v2zM14,20h5v2h-5zM11.99,8C13.1,8 14,7.1 14,6s-0.9,-2 -2.01,-2S10,4.9 10,6s0.89,2 1.99,2zM17,0L7,0C5.9,0 5,0.9 5,2v14c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,2c0,-1.1 -0.9,-2 -2,-2zM17,16L7,16v-2h10v2zM17,12.5c0,-1.67 -3.33,-2.5 -5,-2.5s-5,0.83 -5,2.5L7,2h10v10.5z" />
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_outline_camera_rear.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M5,20v2h5v2l3,-3 -3,-3v2zM14,20h5v2h-5zM17,0L7,0C5.9,0 5,0.9 5,2v14c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,2c0,-1.1 -0.9,-2 -2,-2zM17,16L7,16L7,2h10v14zM12,7c1.1,0 2,-0.9 1.99,-2 0,-1.1 -0.9,-2 -2,-2S10,3.9 10,5s0.89,2 2,2z" />
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_play.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@android:color/white"
android:pathData="M8.5,8.64L13.77,12L8.5,15.36V8.64M6.5,5V19L17.5,12" />
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_share.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92s2.92,-1.31 2.92,-2.92c0,-1.61 -1.31,-2.92 -2.92,-2.92zM18,4c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM6,13c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM18,20.02c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z"/>
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_take_picture.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="@dimen/button_size_big"
android:height="@dimen/button_size_big"
android:viewportWidth="214.68"
android:viewportHeight="214.68">
<path
android:fillAlpha="1"
android:fillColor="#fff"
android:pathData="M107.34,107.34m-98.88,0a98.88,98.88 0,1 1,197.76 0a98.88,98.88 0,1 1,-197.76 0" />
<path
android:fillAlpha="0.5"
android:fillColor="#fff"
android:pathData="M107.34,107.34m-107.34,0a107.34,107.34 0,1 1,214.68 0a107.34,107.34 0,1 1,-214.68 0"
android:strokeAlpha="0.5" />
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_take_video.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="@dimen/button_size_big"
android:height="@dimen/button_size_big"
android:viewportWidth="214.68"
android:viewportHeight="214.68">
<path
android:fillAlpha="0.5"
android:fillColor="#fff"
android:pathData="M107.34,107.34m-107.34,0a107.34,107.34 0,1 1,214.68 0a107.34,107.34 0,1 1,-214.68 0"
android:strokeAlpha="0.5" />
<path
android:fillColor="#cc3636"
android:pathData="M107.34,107.34m-98.88,0a98.88,98.88 0,1 1,197.76 0a98.88,98.88 0,1 1,-197.76 0" />
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_timer_10.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12.9,13.22C12.9,13.82 12.86,14.33 12.78,14.75C12.7,15.17 12.58,15.5 12.42,15.77C12.26,16.03 12.06,16.22 11.83,16.34C11.6,16.46 11.32,16.5 11,16.5C10.71,16.5 10.43,16.46 10.19,16.34C9.95,16.22 9.75,16.03 9.59,15.77C9.43,15.5 9.3,15.17 9.21,14.75C9.12,14.33 9.08,13.82 9.08,13.22V10.72C9.08,10.12 9.12,9.61 9.21,9.2C9.3,8.79 9.42,8.46 9.59,8.2C9.75,7.95 9.95,7.77 10.19,7.65C10.43,7.54 10.7,7.5 11,7.5C11.31,7.5 11.58,7.54 11.81,7.65C12.05,7.76 12.25,7.94 12.41,8.2C12.57,8.45 12.7,8.78 12.78,9.19C12.86,9.6 12.91,10.11 12.91,10.71V13.22M13.82,7.05C13.5,6.65 13.07,6.35 12.59,6.17C12.12,6 11.58,5.9 11,5.9C10.42,5.9 9.89,6 9.41,6.17C8.93,6.35 8.5,6.64 8.18,7.05C7.84,7.46 7.58,8 7.39,8.64C7.21,9.29 7.11,10.09 7.11,11.03V12.95C7.11,13.89 7.2,14.69 7.39,15.34C7.58,16 7.84,16.53 8.19,16.94C8.53,17.35 8.94,17.65 9.42,17.83C9.9,18 10.43,18.11 11,18.11C11.6,18.11 12.13,18 12.6,17.83C13.08,17.65 13.5,17.35 13.82,16.94C14.16,16.53 14.42,16 14.6,15.34C14.78,14.69 14.88,13.89 14.88,12.95V11.03C14.88,10.09 14.79,9.29 14.6,8.64C14.42,8 14.16,7.45 13.82,7.05M23.78,14.37C23.64,14.09 23.43,13.84 23.15,13.63C22.87,13.42 22.54,13.24 22.14,13.1C21.74,12.96 21.29,12.83 20.79,12.72C20.44,12.65 20.15,12.57 19.92,12.5C19.69,12.41 19.5,12.33 19.37,12.24C19.23,12.15 19.14,12.05 19.09,11.94C19.04,11.83 19,11.7 19,11.55C19,11.41 19.04,11.27 19.1,11.14C19.16,11 19.25,10.89 19.37,10.8C19.5,10.7 19.64,10.62 19.82,10.56C20,10.5 20.22,10.47 20.46,10.47C20.71,10.47 20.93,10.5 21.12,10.58C21.31,10.65 21.47,10.75 21.6,10.87C21.73,11 21.82,11.13 21.89,11.29C21.95,11.45 22,11.61 22,11.78H23.94C23.94,11.39 23.86,11.03 23.7,10.69C23.54,10.35 23.31,10.06 23,9.81C22.71,9.56 22.35,9.37 21.92,9.22C21.5,9.07 21,9 20.46,9C19.95,9 19.5,9.07 19.07,9.21C18.66,9.35 18.3,9.54 18,9.78C17.72,10 17.5,10.3 17.34,10.62C17.18,10.94 17.11,11.27 17.11,11.63C17.11,12 17.19,12.32 17.34,12.59C17.5,12.87 17.7,13.11 18,13.32C18.25,13.53 18.58,13.7 18.96,13.85C19.34,14 19.77,14.11 20.23,14.21C20.62,14.29 20.94,14.38 21.18,14.47C21.42,14.56 21.61,14.66 21.75,14.76C21.88,14.86 21.97,15 22,15.1C22.07,15.22 22.09,15.35 22.09,15.5C22.09,15.81 21.96,16.06 21.69,16.26C21.42,16.46 21.03,16.55 20.5,16.55C20.3,16.55 20.09,16.53 19.88,16.47C19.67,16.42 19.5,16.34 19.32,16.23C19.15,16.12 19,15.97 18.91,15.79C18.8,15.61 18.74,15.38 18.73,15.12H16.84C16.84,15.5 16.92,15.83 17.08,16.17C17.24,16.5 17.47,16.82 17.78,17.1C18.09,17.37 18.47,17.59 18.93,17.76C19.39,17.93 19.91,18 20.5,18C21.04,18 21.5,17.95 21.95,17.82C22.38,17.69 22.75,17.5 23.06,17.28C23.37,17.05 23.6,16.77 23.77,16.45C23.94,16.13 24,15.78 24,15.39C24,15 23.93,14.65 23.78,14.37M0,7.72V9.4L3,8.4V18H5V6H4.75L0,7.72Z" />
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_timer_3.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M20.87,14.37C20.73,14.09 20.5,13.84 20.24,13.63C19.96,13.42 19.63,13.24 19.23,13.1C18.83,12.96 18.38,12.83 17.88,12.72C17.53,12.65 17.24,12.57 17,12.5C16.78,12.41 16.6,12.33 16.46,12.24C16.32,12.15 16.23,12.05 16.18,11.94C16.13,11.83 16.1,11.7 16.1,11.55C16.1,11.4 16.13,11.27 16.19,11.14C16.25,11 16.34,10.89 16.46,10.8C16.58,10.7 16.73,10.62 16.91,10.56C17.09,10.5 17.31,10.47 17.55,10.47C17.8,10.47 18,10.5 18.21,10.58C18.4,10.65 18.56,10.75 18.69,10.87C18.82,11 18.91,11.13 19,11.29C19.04,11.45 19.08,11.61 19.08,11.78H21.03C21.03,11.39 20.95,11.03 20.79,10.69C20.63,10.35 20.4,10.06 20.1,9.81C19.8,9.56 19.44,9.37 19,9.22C18.58,9.07 18.09,9 17.55,9C17.04,9 16.57,9.07 16.16,9.21C15.75,9.35 15.39,9.54 15.1,9.78C14.81,10 14.59,10.3 14.43,10.62C14.27,10.94 14.2,11.27 14.2,11.63C14.2,12 14.28,12.31 14.43,12.59C14.58,12.87 14.8,13.11 15.07,13.32C15.34,13.53 15.67,13.7 16.05,13.85C16.43,14 16.86,14.11 17.32,14.21C17.71,14.29 18.03,14.38 18.27,14.47C18.5,14.56 18.7,14.66 18.84,14.76C18.97,14.86 19.06,15 19.11,15.1C19.16,15.22 19.18,15.35 19.18,15.5C19.18,15.81 19.05,16.06 18.78,16.26C18.5,16.46 18.12,16.55 17.61,16.55C17.39,16.55 17.18,16.53 16.97,16.47C16.76,16.42 16.57,16.34 16.41,16.23C16.24,16.12 16.11,15.97 16,15.79C15.89,15.61 15.83,15.38 15.82,15.12H13.93C13.93,15.5 14,15.83 14.17,16.17C14.33,16.5 14.56,16.82 14.87,17.1C15.18,17.37 15.56,17.59 16,17.76C16.5,17.93 17,18 17.6,18C18.13,18 18.61,17.95 19.04,17.82C19.47,17.69 19.84,17.5 20.15,17.28C20.46,17.05 20.69,16.77 20.86,16.45C21.03,16.13 21.11,15.78 21.11,15.39C21.09,15 21,14.65 20.87,14.37M11.61,12.97C11.45,12.73 11.25,12.5 11,12.32C10.74,12.13 10.43,11.97 10.06,11.84C10.36,11.7 10.63,11.54 10.86,11.34C11.09,11.14 11.28,10.93 11.43,10.7C11.58,10.47 11.7,10.24 11.77,10C11.85,9.75 11.88,9.5 11.88,9.26C11.88,8.71 11.79,8.22 11.6,7.8C11.42,7.38 11.16,7.03 10.82,6.74C10.5,6.46 10.09,6.24 9.62,6.1C9.17,5.97 8.65,5.9 8.09,5.9C7.54,5.9 7.03,6 6.57,6.14C6.1,6.31 5.7,6.54 5.37,6.83C5.04,7.12 4.77,7.46 4.59,7.86C4.39,8.25 4.3,8.69 4.3,9.15H6.28C6.28,8.89 6.33,8.66 6.42,8.46C6.5,8.26 6.64,8.08 6.8,7.94C6.97,7.8 7.16,7.69 7.38,7.61C7.6,7.53 7.84,7.5 8.11,7.5C8.72,7.5 9.17,7.65 9.47,7.96C9.77,8.27 9.91,8.71 9.91,9.28C9.91,9.55 9.87,9.8 9.79,10C9.71,10.24 9.58,10.43 9.41,10.59C9.24,10.75 9.03,10.87 8.78,10.96C8.53,11.05 8.23,11.09 7.89,11.09H6.72V12.66H7.9C8.24,12.66 8.54,12.7 8.81,12.77C9.08,12.85 9.31,12.96 9.5,13.12C9.69,13.28 9.84,13.5 9.94,13.73C10.04,13.97 10.1,14.27 10.1,14.6C10.1,15.22 9.92,15.69 9.57,16C9.22,16.35 8.73,16.5 8.12,16.5C7.83,16.5 7.56,16.47 7.32,16.38C7.08,16.3 6.88,16.18 6.71,16C6.54,15.86 6.41,15.68 6.32,15.46C6.23,15.24 6.18,15 6.18,14.74H4.19C4.19,15.29 4.3,15.77 4.5,16.19C4.72,16.61 5,16.96 5.37,17.24C5.73,17.5 6.14,17.73 6.61,17.87C7.08,18 7.57,18.08 8.09,18.08C8.66,18.08 9.18,18 9.67,17.85C10.16,17.7 10.58,17.47 10.93,17.17C11.29,16.87 11.57,16.5 11.77,16.07C11.97,15.64 12.07,15.14 12.07,14.59C12.07,14.3 12.03,14 11.96,13.73C11.88,13.5 11.77,13.22 11.61,12.97Z" />
</vector>
================================================
FILE: camerax-demo/app/src/main/res/drawable/ic_timer_off.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,20A7,7 0 0,1 5,13C5,11.72 5.35,10.5 5.95,9.5L15.5,19.04C14.5,19.65 13.28,20 12,20M3,4L1.75,5.27L4.5,8.03C3.55,9.45 3,11.16 3,13A9,9 0 0,0 12,22C13.84,22 15.55,21.45 17,20.5L19.5,23L20.75,21.73L13.04,14L3,4M11,9.44L13,11.44V8H11M15,1H9V3H15M19.04,4.55L17.62,5.97C16.07,4.74 14.12,4 12,4C10.17,4 8.47,4.55 7.05,5.5L8.5,6.94C9.53,6.35 10.73,6 12,6A7,7 0 0,1 19,13C19,14.27 18.65,15.47 18.06,16.5L19.5,17.94C20.45,16.53 21,14.83 21,13C21,10.88 20.26,8.93 19.03,7.39L20.45,5.97L19.04,4.55Z" />
</vector>
================================================
FILE: camerax-demo/app/src/main/res/layout/activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentNavHost"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
</FrameLayout>
================================================
FILE: camerax-demo/app/src/main/res/layout/fragment_camera.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:background="@android:color/black"
tools:context=".fragments.CameraFragment">
<androidx.camera.view.PreviewView
android:id="@+id/viewFinder"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0.4"
android:background="@android:color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/btnTakePicture" />
<ImageButton
android:id="@+id/btnTakePicture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:padding="@dimen/double_margin"
android:src="@drawable/ic_take_picture"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/btnGallery"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@android:color/transparent"
android:scaleType="centerCrop"
android:src="@drawable/ic_no_picture"
app:layout_constraintBottom_toBottomOf="@id/btnTakePicture"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/btnTakePicture"
app:layout_constraintTop_toTopOf="@id/btnTakePicture"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/btnSwitchCamera"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@drawable/bg_button_round"
android:padding="@dimen/half_margin"
android:scaleType="centerInside"
android:src="@drawable/ic_outline_camera_front"
app:layout_constraintBottom_toBottomOf="@id/btnTakePicture"
app:layout_constraintEnd_toStartOf="@id/btnTakePicture"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/btnTakePicture"
tools:ignore="ContentDescription" />
<View
android:id="@+id/viewBg2"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0.4"
android:background="@android:color/black"
android:elevation="1dp"
app:layout_constraintBottom_toBottomOf="@id/btnFlash"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/btnTimer"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@android:color/transparent"
android:elevation="1dp"
android:padding="@dimen/half_margin"
android:scaleType="centerInside"
android:src="@drawable/ic_timer_off"
app:layout_constraintEnd_toStartOf="@+id/btnGrid"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/btnGrid"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@android:color/transparent"
android:elevation="1dp"
android:padding="@dimen/half_margin"
android:scaleType="centerInside"
android:src="@drawable/ic_grid_off"
app:layout_constraintEnd_toStartOf="@+id/btnFlash"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/btnTimer"
app:layout_constraintTop_toTopOf="@id/btnTimer"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/btnFlash"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@android:color/transparent"
android:elevation="1dp"
android:padding="@dimen/half_margin"
android:scaleType="centerInside"
android:src="@drawable/ic_flash_off"
app:layout_constraintEnd_toStartOf="@+id/btnHdr"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/btnGrid"
app:layout_constraintTop_toTopOf="@id/btnGrid"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/btnHdr"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@android:color/transparent"
android:elevation="1dp"
android:padding="@dimen/half_margin"
android:scaleType="centerInside"
android:src="@drawable/ic_hdr_off"
app:layout_constraintEnd_toStartOf="@id/btnExposure"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/btnFlash"
app:layout_constraintTop_toTopOf="@id/btnFlash"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/btnExposure"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@android:color/transparent"
android:elevation="1dp"
android:padding="@dimen/half_margin"
android:scaleType="centerInside"
android:src="@drawable/ic_exposure"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/btnHdr"
app:layout_constraintTop_toTopOf="@id/btnFlash"
tools:ignore="ContentDescription" />
<View
android:id="@+id/gridVertical1"
android:layout_width="1dp"
android:layout_height="0dp"
android:alpha="0.5"
android:background="@android:color/white"
android:elevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.33"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/gridVertical2"
android:layout_width="1dp"
android:layout_height="0dp"
android:alpha="0.5"
android:background="@android:color/white"
android:elevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.66"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/gridHorizontal1"
android:layout_width="0dp"
android:layout_height="1dp"
android:alpha="0.5"
android:background="@android:color/white"
android:elevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.33" />
<View
android:id="@+id/gridHorizontal2"
android:layout_width="0dp"
android:layout_height="1dp"
android:alpha="0.5"
android:background="@android:color/white"
android:elevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.66" />
<androidx.constraintlayout.widget.Group
android:id="@+id/groupGridLines"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="gridVertical1,gridVertical2,gridHorizontal1,gridHorizontal2" />
<LinearLayout
android:id="@+id/llTimerOptions"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@android:color/black"
android:elevation="3dp"
android:orientation="horizontal"
android:paddingStart="@dimen/fab_margin"
android:paddingTop="@dimen/double_margin"
android:paddingEnd="@dimen/fab_margin"
android:paddingBottom="@dimen/fab_margin"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageButton
android:id="@+id/btnTimerOff"
android:layout_width="0dp"
android:layout_height="@dimen/button_size_small"
android:layout_weight="1"
android:background="@android:color/transparent"
android:src="@drawable/ic_timer_off"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/btnTimer3"
android:layout_width="0dp"
android:layout_height="@dimen/button_size_small"
android:layout_weight="1"
android:background="@android:color/transparent"
android:src="@drawable/ic_timer_3"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/btnTimer10"
android:layout_width="0dp"
android:layout_height="@dimen/button_size_small"
android:layout_weight="1"
android:background="@android:color/transparent"
android:src="@drawable/ic_timer_10"
tools:ignore="ContentDescription" />
</LinearLayout>
<LinearLayout
android:id="@+id/llFlashOptions"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@android:color/black"
android:elevation="3dp"
android:orientation="horizontal"
android:paddingStart="@dimen/fab_margin"
android:paddingTop="@dimen/double_margin"
android:paddingEnd="@dimen/fab_margin"
android:paddingBottom="@dimen/fab_margin"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageButton
android:id="@+id/btnFlashOff"
android:layout_width="0dp"
android:layout_height="@dimen/button_size_small"
android:layout_weight="1"
android:background="@android:color/transparent"
android:src="@drawable/ic_flash_off"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/btnFlashAuto"
android:layout_width="0dp"
android:layout_height="@dimen/button_size_small"
android:layout_weight="1"
android:background="@android:color/transparent"
android:src="@drawable/ic_flash_auto"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/btnFlashOn"
android:layout_width="0dp"
android:layout_height="@dimen/button_size_small"
android:layout_weight="1"
android:background="@android:color/transparent"
android:src="@drawable/ic_flash_on"
tools:ignore="ContentDescription" />
</LinearLayout>
<FrameLayout
android:id="@+id/flExposure"
android:layout_width="0dp"
android:layout_height="0dp"
android:elevation="3dp"
android:orientation="horizontal"
android:padding="@dimen/double_margin"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.slider.Slider
android:id="@+id/sliderExposure"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="@dimen/fab_margin"
android:background="@drawable/bg_options"
android:paddingHorizontal="@dimen/fab_margin"
android:paddingVertical="@dimen/double_margin"
android:progressBackgroundTint="@android:color/white"
app:haloColor="@color/colorAccent"
app:thumbColor="@color/colorAccent" />
</FrameLayout>
<TextView
android:id="@+id/tvCountDown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="4dp"
android:textColor="@android:color/white"
android:textSize="@dimen/count_down_text_size"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="0" />
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: camerax-demo/app/src/main/res/layout/fragment_preview.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<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/layoutRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
tools:context=".fragments.PreviewFragment">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pagerPhotos"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/bgTop"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0.4"
android:background="@android:color/black"
app:layout_constraintBottom_toBottomOf="@id/btnBack"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/bgBottom"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0.4"
android:background="@android:color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/btnShare" />
<ImageButton
android:id="@+id/btnBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:fitsSystemWindows="false"
android:padding="@dimen/fab_margin"
android:src="@drawable/ic_arrow_back"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/btnShare"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:fitsSystemWindows="false"
android:padding="@dimen/fab_margin"
android:src="@drawable/ic_share"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.3"
app:layout_constraintStart_toStartOf="parent" />
<ImageButton
android:id="@+id/btnDelete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackgroundBorderless"
android:fitsSystemWindows="false"
android:padding="@dimen/fab_margin"
android:src="@drawable/ic_delete"
app:layout_constraintBottom_toBottomOf="@id/btnShare"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.7"
app:layout_constraintStart_toStartOf="parent" />
<androidx.constraintlayout.widget.Group
android:id="@+id/groupPreviewActions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="bgTop,bgBottom,btnBack,btnShare,btnDelete" />
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: camerax-demo/app/src/main/res/layout/fragment_video.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
tools:context=".fragments.VideoFragment">
<androidx.camera.view.PreviewView
android:id="@+id/viewFinder"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0.4"
android:background="@android:color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/btnRecordVideo" />
<ImageButton
android:id="@+id/btnRecordVideo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:padding="@dimen/double_margin"
android:src="@drawable/ic_take_video"
app:backgroundTint="@android:color/transparent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageButton
android:id="@+id/btnGallery"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@android:color/transparent"
android:elevation="1dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_no_picture"
app:layout_constraintBottom_toBottomOf="@id/btnRecordVideo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/btnRecordVideo"
app:layout_constraintTop_toTopOf="@id/btnRecordVideo" />
<ImageButton
android:id="@+id/btnSwitchCamera"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@drawable/bg_button_round"
android:elevation="1dp"
android:padding="@dimen/half_margin"
android:scaleType="centerInside"
android:src="@drawable/ic_outline_camera_front"
app:layout_constraintBottom_toBottomOf="@id/btnRecordVideo"
app:layout_constraintEnd_toStartOf="@id/btnRecordVideo"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/btnRecordVideo" />
<View
android:id="@+id/viewBg2"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0.4"
android:background="@android:color/black"
android:elevation="1dp"
app:layout_constraintBottom_toBottomOf="@id/btnFlash"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/btnGrid"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@android:color/transparent"
android:elevation="1dp"
android:padding="@dimen/half_margin"
android:scaleType="centerInside"
android:src="@drawable/ic_grid_off"
app:layout_constraintBottom_toBottomOf="@id/btnFlash"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/btnFlash" />
<ImageButton
android:id="@+id/btnFlash"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@android:color/transparent"
android:elevation="1dp"
android:padding="@dimen/half_margin"
android:scaleType="centerInside"
android:src="@drawable/ic_flash_off"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.7"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/gridVertical1"
android:layout_width="1dp"
android:layout_height="0dp"
android:alpha="0.5"
android:background="@android:color/white"
android:elevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.33"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/gridVertical2"
android:layout_width="1dp"
android:layout_height="0dp"
android:alpha="0.5"
android:background="@android:color/white"
android:elevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.66"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/gridHorizontal1"
android:layout_width="0dp"
android:layout_height="1dp"
android:alpha="0.5"
android:background="@android:color/white"
android:elevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.33" />
<View
android:id="@+id/gridHorizontal2"
android:layout_width="0dp"
android:layout_height="1dp"
android:alpha="0.5"
android:background="@android:color/white"
android:elevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.66" />
<androidx.constraintlayout.widget.Group
android:id="@+id/groupGridLines"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="gridVertical1,gridVertical2,gridHorizontal1,gridHorizontal2" />
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: camerax-demo/app/src/main/res/layout/item_picture.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imagePreview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitCenter" />
<ImageView
android:id="@+id/imagePlay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="@dimen/fab_margin"
android:src="@drawable/ic_play" />
</FrameLayout>
================================================
FILE: camerax-demo/app/src/main/res/layout-land/fragment_camera.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:background="@android:color/black"
tools:context=".fragments.CameraFragment">
<androidx.camera.view.PreviewView
android:id="@+id/viewFinder"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0.4"
android:background="@android:color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/btnTakePicture"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/btnTakePicture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:padding="@dimen/double_margin"
android:src="@drawable/ic_take_picture"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/btnGallery"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@android:color/transparent"
android:scaleType="centerCrop"
android:src="@drawable/ic_no_picture"
app:layout_constraintBottom_toTopOf="@id/btnTakePicture"
app:layout_constraintEnd_toEndOf="@id/btnTakePicture"
app:layout_constraintStart_toStartOf="@id/btnTakePicture"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/btnSwitchCamera"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@drawable/bg_button_round"
android:padding="@dimen/half_margin"
android:scaleType="centerInside"
android:src="@drawable/ic_outline_camera_front"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/btnTakePicture"
app:layout_constraintStart_toStartOf="@id/btnTakePicture"
app:layout_constraintTop_toBottomOf="@id/btnTakePicture"
tools:ignore="ContentDescription" />
<View
android:id="@+id/viewBg2"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0.4"
android:background="@android:color/black"
android:elevation="1dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/btnFlash"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/btnTimer"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@android:color/transparent"
android:elevation="1dp"
android:padding="@dimen/half_margin"
android:scaleType="centerInside"
android:src="@drawable/ic_timer_off"
app:layout_constraintBottom_toTopOf="@+id/btnGrid"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/btnGrid"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@android:color/transparent"
android:elevation="1dp"
android:padding="@dimen/half_margin"
android:scaleType="centerInside"
android:src="@drawable/ic_grid_off"
app:layout_constraintBottom_toTopOf="@+id/btnFlash"
app:layout_constraintStart_toStartOf="@id/btnTimer"
app:layout_constraintTop_toBottomOf="@+id/btnTimer"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/btnFlash"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@android:color/transparent"
android:elevation="1dp"
android:padding="@dimen/half_margin"
android:scaleType="centerInside"
android:src="@drawable/ic_flash_off"
app:layout_constraintBottom_toTopOf="@+id/btnHdr"
app:layout_constraintStart_toStartOf="@id/btnGrid"
app:layout_constraintTop_toBottomOf="@+id/btnGrid"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/btnHdr"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@android:color/transparent"
android:elevation="1dp"
android:padding="@dimen/half_margin"
android:scaleType="centerInside"
android:src="@drawable/ic_hdr_off"
app:layout_constraintBottom_toTopOf="@id/btnExposure"
app:layout_constraintStart_toStartOf="@id/btnFlash"
app:layout_constraintTop_toBottomOf="@+id/btnFlash"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/btnExposure"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@android:color/transparent"
android:elevation="1dp"
android:padding="@dimen/half_margin"
android:scaleType="centerInside"
android:src="@drawable/ic_exposure"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/btnFlash"
app:layout_constraintTop_toBottomOf="@+id/btnHdr"
tools:ignore="ContentDescription" />
<View
android:id="@+id/gridVertical1"
android:layout_width="1dp"
android:layout_height="0dp"
android:alpha="0.5"
android:background="@android:color/white"
android:elevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.33"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/gridVertical2"
android:layout_width="1dp"
android:layout_height="0dp"
android:alpha="0.5"
android:background="@android:color/white"
android:elevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.66"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/gridHorizontal1"
android:layout_width="0dp"
android:layout_height="1dp"
android:alpha="0.5"
android:background="@android:color/white"
android:elevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.33" />
<View
android:id="@+id/gridHorizontal2"
android:layout_width="0dp"
android:layout_height="1dp"
android:alpha="0.5"
android:background="@android:color/white"
android:elevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.66" />
<androidx.constraintlayout.widget.Group
android:id="@+id/groupGridLines"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
app:constraint_referenced_ids="gridVertical1,gridVertical2,gridHorizontal1,gridHorizontal2" />
<LinearLayout
android:id="@+id/llTimerOptions"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:background="@android:color/black"
android:elevation="3dp"
android:orientation="vertical"
android:paddingStart="@dimen/fab_margin"
android:paddingTop="@dimen/double_margin"
android:paddingEnd="@dimen/fab_margin"
android:paddingBottom="@dimen/fab_margin"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageButton
android:id="@+id/btnTimerOff"
android:layout_width="@dimen/button_size_small"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@android:color/transparent"
android:src="@drawable/ic_timer_off"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/btnTimer3"
android:layout_width="@dimen/button_size_small"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@android:color/transparent"
android:src="@drawable/ic_timer_3"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/btnTimer10"
android:layout_width="@dimen/button_size_small"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@android:color/transparent"
android:src="@drawable/ic_timer_10"
tools:ignore="ContentDescription" />
</LinearLayout>
<LinearLayout
android:id="@+id/llFlashOptions"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:background="@android:color/black"
android:elevation="3dp"
android:orientation="vertical"
android:paddingStart="@dimen/fab_margin"
android:paddingTop="@dimen/double_margin"
android:paddingEnd="@dimen/fab_margin"
android:paddingBottom="@dimen/fab_margin"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageButton
android:id="@+id/btnFlashOff"
android:layout_width="@dimen/button_size_small"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@android:color/transparent"
android:src="@drawable/ic_flash_off"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/btnFlashAuto"
android:layout_width="@dimen/button_size_small"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@android:color/transparent"
android:src="@drawable/ic_flash_auto"
tools:ignore="ContentDescription" />
<ImageButton
android:id="@+id/btnFlashOn"
android:layout_width="@dimen/button_size_small"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@android:color/transparent"
android:src="@drawable/ic_flash_on"
tools:ignore="ContentDescription" />
</LinearLayout>
<FrameLayout
android:id="@+id/flExposure"
android:layout_width="0dp"
android:layout_height="0dp"
android:elevation="3dp"
android:orientation="horizontal"
android:padding="@dimen/double_margin"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.slider.Slider
android:id="@+id/sliderExposure"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="@dimen/double_margin"
android:background="@drawable/bg_options"
android:paddingHorizontal="@dimen/fab_margin"
android:paddingVertical="@dimen/double_margin"
android:progressBackgroundTint="@android:color/white"
app:haloColor="@color/colorAccent"
app:thumbColor="@color/colorAccent" />
</FrameLayout>
<TextView
android:id="@+id/tvCountDown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="4dp"
android:textColor="@android:color/white"
android:textSize="@dimen/count_down_text_size"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="0" />
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: camerax-demo/app/src/main/res/layout-land/fragment_video.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
tools:context=".fragments.CameraFragment">
<androidx.camera.view.PreviewView
android:id="@+id/viewFinder"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0.4"
android:background="@android:color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/btnRecordVideo"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/btnRecordVideo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:padding="@dimen/double_margin"
android:src="@drawable/ic_take_video"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/btnGallery"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@android:color/transparent"
android:elevation="1dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_no_picture"
app:layout_constraintBottom_toTopOf="@id/btnRecordVideo"
app:layout_constraintEnd_toEndOf="@id/btnRecordVideo"
app:layout_constraintStart_toStartOf="@id/btnRecordVideo"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/btnSwitchCamera"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@drawable/bg_button_round"
android:elevation="1dp"
android:padding="@dimen/half_margin"
android:scaleType="centerInside"
android:src="@drawable/ic_outline_camera_front"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/btnRecordVideo"
app:layout_constraintStart_toStartOf="@id/btnRecordVideo"
app:layout_constraintTop_toBottomOf="@id/btnRecordVideo" />
<View
android:id="@+id/viewBg2"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0.4"
android:background="@android:color/black"
android:elevation="1dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/btnFlash"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageButton
android:id="@+id/btnGrid"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@android:color/transparent"
android:elevation="1dp"
android:padding="@dimen/half_margin"
android:scaleType="centerInside"
android:src="@drawable/ic_grid_off"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/btnFlash"
app:layout_constraintStart_toStartOf="@id/btnFlash"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.3" />
<ImageButton
android:id="@+id/btnFlash"
android:layout_width="@dimen/button_size_small"
android:layout_height="@dimen/button_size_small"
android:background="@android:color/transparent"
android:elevation="1dp"
android:padding="@dimen/half_margin"
android:scaleType="centerInside"
android:src="@drawable/ic_flash_off"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.7" />
<View
android:id="@+id/gridVertical1"
android:layout_width="1dp"
android:layout_height="0dp"
android:alpha="0.5"
android:background="@android:color/white"
android:elevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.33"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/gridVertical2"
android:layout_width="1dp"
android:layout_height="0dp"
android:alpha="0.5"
android:background="@android:color/white"
android:elevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.66"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/gridHorizontal1"
android:layout_width="0dp"
android:layout_height="1dp"
android:alpha="0.5"
android:background="@android:color/white"
android:elevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.33" />
<View
android:id="@+id/gridHorizontal2"
android:layout_width="0dp"
android:layout_height="1dp"
android:alpha="0.5"
android:background="@android:color/white"
android:elevation="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.66" />
<androidx.constraintlayout.widget.Group
android:id="@+id/groupGridLines"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="gridVertical1,gridVertical2,gridHorizontal1,gridHorizontal2" />
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: camerax-demo/app/src/main/res/menu/menu_main.xml
================================================
<menu 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"
tools:context="com.robertlevonyan.demo.camerax.MainActivity">
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:orderInCategory="100"
app:showAsAction="never"/>
</menu>
================================================
FILE: camerax-demo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
================================================
FILE: camerax-demo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
================================================
FILE: camerax-demo/app/src/main/res/navigation/nav_graph.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<navigation 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/nav_host"
app:startDestination="@id/cameraFragment">
<fragment
android:id="@+id/cameraFragment"
android:name="com.robertlevonyan.demo.camerax.fragments.CameraFragment"
android:label="fragment_camera"
tools:layout="@layout/fragment_camera" >
<action
android:id="@+id/action_camera_to_preview"
app:destination="@id/previewFragment"
app:enterAnim="@anim/slide_in"
app:exitAnim="@anim/slide_out"
app:popEnterAnim="@anim/slide_in_pop"
app:popExitAnim="@anim/slide_out_pop"
app:popUpTo="@+id/cameraFragment" />
<action
android:id="@+id/action_camera_to_video"
app:destination="@id/videoFragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:launchSingleTop="true"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim"
app:popUpTo="@+id/nav_host"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="@+id/previewFragment"
android:name="com.robertlevonyan.demo.camerax.fragments.PreviewFragment"
android:label="fragment_preview"
tools:layout="@layout/fragment_preview" />
<fragment
android:id="@+id/videoFragment"
android:name="com.robertlevonyan.demo.camerax.fragments.VideoFragment"
android:label="fragment_video"
tools:layout="@layout/fragment_video" >
<action
android:id="@+id/action_video_to_preview"
app:destination="@id/previewFragment"
app:popUpTo="@+id/videoFragment" />
<action
android:id="@+id/action_video_to_camera"
app:destination="@id/cameraFragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:launchSingleTop="true"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim"
app:popUpTo="@+id/nav_host"
app:popUpToInclusive="true" />
</fragment>
</navigation>
================================================
FILE: camerax-demo/app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#1a1a18</color>
<color name="colorPrimaryDark">#80000000</color>
<color name="colorAccent">#e7a942</color>
<color name="colorButtonGreyBg">#444444</color>
</resources>
================================================
FILE: camerax-demo/app/src/main/res/values/dimens.xml
================================================
<resources>
<dimen name="half_margin">8dp</dimen>
<dimen name="fab_margin">16dp</dimen>
<dimen name="medium_margin">24dp</dimen>
<dimen name="double_margin">32dp</dimen>
<dimen name="button_size_shutter">100dp</dimen>
<dimen name="button_size_big">56dp</dimen>
<dimen name="button_size_small">36dp</dimen>
<dimen name="count_down_text_size">72sp</dimen>
</resources>
================================================
FILE: camerax-demo/app/src/main/res/values/ic_launcher_background.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#F1F1F1</color>
</resources>
================================================
FILE: camerax-demo/app/src/main/res/values/strings.xml
================================================
<resources>
<string name="app_name">CameraX Demo</string>
<string name="action_settings">Settings</string>
<string name="message_no_permissions">The app cannot work without permissions</string>
<string name="label_ok">OK</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
<string name="cd_take_picture">Take picture</string>
<string name="cb_open_gallery">Navigate to Gallery</string>
<string name="cb_switch">Switch between front and back cameras</string>
<string name="cb_timer">Select a timer</string>
</resources>
================================================
FILE: camerax-demo/app/src/main/res/values/styles.xml
================================================
<resources>
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:navigationBarColor">@color/colorPrimaryDark</item>
</style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.MaterialComponents.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.MaterialComponents.Dark" />
</resources>
================================================
FILE: camerax-demo/app/src/main/res/xml/provider_paths.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="external_files" path="."/>
</paths>
================================================
FILE: camerax-demo/build.gradle.kts
================================================
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.android.nav.safeargs) apply false
}
tasks.register("clean", Delete::class) {
delete(layout.buildDirectory)
}
================================================
FILE: camerax-demo/gradle/libs.versions.toml
================================================
[versions]
androidGradlePlugin = "8.9.1"
androidxComposeBom = "2025.04.00"
activityKtx = "1.10.1"
appcompat = "1.7.0"
camera = "1.4.2"
coil = "2.7.0"
coilVideo = "2.4.0"
constraintlayoutVersion = "2.2.1"
fragmentKtx = "1.8.6"
ksp = "2.1.20-2.0.0"
kotlin = "2.1.20"
kotlinxCoroutines = "1.10.2"
material = "1.12.0"
coreKtx = "1.16.0"
multidex = "2.0.1"
composePaging = "3.3.6"
composeCoil = "2.7.0"
lifecycle = "2.8.7"
navigation = "2.8.9"
viewpager2 = "1.1.0"
[libraries]
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camera" }
androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "camera" }
androidx-camera-extensions = { module = "androidx.camera:camera-extensions", version.ref = "camera" }
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camera" }
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "camera" }
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayoutVersion" }
androidx-fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentKtx" }
androidx-navigation-fragment-ktx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigation" }
androidx-navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigation" }
androidx-viewpager2 = { module = "androidx.viewpager2:viewpager2", version.ref = "viewpager2" }
coil = { module = "io.coil-kt:coil", version.ref = "coil" }
coil-video = { module = "io.coil-kt:coil-video", version.ref = "coilVideo" }
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
firebase-analytics-ktx = { group = "com.google.firebase", name = "firebase-analytics-ktx" }
firebase-crashlytics-ktx = { group = "com.google.firebase", name = "firebase-crashlytics-ktx" }
firebase-firestore-ktx = { group = "com.google.firebase", name = "firebase-firestore-ktx" }
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
multidex = { group = "androidx.multidex", name = "multidex", version.ref = "multidex" }
lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-compose-material3-windowsize = { group = "androidx.compose.material3", name = "material3-window-size-class" }
androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityKtx" }
compose-paging = { group = "androidx.paging", name = "paging-compose", version.ref = "composePaging" }
compose-coil = { group = "io.coil-kt", name = "coil-compose", version.ref = "composeCoil" }
[bundles]
firebase = ["firebase-analytics-ktx", "firebase-crashlytics-ktx", "firebase-firestore-ktx"]
compose = ["androidx-compose-material3", "androidx-compose-material3-windowsize", "androidx-compose-foundation", "androidx-compose-ui", "androidx-compose-ui-tooling"]
[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
android-nav-safeargs = { id = "androidx.navigation.safeargs.kotlin", version.ref = "navigation" }
================================================
FILE: camerax-demo/gradle/wrapper/gradle-wrapper.properties
================================================
#Fri Oct 27 14:13:16 AMT 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
================================================
FILE: camerax-demo/gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false
================================================
FILE: camerax-demo/settings.gradle
================================================
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "camerax-demo"
include ':app'
gitextract_1un5hc0l/
├── .gitignore
├── LICENSE
├── README.md
└── camerax-demo/
├── .gitignore
├── .idea/
│ ├── .gitignore
│ └── codeStyles/
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── app/
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── kotlin/
│ │ └── com/
│ │ └── robertlevonyan/
│ │ └── demo/
│ │ └── camerax/
│ │ ├── CameraXApplication.kt
│ │ ├── MainActivity.kt
│ │ ├── adapter/
│ │ │ ├── Media.kt
│ │ │ ├── MediaAdapter.kt
│ │ │ └── MediaDiffCallback.kt
│ │ ├── analyzer/
│ │ │ └── LuminosityAnalyzer.kt
│ │ ├── enums/
│ │ │ └── CameraTimer.kt
│ │ ├── fragments/
│ │ │ ├── BaseFragment.kt
│ │ │ ├── CameraFragment.kt
│ │ │ ├── PreviewFragment.kt
│ │ │ └── VideoFragment.kt
│ │ └── utils/
│ │ ├── Extensions.kt
│ │ ├── MainExecutor.kt
│ │ ├── SharedPrefsManager.kt
│ │ ├── SwipeGestureDetector.kt
│ │ └── ThreadExecutor.kt
│ └── res/
│ ├── anim/
│ │ ├── slide_in.xml
│ │ ├── slide_in_pop.xml
│ │ ├── slide_out.xml
│ │ └── slide_out_pop.xml
│ ├── drawable/
│ │ ├── bg_button_round.xml
│ │ ├── bg_options.xml
│ │ ├── ic_arrow_back.xml
│ │ ├── ic_delete.xml
│ │ ├── ic_edit.xml
│ │ ├── ic_exposure.xml
│ │ ├── ic_flash_auto.xml
│ │ ├── ic_flash_off.xml
│ │ ├── ic_flash_on.xml
│ │ ├── ic_grid_off.xml
│ │ ├── ic_grid_on.xml
│ │ ├── ic_hdr_off.xml
│ │ ├── ic_hdr_on.xml
│ │ ├── ic_launcher_foreground.xml
│ │ ├── ic_no_picture.xml
│ │ ├── ic_outline_camera_enhance.xml
│ │ ├── ic_outline_camera_front.xml
│ │ ├── ic_outline_camera_rear.xml
│ │ ├── ic_play.xml
│ │ ├── ic_share.xml
│ │ ├── ic_take_picture.xml
│ │ ├── ic_take_video.xml
│ │ ├── ic_timer_10.xml
│ │ ├── ic_timer_3.xml
│ │ └── ic_timer_off.xml
│ ├── layout/
│ │ ├── activity_main.xml
│ │ ├── fragment_camera.xml
│ │ ├── fragment_preview.xml
│ │ ├── fragment_video.xml
│ │ └── item_picture.xml
│ ├── layout-land/
│ │ ├── fragment_camera.xml
│ │ └── fragment_video.xml
│ ├── menu/
│ │ └── menu_main.xml
│ ├── mipmap-anydpi-v26/
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ ├── navigation/
│ │ └── nav_graph.xml
│ ├── values/
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── xml/
│ └── provider_paths.xml
├── build.gradle.kts
├── gradle/
│ ├── libs.versions.toml
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
└── settings.gradle
Condensed preview — 79 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (173K chars).
[
{
"path": ".gitignore",
"chars": 1002,
"preview": "# Built application files\n*.apk\n*.ap_\n\n# Files for the ART/Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated file"
},
{
"path": "LICENSE",
"chars": 1063,
"preview": "MIT License\n\nCopyright (c) 2019 Robert\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
},
{
"path": "README.md",
"chars": 3340,
"preview": "# <img src=\"https://github.com/robertlevonyan/camerax-demo/blob/master/camerax-demo/app/src/main/ic_launcher-web.png\" w"
},
{
"path": "camerax-demo/.gitignore",
"chars": 366,
"preview": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor."
},
{
"path": "camerax-demo/.idea/.gitignore",
"chars": 47,
"preview": "# Default ignored files\n/shelf/\n/workspace.xml\n"
},
{
"path": "camerax-demo/.idea/codeStyles/Project.xml",
"chars": 3669,
"preview": "<component name=\"ProjectCodeStyleConfiguration\">\n <code_scheme name=\"Project\" version=\"173\">\n <option name=\"RIGHT_MA"
},
{
"path": "camerax-demo/.idea/codeStyles/codeStyleConfig.xml",
"chars": 142,
"preview": "<component name=\"ProjectCodeStyleConfiguration\">\n <state>\n <option name=\"USE_PER_PROJECT_SETTINGS\" value=\"true\" />\n "
},
{
"path": "camerax-demo/app/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "camerax-demo/app/build.gradle.kts",
"chars": 1762,
"preview": "plugins {\n alias(libs.plugins.android.application)\n alias(libs.plugins.kotlin.android)\n alias(libs.plugins.andr"
},
{
"path": "camerax-demo/app/proguard-rules.pro",
"chars": 755,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "camerax-demo/app/src/main/AndroidManifest.xml",
"chars": 1831,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:to"
},
{
"path": "camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/CameraXApplication.kt",
"chars": 398,
"preview": "package com.robertlevonyan.demo.camerax\n\nimport android.app.Application\nimport coil.ImageLoader\nimport coil.ImageLoaderF"
},
{
"path": "camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/MainActivity.kt",
"chars": 313,
"preview": "package com.robertlevonyan.demo.camerax\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\n\nclass"
},
{
"path": "camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/adapter/Media.kt",
"chars": 176,
"preview": "package com.robertlevonyan.demo.camerax.adapter\n\nimport android.net.Uri\nimport java.util.*\n\ndata class Media(\n val ur"
},
{
"path": "camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/adapter/MediaAdapter.kt",
"chars": 1953,
"preview": "package com.robertlevonyan.demo.camerax.adapter\n\nimport android.net.Uri\nimport android.view.View\nimport android.view.Vie"
},
{
"path": "camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/adapter/MediaDiffCallback.kt",
"chars": 358,
"preview": "package com.robertlevonyan.demo.camerax.adapter\n\nimport androidx.recyclerview.widget.DiffUtil\n\nclass MediaDiffCallback :"
},
{
"path": "camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/analyzer/LuminosityAnalyzer.kt",
"chars": 1536,
"preview": "package com.robertlevonyan.demo.camerax.analyzer\n\nimport android.util.Log\nimport androidx.camera.core.ImageAnalysis\nimpo"
},
{
"path": "camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/enums/CameraTimer.kt",
"chars": 99,
"preview": "package com.robertlevonyan.demo.camerax.enums\n\nenum class CameraTimer {\n OFF,\n S3,\n S10\n}\n"
},
{
"path": "camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/fragments/BaseFragment.kt",
"chars": 6739,
"preview": "package com.robertlevonyan.demo.camerax.fragments\n\nimport android.Manifest\nimport android.content.ContentUris\nimport and"
},
{
"path": "camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/fragments/CameraFragment.kt",
"chars": 25485,
"preview": "package com.robertlevonyan.demo.camerax.fragments\n\nimport android.annotation.SuppressLint\nimport android.content.Content"
},
{
"path": "camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/fragments/PreviewFragment.kt",
"chars": 2861,
"preview": "package com.robertlevonyan.demo.camerax.fragments\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android"
},
{
"path": "camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/fragments/VideoFragment.kt",
"chars": 16473,
"preview": "package com.robertlevonyan.demo.camerax.fragments\n\nimport android.animation.ObjectAnimator\nimport android.annotation.Sup"
},
{
"path": "camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/utils/Extensions.kt",
"chars": 5323,
"preview": "package com.robertlevonyan.demo.camerax.utils\n\nimport android.animation.Animator\nimport android.animation.AnimatorListen"
},
{
"path": "camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/utils/MainExecutor.kt",
"chars": 256,
"preview": "package com.robertlevonyan.demo.camerax.utils\n\nimport android.os.Handler\nimport android.os.Looper\n\nclass MainExecutor : "
},
{
"path": "camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/utils/SharedPrefsManager.kt",
"chars": 788,
"preview": "package com.robertlevonyan.demo.camerax.utils\n\nimport android.content.Context\n\nclass SharedPrefsManager private construc"
},
{
"path": "camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/utils/SwipeGestureDetector.kt",
"chars": 1216,
"preview": "package com.robertlevonyan.demo.camerax.utils\n\nimport android.view.GestureDetector\nimport android.view.MotionEvent\nimpor"
},
{
"path": "camerax-demo/app/src/main/kotlin/com/robertlevonyan/demo/camerax/utils/ThreadExecutor.kt",
"chars": 268,
"preview": "package com.robertlevonyan.demo.camerax.utils\n\nimport android.os.Handler\nimport java.util.concurrent.Executor\n\nopen clas"
},
{
"path": "camerax-demo/app/src/main/res/anim/slide_in.xml",
"chars": 194,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<translate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android"
},
{
"path": "camerax-demo/app/src/main/res/anim/slide_in_pop.xml",
"chars": 195,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<translate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android"
},
{
"path": "camerax-demo/app/src/main/res/anim/slide_out.xml",
"chars": 195,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<translate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android"
},
{
"path": "camerax-demo/app/src/main/res/anim/slide_out_pop.xml",
"chars": 194,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<translate xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android"
},
{
"path": "camerax-demo/app/src/main/res/drawable/bg_button_round.xml",
"chars": 193,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "camerax-demo/app/src/main/res/drawable/bg_options.xml",
"chars": 236,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_arrow_back.xml",
"chars": 347,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_delete.xml",
"chars": 409,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_edit.xml",
"chars": 527,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_exposure.xml",
"chars": 502,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_flash_auto.xml",
"chars": 421,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:wi"
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_flash_off.xml",
"chars": 421,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:wi"
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_flash_on.xml",
"chars": 350,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:wi"
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_grid_off.xml",
"chars": 681,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:wi"
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_grid_on.xml",
"chars": 544,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:wi"
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_hdr_off.xml",
"chars": 644,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:wi"
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_hdr_on.xml",
"chars": 589,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:wi"
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_launcher_foreground.xml",
"chars": 3196,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"108dp\"\n android:height=\"108dp\"\n"
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_no_picture.xml",
"chars": 761,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_outline_camera_enhance.xml",
"chars": 654,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_outline_camera_front.xml",
"chars": 567,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_outline_camera_rear.xml",
"chars": 506,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_play.xml",
"chars": 336,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_share.xml",
"chars": 908,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_take_picture.xml",
"chars": 641,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"@dimen/button_size_big\"\n androi"
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_take_video.xml",
"chars": 614,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"@dimen/button_size_big\"\n androi"
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_timer_10.xml",
"chars": 2958,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:wi"
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_timer_3.xml",
"chars": 3298,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:wi"
},
{
"path": "camerax-demo/app/src/main/res/drawable/ic_timer_off.xml",
"chars": 808,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:wi"
},
{
"path": "camerax-demo/app/src/main/res/layout/activity_main.xml",
"chars": 595,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns"
},
{
"path": "camerax-demo/app/src/main/res/layout/fragment_camera.xml",
"chars": 14165,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
},
{
"path": "camerax-demo/app/src/main/res/layout/fragment_preview.xml",
"chars": 3562,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
},
{
"path": "camerax-demo/app/src/main/res/layout/fragment_video.xml",
"chars": 6981,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
},
{
"path": "camerax-demo/app/src/main/res/layout/item_picture.xml",
"chars": 661,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n andro"
},
{
"path": "camerax-demo/app/src/main/res/layout-land/fragment_camera.xml",
"chars": 14052,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
},
{
"path": "camerax-demo/app/src/main/res/layout-land/fragment_video.xml",
"chars": 6924,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
},
{
"path": "camerax-demo/app/src/main/res/menu/menu_main.xml",
"chars": 423,
"preview": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app=\"http://schemas.android.com/apk/res-aut"
},
{
"path": "camerax-demo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
"chars": 267,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "camerax-demo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
"chars": 267,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "camerax-demo/app/src/main/res/navigation/nav_graph.xml",
"chars": 2493,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<navigation xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:"
},
{
"path": "camerax-demo/app/src/main/res/values/colors.xml",
"chars": 262,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"colorPrimary\">#1a1a18</color>\n <color name=\"color"
},
{
"path": "camerax-demo/app/src/main/res/values/dimens.xml",
"chars": 400,
"preview": "<resources>\n <dimen name=\"half_margin\">8dp</dimen>\n <dimen name=\"fab_margin\">16dp</dimen>\n <dimen name=\"medium_"
},
{
"path": "camerax-demo/app/src/main/res/values/ic_launcher_background.xml",
"chars": 120,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"ic_launcher_background\">#F1F1F1</color>\n</resources>"
},
{
"path": "camerax-demo/app/src/main/res/values/strings.xml",
"chars": 636,
"preview": "<resources>\n <string name=\"app_name\">CameraX Demo</string>\n <string name=\"action_settings\">Settings</string>\n <"
},
{
"path": "camerax-demo/app/src/main/res/values/styles.xml",
"chars": 731,
"preview": "<resources>\n\n <style name=\"AppTheme\" parent=\"Theme.MaterialComponents.Light.DarkActionBar\">\n <item name=\"color"
},
{
"path": "camerax-demo/app/src/main/res/xml/provider_paths.xml",
"chars": 105,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths>\n <external-path name=\"external_files\" path=\".\"/>\n</paths>"
},
{
"path": "camerax-demo/build.gradle.kts",
"chars": 294,
"preview": "plugins {\n alias(libs.plugins.android.application) apply false\n alias(libs.plugins.kotlin.android) apply false\n "
},
{
"path": "camerax-demo/gradle/libs.versions.toml",
"chars": 4434,
"preview": "[versions]\nandroidGradlePlugin = \"8.9.1\"\nandroidxComposeBom = \"2025.04.00\"\nactivityKtx = \"1.10.1\"\nappcompat = \"1.7.0\"\nca"
},
{
"path": "camerax-demo/gradle/wrapper/gradle-wrapper.properties",
"chars": 233,
"preview": "#Fri Oct 27 14:13:16 AMT 2023\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://"
},
{
"path": "camerax-demo/gradle.properties",
"chars": 1274,
"preview": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will ov"
},
{
"path": "camerax-demo/settings.gradle",
"chars": 330,
"preview": "pluginManagement {\n repositories {\n gradlePluginPortal()\n google()\n mavenCentral()\n }\n}\ndepen"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the robertlevonyan/camerax-demo GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 79 files (155.6 KB), approximately 45.5k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.