Android CameraX Preview Not Circular

87 Views Asked by At

I have this layout for dialogFragment, which contain the circle layout. i just need to show circular layout when showing the dialog, I've create custom views or CardView's but nothing worked every thing i tried camera shows in rectangular view and the circular of card view or custom class at best shows a shadows of circle card view i mean, i need show just a circle dialog fragment and the camera shows in it.

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <data>

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.cardview.widget.CardView
            android:id="@+id/cameraWrapper"
            android:layout_width="300dp"
            android:layout_height="300dp"
            android:foreground="@drawable/background_camera_overlay_circle"
            android:padding="-20dp"
            app:cardCornerRadius="360dp"
            app:cardElevation="0dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <androidx.camera.view.PreviewView
                android:id="@+id/preview"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

        </androidx.cardview.widget.CardView>
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

and this is my dialogFragment for showing camera dialog

class CameraDialog : DialogFragment(R.layout.dialog_camera) {
    companion object {
        private const val TAG = "CameraXApp"
    }

    private lateinit var binding: DialogCameraBinding
    private var videoCapture: VideoCapture<Recorder>? = null
    private var recording: Recording? = null
    private lateinit var cameraExecutor: ExecutorService

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        binding = DialogCameraBinding.inflate(inflater, container, false)
        dialog?.window?.setBackgroundDrawable(ColorDrawable(0x00ffffff))
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        startCamera()
        cameraExecutor = Executors.newSingleThreadExecutor()
        /**
         * this handler makes this recording camera with delay after that saving
         */
        view.postDelayed({
            captureVideo()
        }, 1000)
    }

    private fun captureVideo() {
        val videoCapture = this.videoCapture ?: return
        val curRecording = recording
        if (curRecording != null) {
            curRecording.stop()
            recording = null
            return
        }

        recording = videoCapture.output
            .prepareRecording(
                requireContext(),
                requireActivity().contentResolver.createVideoStoreOutputOptions(
                    ContentValues().createVideoContentValues()
                )
            )
            .apply {
                // Enable Audio for recording
                if (
                    PermissionChecker.checkSelfPermission(
                        requireContext(),
                        Manifest.permission.RECORD_AUDIO
                    ) ==
                    PermissionChecker.PERMISSION_GRANTED
                ) {
                    withAudioEnabled()
                }
            }
            .start(ContextCompat.getMainExecutor(requireContext())) { recordEvent ->
                when (recordEvent) {
                    is VideoRecordEvent.Finalize -> {
                        if (!recordEvent.hasError())
                            Toast.makeText(requireContext(), "captureVideo", Toast.LENGTH_SHORT).show()
                        else {
                            recording?.close()
                            recording = null
                            Timber.tag(TAG).e("captureVideo: got error")
                        }
                        dialog?.dismiss()
                    }
                }
            }
    }

    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
        cameraProviderFuture.addListener({
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            // Preview
            val preview = Preview.Builder()
                .build()
                .also {
                    it.setSurfaceProvider(binding.preview.surfaceProvider)
                }

            // Video
            val recorder = Recorder.Builder()
                .setQualitySelector(
                    QualitySelector.from(
                        Quality.HIGHEST,
                        FallbackStrategy.higherQualityOrLowerThan(Quality.SD)
                    )
                )
                .build()
            videoCapture = VideoCapture.withOutput(recorder)

            // Select back camera as a default
            val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(this, cameraSelector, preview, videoCapture)
            } catch (exc: Exception) {
                Timber.e(TAG, "Use case binding failed", exc)
            }
        }, ContextCompat.getMainExecutor(requireContext()))
    }

    override fun onDestroy() {
        super.onDestroy()
        cameraExecutor.shutdown()
    }
}

What I'm getting now

current

i need to show the camera inside the red circle

expected

circle dialog fragment for camera but shows in rectangle view

2

There are 2 best solutions below

6
Chirag Thummar On

You Can not directly make it round. For that need to make a custom view for that like below.

You can use this class in XML to show the camera round. Don't forget to pass the required argument into it.

RoundedCameraPreview.java

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.camera.view.PreviewView;
import future.quantumpaper.sci10gx302.papergenerator.R;


