Currently there's no equivalent to CameraView (and PreviewView) in Compose. Is it possible to wrap it and display it in a compose layout?
There is still no CameraX composable. You need to use AndroidView to create one.
Updated example for Compose 1.0.0-beta02:
#Composable
fun CameraPreview(
modifier: Modifier = Modifier,
cameraSelector: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA,
scaleType: PreviewView.ScaleType = PreviewView.ScaleType.FILL_CENTER,
) {
val lifecycleOwner = LocalLifecycleOwner.current
AndroidView(
modifier = modifier,
factory = { context ->
val previewView = PreviewView(context).apply {
this.scaleType = scaleType
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
// Preview is incorrectly scaled in Compose on some devices without this
implementationMode = PreviewView.ImplementationMode.COMPATIBLE
}
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
// Preview
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
try {
// Must unbind the use-cases before rebinding them.
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
lifecycleOwner, cameraSelector, preview
)
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(context))
previewView
})
}
At the moment there isn't any official
Composable function for CameraX so we have to inflate the legacy android view inside compose.
To achieve that
we can use AndroidView composable function,
it accepts two parameters
#param resId The id of the layout resource to be inflated.
#param postInflationCallback The callback to be invoked after the layout
is inflated.
and to access the lifecycle and context we use the ambients
val lifecycleOwner = LifecycleOwnerAmbient.current
val context = ContextAmbient.current
As we have everything we need let's do it:
Create a layout camera_host.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.camera.view.PreviewView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/previewView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
and inflate it using AndroidView Composable function.
#Composable
fun SimpleCameraPreview() {
val lifecycleOwner = LifecycleOwnerAmbient.current
val context = ContextAmbient.current
val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) }
AndroidView(resId = R.layout.camera_host) { inflatedLayout ->
//You can call
// findViewById<>() and etc ... on inflatedLayout
// here PreviewView is the root of my layout so I just cast it to
// the PreviewView and no findViewById is required
cameraProviderFuture.addListener(Runnable {
val cameraProvider = cameraProviderFuture.get()
bindPreview(
lifecycleOwner,
inflatedLayout as PreviewView /*the inflated layout*/,
cameraProvider)
}, ContextCompat.getMainExecutor(context))
}
}
fun bindPreview(
lifecycleOwner: LifecycleOwner,
previewView: PreviewView,
cameraProvider: ProcessCameraProvider
) {
var preview: Preview = Preview.Builder().build()
var cameraSelector: CameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build()
preview.setSurfaceProvider(previewView.createSurfaceProvider())
var camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SimpleCameraPreview()
}
}
}
This is my snippet (based on Sean's answer), which also handles torch state and resource disposition and adds a focus on tap logic.
Dependencies:
implementation 'androidx.camera:camera-camera2:1.1.0-alpha11'
implementation 'androidx.camera:camera-view:1.0.0-alpha31'
implementation 'androidx.camera:camera-lifecycle:1.1.0-alpha11'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.0-RC'
#Composable
fun CameraPreview(
modifier: Modifier = Modifier,
cameraSelector: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA,
implementationMode: PreviewView.ImplementationMode = PreviewView.ImplementationMode.COMPATIBLE,
scaleType: PreviewView.ScaleType = PreviewView.ScaleType.FILL_CENTER,
imageAnalysis: ImageAnalysis? = null,
imageCapture: ImageCapture? = null,
preview: Preview = remember { Preview.Builder().build() },
enableTorch: Boolean = false,
focusOnTap: Boolean = false
) {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
val cameraProvider by produceState<ProcessCameraProvider?>(initialValue = null) {
value = ProcessCameraProvider.getInstance(context).await()
}
// TODO: add cameraSelector
val camera = remember(cameraProvider) {
cameraProvider?.let {
it.unbindAll()
it.bindToLifecycle(
lifecycleOwner,
cameraSelector,
*listOfNotNull(imageAnalysis, imageCapture, preview).toTypedArray()
)
}
}
LaunchedEffect(camera, enableTorch) {
camera?.let {
if (it.cameraInfo.hasFlashUnit()) {
it.cameraControl.enableTorch(enableTorch).await()
}
}
}
DisposableEffect(Unit) {
onDispose {
cameraProvider?.unbindAll()
}
}
AndroidView(
modifier = modifier.pointerInput(camera, focusOnTap) {
if (!focusOnTap) return#pointerInput
detectTapGestures {
val meteringPointFactory = SurfaceOrientedMeteringPointFactory(
size.width.toFloat(),
size.height.toFloat()
)
val meteringAction = FocusMeteringAction.Builder(
meteringPointFactory.createPoint(it.x, it.y),
FocusMeteringAction.FLAG_AF
).disableAutoCancel().build()
camera?.cameraControl?.startFocusAndMetering(meteringAction)
}
},
factory = { _ ->
PreviewView(context).also {
it.scaleType = scaleType
it.implementationMode = implementationMode
preview.setSurfaceProvider(it.surfaceProvider)
}
}
)
}
I've created a library to use CameraX in Jetpack Compose. It might be useful until an official library comes out.
https://github.com/skgmn/CameraXX
In your build.gradle, (requires GitHub personal access token)
implementation "com.github.skgmn:cameraxx-composable:0.3.0"
Composable method signature
CameraPreview(
modifier: Modifier = Modifier,
cameraSelector: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA,
preview: Preview?,
imageCapture: ImageCapture? = null,
imageAnalysis: ImageAnalysis? = null
)
You can omit preview parameter to use default Preview instance.
An example
class MainViewModel : ViewModel() {
val imageCapture = ImageCapture.Builder().build()
}
#Composable
fun Main() {
val viewModel: MainViewModel = viewModel()
val imageCapture by remember { viewModel.imageCapture }
CameraPreview(Modifier.fillMaxSize(), imageCapture)
}
Related
I am trying to integrate CameraX in my flutter app but I get error saying Cannot access class 'com.google.common.util.concurrent.ListenableFuture'. Check your module classpath for missing or conflicting dependencies
There error comes from below line
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
Below is my native view
class CealScanQrView(val context: Context, id: Int, creationParams: Map<String?, Any?>?) :
PlatformView {
private var mCameraProvider: ProcessCameraProvider? = null
private var preview: PreviewView
private var linearLayout: LinearLayout = LinearLayout(context)
private lateinit var cameraExecutor: ExecutorService
private lateinit var options: BarcodeScannerOptions
private lateinit var scanner: BarcodeScanner
private var analysisUseCase: ImageAnalysis = ImageAnalysis.Builder()
.build()
companion object {
private val REQUIRED_PERMISSIONS = mutableListOf(Manifest.permission.CAMERA).toTypedArray()
}
init {
val linearLayoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
linearLayout.layoutParams = linearLayoutParams
linearLayout.orientation = LinearLayout.VERTICAL
preview = PreviewView(context)
preview.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
linearLayout.addView(preview)
setUpCamera()
}
private fun setUpCamera(){
if (allPermissionsGranted()) {
startCamera()
}
cameraExecutor = Executors.newSingleThreadExecutor()
options = BarcodeScannerOptions.Builder()
.setBarcodeFormats(
Barcode.FORMAT_QR_CODE)
.build()
scanner = BarcodeScanning.getClient(options)
analysisUseCase.setAnalyzer(
// newSingleThreadExecutor() will let us perform analysis on a single worker thread
Executors.newSingleThreadExecutor()
) { imageProxy ->
processImageProxy(scanner, imageProxy)
}
}
override fun getView(): View {
return linearLayout
}
override fun dispose() {
cameraExecutor.shutdown()
}
#SuppressLint("UnsafeOptInUsageError")
private fun processImageProxy(
barcodeScanner: BarcodeScanner,
imageProxy: ImageProxy
) {
imageProxy.image?.let { image ->
val inputImage =
InputImage.fromMediaImage(
image,
imageProxy.imageInfo.rotationDegrees
)
barcodeScanner.process(inputImage)
.addOnSuccessListener { barcodeList ->
val barcode = barcodeList.getOrNull(0)
// `rawValue` is the decoded value of the barcode
barcode?.rawValue?.let { value ->
mCameraProvider?.unbindAll()
}
}
.addOnFailureListener {
// This failure will happen if the barcode scanning model
// fails to download from Google Play Services
}
.addOnCompleteListener {
// When the image is from CameraX analysis use case, must
// call image.close() on received images when finished
// using them. Otherwise, new images may not be received
// or the camera may stall.
imageProxy.image?.close()
imageProxy.close()
}
}
}
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
}
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener({
// Used to bind the lifecycle of cameras to the lifecycle owner
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
mCameraProvider = cameraProvider
// Preview
val surfacePreview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(preview.surfaceProvider)
}
// Select back camera as a default
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
// Unbind use cases before rebinding
cameraProvider.unbindAll()
// Bind use cases to camera
cameraProvider.bindToLifecycle(
(context as FlutterActivity),
cameraSelector,
surfacePreview,
analysisUseCase,
)
} catch (exc: Exception) {
// Do nothing on exception
}
}, ContextCompat.getMainExecutor(context))
}
}
class CealScanQrViewFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
val creationParams = args as Map<String?, Any?>?
return CealScanQrView(context, viewId, creationParams)
}
}
Add this line to your app build.gradle dependencies:
implementation 'com.google.guava:guava:29.0-android'
I am trying to use ViewModel with Jetpack Compose,
By doing a number increment.
But it's not working. Maybe I'm not using the view model in right way.
Heres my Main Activity code
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Greeting()
}
}
}
#Composable
fun Greeting(
helloViewModel: ViewModel = viewModel()
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
Text(
text = helloViewModel.number.toString(),
fontSize = 60.sp,
fontWeight = FontWeight.Bold
)
Button(onClick = { helloViewModel.addNumber() }) {
Text(text = "Increment Number ${helloViewModel.number}")
}
}
}
#Preview(showBackground = true)
#Composable
fun DefaultPreview() {
JetpackcomposepracticeTheme {
Greeting()
}
}
And here is my Viewmodel class.
It works fine with xml.
How do i create the object of view model:
class ViewModel: ViewModel() {
var number : Int = 0
fun addNumber(){
number++
}
}
Compose can recompose when some with mutable state value container changes. You can create it manually with mutableStateOf(), mutableStateListOf(), etc, or by wrapping Flow/LiveData.
class ViewModel: ViewModel() {
var number : Int by mutableStateOf(0)
private set
fun addNumber(){
number++
}
}
I suggest you start with state in compose documentation, including this youtube video which explains the basic principles.
I use Hilt along with view-Model. Here is another way of using it
#Composable
fun PokemonListScreen(
navController: NavController
) {
val viewModel = hiltViewModel<PokemonListVm>()
val lazyPokemonItems: LazyPagingItems<PokedexListEntry> = viewModel.pokemonList.collectAsLazyPagingItems()
}
I use this composable instead of a fragment and I keep one reference to the composable in the parent
Hi i develop a simple QRcode scanner with CameraX. It works but i'd like show a preiew shape around qrcode.
I create a custom view and send boundBox of barcode but.. dimensions and position are wrong.
I think that it's a coordinate translate problems.. maybe :(
here a little project
https://github.com/giuseppesorce/cameraxscan
Some code:
package com.gs.scancamerax
import android.Manifest.permission.CAMERA
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.DisplayMetrics
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.common.InputImage
import com.gs.scancamerax.databinding.FragmentScanBarcodeBinding
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
typealias BarcodeListener = (barcode: String) -> Unit
class ScanBarcodeFragment : Fragment() {
private var scanningResultListener: ScanningResultListener? = null
private var flashEnabled: Boolean = false
private var camera: Camera? = null
private var processingBarcode = AtomicBoolean(false)
private lateinit var cameraExecutor: ExecutorService
private var _binding: FragmentScanBarcodeBinding? = null
private val binding get() = _binding!!
private val TAG = "CameraXBasic"
private val RATIO_4_3_VALUE = 4.0 / 3.0
private val RATIO_16_9_VALUE = 16.0 / 9.0
private var imageCapture: ImageCapture? = null
private var imageAnalyzer: ImageAnalysis? = null
private var cameraProvider: ProcessCameraProvider? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentScanBarcodeBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onResume() {
super.onResume()
processingBarcode.set(false)
initFragment()
}
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener(Runnable {
// CameraProvider
cameraProvider = cameraProviderFuture.get()
// Build and bind the camera use cases
bindCameraUseCases()
}, ContextCompat.getMainExecutor(requireContext()))
}
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
}
private var preview: Preview? = null
private fun bindCameraUseCases() {
// Get screen metrics used to setup camera for full screen resolution
val metrics =
DisplayMetrics().also { binding.fragmentScanBarcodePreviewView.display.getRealMetrics(it) }
Log.d(TAG, "Screen metrics: ${metrics.widthPixels} x ${metrics.heightPixels}")
val screenAspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels)
Log.d(TAG, "Preview aspect ratio: $screenAspectRatio")
val rotation = binding.fragmentScanBarcodePreviewView.display.rotation
// CameraProvider
val cameraProvider = cameraProvider
?: throw IllegalStateException("Camera initialization failed.")
// CameraSelector
val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
// Preview
preview = Preview.Builder()
// We request aspect ratio but no resolution
.setTargetAspectRatio(screenAspectRatio)
// Set initial target rotation
.setTargetRotation(rotation)
.build()
// ImageCapture
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
// We request aspect ratio but no resolution to match preview config, but letting
// CameraX optimize for whatever specific resolution best fits our use cases
.setTargetAspectRatio(screenAspectRatio)
// Set initial target rotation, we will have to call this again if rotation changes
// during the lifecycle of this use case
.setTargetRotation(rotation)
.build()
// ImageAnalysis
imageAnalyzer = ImageAnalysis.Builder()
// We request aspect ratio but no resolution
.setTargetAspectRatio(screenAspectRatio)
// Set initial target rotation, we will have to call this again if rotation changes
// during the lifecycle of this use case
.setTargetRotation(rotation)
.build()
// The analyzer can then be assigned to the instance
.also {
it.setAnalyzer(cameraExecutor, BarcodeAnalyzer { luma ->
// Values returned from our analyzer are passed to the attached listener
// We log image analysis results here - you should do something useful
// instead!
Log.d(TAG, "Average luminosity: $luma")
})
}
// Must unbind the use-cases before rebinding them
cameraProvider.unbindAll()
try {
// A variable number of use-cases can be passed here -
// camera provides access to CameraControl & CameraInfo
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture, imageAnalyzer
)
// Attach the viewfinder's surface provider to preview use case
preview?.setSurfaceProvider(binding.fragmentScanBarcodePreviewView.surfaceProvider)
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}
private var lensFacing: Int = CameraSelector.LENS_FACING_BACK
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(
requireContext(), it
) == PackageManager.PERMISSION_GRANTED
}
fun initFragment() {
cameraExecutor = Executors.newSingleThreadExecutor()
if (allPermissionsGranted()) {
binding.fragmentScanBarcodePreviewView.post {
startCamera()
}
} else {
requestPermissions(REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
}
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>,
grantResults: IntArray
) {
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
startCamera()
} else {
activity?.let {
Toast.makeText(
it.applicationContext,
"Permissions not granted by the user.",
Toast.LENGTH_SHORT
).show()
}
}
}
}
private fun searchBarcode(barcode: String) {
Log.e("driver", "searchBarcode: $barcode")
}
override fun onDestroy() {
cameraExecutor.shutdown()
camera = null
_binding = null
super.onDestroy()
}
inner class BarcodeAnalyzer(private val barcodeListener: BarcodeListener) :
ImageAnalysis.Analyzer {
private val scanner = BarcodeScanning.getClient()
#SuppressLint("UnsafeExperimentalUsageError")
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image
if (mediaImage != null) {
val image =
InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
// Pass image to the scanner and have it do its thing
scanner.process(image)
.addOnSuccessListener { barcodes ->
for (barcode in barcodes) {
barcodeListener(barcode.rawValue ?: "")
binding.myView.setBounds(barcode.boundingBox)
}
}
.addOnFailureListener {
// You should really do something about Exceptions
}
.addOnCompleteListener {
// It's important to close the imageProxy
imageProxy.close()
}
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
fun setScanResultListener(listener: ScanningResultListener) {
this.scanningResultListener = listener
}
companion object {
private val REQUIRED_PERMISSIONS = arrayOf(CAMERA)
private const val REQUEST_CODE_PERMISSIONS = 10
const val TAG = "BarCodeFragment"
#JvmStatic
fun newInstance() = ScanBarcodeFragment()
}
}
Not sure if you are still having the problem, but now CameraX provides a helper class called CoordinateTransform that transforms the coordinates from one use case to another. For example, you can transform the bounding box of the QR code detected from ImageAnalysis to PreviewView coordinates, and draw a bounding box there.
// ImageProxy is the output of an ImageAnalysis.
OutputTransform source = ImageProxyTransformFactory().getOutputTransform(imageProxy);
OutputTransform target = previewView.getOutputTransform();
// Build the transform from ImageAnalysis to PreviewView
CoordinateTransform coordinateTransform = new CoordinateTransform(source, target);
// Detect barcode in ImageProxy and transform the coordinates to PreviewView.
RectF boundingBox = detectBarcodeInImageProxy(imageProxy);
coordinateTransform.mapRect(boundingBox);
// Now boundingBox contains the coordinates of the bounding box in PreviewView.
I am using camerax to capture images in my android app.
Everything is working fine for me but some users are reporting black preview screen when using camerax activity.
But when users opens the app from recents app, the preview seems to work.
So, I think the issue might be with the lifecycle binding.
I am using
implementation "androidx.camera:camera-camera2:1.0.0-beta08"
Here is my code
<androidx.camera.view.PreviewView
android:id="#+id/viewFinder"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(viewFinder.createSurfaceProvider())
}
imageCapture = ImageCapture.Builder()
.build()
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build()
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(this))
}
private fun takePhoto() {
val imageCapture = imageCapture ?: return
val photoFile = File(
getExternalFilesDir("scantmp"),
SimpleDateFormat(FILENAME_FORMAT, Locale.US
).format(System.currentTimeMillis()) + ".png")
val outputOptions = ImageCapture
.OutputFileOptions
.Builder(photoFile)
.build()
imageCapture.takePicture(
outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
savedImageUri = Uri.fromFile(photoFile)
flash.visibility = View.GONE
closeCamera.visibility = View.GONE
takeAgain.visibility = View.VISIBLE
saveImage.visibility = View.VISIBLE
imgCapture.visibility = View.INVISIBLE
imageCaptured.setImageURI(Uri.fromFile(photoFile))
imageCaptured.visibility = View.VISIBLE
viewFinder.visibility = View.GONE
}
})
}
The above problem is solved in 1.0.0-beta11 update.
Try updating your library to latest version to solve this problem.
I added cameraX in my app as it described in this tutorial. The only problem i faced is image rotation. In app manifest file I use this settings for camera activity android:screenOrientation="portrait". My goal is to make this activity always in portrait mode, while captured images should have real rotation.
How can i achieve this? Is it possible for cameraX to detect different rotation while activity has fixed?
This is my code in camera activity
private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
private lateinit var imageCapture: ImageCapture
private val executor = Executors.newSingleThreadExecutor()
private var camera: Camera? = null
...
override fun onCreate(savedInstanceState: Bundle?)
{
...
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
preview_view.post(
{
startCamera()
})
}
...
fun startCamera()
{
preview = Preview.Builder().apply {
setTargetAspectRatio(AspectRatio.RATIO_16_9)
setTargetRotation(preview_view.display.rotation)
}.build()
imageCapture = ImageCapture.Builder().apply {
setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
}.build()
val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
cameraProviderFuture.addListener(Runnable {
val cameraProvider = cameraProviderFuture.get()
cameraProvider.unbindAll()
camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
preview.setSurfaceProvider(preview_view.createSurfaceProvider(camera!!.cameraInfo))
}, ContextCompat.getMainExecutor(this))
}
...
fun takePicture()
{
val file = createFile(getOutputDirectory(), FILENAME, PHOTO_EXTENSION)
val outputFileOptions = ImageCapture.OutputFileOptions.Builder(file).build()
imageCapture.takePicture(outputFileOptions, executor, object : ImageCapture.OnImageSavedCallback
{
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults)
{
val my_file_item = MyFileItem.createFromFile(file)
imageCaptured(my_file_item)
}
override fun onError(exception: ImageCaptureException)
{
val msg = "Photo capture failed: ${exception.message}"
preview_view.post({
Toast.makeText(this#ActPhotoCapture2, msg, Toast.LENGTH_LONG).show()
})
}
})
}
If your orientation is locked, you can probably use an orientation listener to listen for changes in the device's orientation, and each time its onOrientationChanged callback is invoked, you'd set the target rotation for the image capture use case.
val orientationEventListener = object : OrientationEventListener(context) {
override fun onOrientationChanged(orientation: Int) {
imageCapture.targetRotation = view.display.rotation
}
}
The view you use to get the rotation can be any view, for example the root view if you're in fragment, or just the PreviewView. You can also enable/disable this listener in onResume and onPause.
ps: The way you're setting up your use cases can cause issues. The use cases shouldn't be initialized before the camera is started. You should build the use cases after this line val cameraProvider = cameraProviderFuture.get().