public class RoundedCameraPreview extends PreviewView {


    Path clipPath;
    boolean isRound;
    Context mContext;
    int padding;

    public RoundedCameraPreview(@NonNull Context context) {
        super(context);
        mContext = context;
    }

    public RoundedCameraPreview(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
    }

    public RoundedCameraPreview(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        TypedArray a = context.getTheme().obtainStyledAttributes(
                attrs,
                R.styleable.cm_PreviewView,
                0, 0);
        try {
            isRound = a.getBoolean(R.styleable.cm_PreviewView_isRound, true);
            padding = a.getInt(R.styleable.cm_PreviewView_image_padding, 0);
        } finally {
            a.recycle();
        }
    }

    public boolean isRound() {
        return isRound;
    }

    public void setIsRound(boolean isRound) {
        this.isRound = isRound;
        invalidate();
        requestLayout();
    }


    public int getImagePadding() {
        return padding;
    }

    public void setImagePadding(int padding) {
        this.padding = padding;
        invalidate();
        requestLayout();
    }

    public RoundedCameraPreview(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mContext = context;
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        if (isRound) {
            clipPath = new Path();
            clipPath.addCircle(canvas.getWidth() / 2, canvas.getWidth() / 2, (canvas.getWidth() / 2) - padding, Path.Direction.CW);
            Paint paint = new Paint();
            paint.setAntiAlias(true);
            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
            canvas.clipPath(clipPath);
            canvas.drawPath(clipPath, paint);
        } else {
            clipPath = new Path();
            clipPath.addRect(padding, padding, (canvas.getWidth()) - padding, (canvas.getHeight()) - padding, Path.Direction.CCW);
            Paint paint = new Paint();
            paint.setAntiAlias(true);
            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
            canvas.clipPath(clipPath);
            canvas.drawPath(clipPath, paint);
        }
        super.dispatchDraw(canvas);
    }
}
4
mahdi_k_ch On

I put my complete code for you

your need this drawable :

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
    <shape android:shape="rectangle" >
        <solid android:color="@android:color/transparent" />
    </shape>

</item>
<item>
    <shape
        android:innerRadiusRatio="2"
        android:shape="ring"
        android:thicknessRatio="1"
        android:useLevel="false" >
        <solid android:color="#000000" />
        <size
            android:height="148dip"
            android:width="148dip" />

    </shape>

</item>

its my activity_main.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="#ffffff"
tools:context=".MainActivity">

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white">

    <TextureView
        android:id="@+id/cameraPreview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

    <ImageView
        android:id="@+id/circularOverlay"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/circular_overlay" />

</FrameLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

and its my MainActivity:

class MainActivity : AppCompatActivity() {

private var textureView: TextureView? =null
private var circularOverlay: ImageView? =null
private var camera: Camera? = null


override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    textureView = findViewById(R.id.cameraPreview)
    circularOverlay = findViewById(R.id.circularOverlay)
    setupCamera()
}


private fun startCameraPreview() {
    camera = Camera.open()
    val parameters = camera!!.parameters

    // Set the preview size to match the TextureView dimensions
    parameters.setPreviewSize(textureView!!.width, textureView!!.height)
    camera!!.setDisplayOrientation(90)
    camera!!.setPreviewTexture(textureView!!.surfaceTexture)
    camera!!.startPreview()
}

fun setupCamera() {
    if (textureView!!.isAvailable) {
        startCameraPreview()
    } else {
        textureView!!.surfaceTextureListener = object : 
TextureView.SurfaceTextureListener {
            override fun onSurfaceTextureAvailable(surfaceTexture: 
SurfaceTexture, width: Int, height: Int) {
                startCameraPreview()
            }

            override fun onSurfaceTextureSizeChanged(surfaceTexture: 
SurfaceTexture, width: Int, height: Int) {
            }

            override fun onSurfaceTextureDestroyed(surfaceTexture: 
SurfaceTexture): Boolean {
                releaseCamera()
                return true
            }

            override fun onSurfaceTextureUpdated(surfaceTexture: SurfaceTexture) 
{
            }
        }
    }
}

private fun releaseCamera() {
    camera?.release()
    camera = null
}
}

and its result:

enter image description here