Android uses Kotlin to use SurfaceView+Camera in Fragment to realize the function of camera preview and photo saving and display

Controls required in the xml file of 1.fragment

<Button
        android:id="@ + id/btn_one"
        app:backgroundTint="@color/white"
        android:text="Click to take photo"
        android:textSize="35sp"
        app:strokeColor="@color/home_center_bg"
        android:layout_marginTop="60dp"
        app:strokeWidth="2dp"
        app:cornerRadius="25dp"
        android:textColor="@color/home_center_bg"
        android:layout_width="200dp"
        android:layout_height="80dp"/>

    <TextView
        android:id="@ + id/tv_pic_dir"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="160dp"
        android:text="Image path:"
        android:textColor="#000000"
        android:textSize="14sp" />

    <SurfaceView
        android:id="@ + id/congig_access_SurfaceView"
        android:layout_width="408dp"
        android:layout_height="408dp"
        android:layout_marginTop="205dp"
        app:round="204dp"
        android:src="@mipmap/door_icon"
        />

    <ImageView
        android:id="@ + id/iv_photo"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_marginTop="650dp"
        android:src="@mipmap/door_icon"
        app:round="204dp" />

2. Create tool classes and interfaces

2.1 CameraTakeListener interface

package com.doshare.boardroom.cameratake.listener

import android.graphics.Bitmap
import java.io.File

interface CameraTakeListener {
    fun onSuccess(bitmapFile: File?, mBitmap: Bitmap?)
    fun onFail(error: String?)
}

2.2 FileUtil tool class

package com.doshare.boardroom.cameratake.utils

import android. content. Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Environment
import android.os.StatFs
import android.text.TextUtils
import top.zibin.luban.CompressionPredicate
import top.zibin.luban.Luban
import top.zibin.luban.OnCompressListener
import java.io.*
import java.util.*


object FileUtil {//sd card size related variables
    //Get the size of each block on the Sdcard
    // Get the number of Blocks available for the program
    // Calculate the standard size using: 1024, of course, 1000 can also be used
    /**
     * Calculate the remaining size of Sdcard
     *
     * @return MB
     */
    fun getAvailableSize():Long
         {
            //sd card size related variables
            val statFs: StatFs
            val file = Environment. getExternalStorageDirectory()
            statFs = StatFs(file.path)
            //Get the size of each block on the Sdcard
            val blockSize = statFs. blockSize. toLong()
            // Get the number of Blocks available for the program
            val blockavailable = statFs.availableBlocks.toLong()
            // Calculate the standard size using: 1024, of course, 1000 can also be used
            return blockSize * blockavailable / 1024 / 1024
        }//Get the total number of blocks on the sdcard
    //Get the size of each block on the sdcard
    // Calculate the standard size using: 1024, of course, 1000 can also be used

    /**
     * SDCard total capacity size
     *
     * @return MB
     */
    val totalSize: Long
        get() {
            val statFs: StatFs
            val file = Environment. getExternalStorageDirectory()
            statFs = StatFs(file.path)
            //Get the total number of blocks on the sdcard
            val blockCount = statFs.blockCount.toLong()
            //Get the size of each block on the sdcard
            val blockSize = statFs. blockSize. toLong()
            // Calculate the standard size using: 1024, of course, 1000 can also be used
            return blockCount * blockSize / 1024 / 1024
        }

    /**
     * Save bitmap to local
     *
     * @param bitmap
     * @return
     */
    fun saveBitmap(bitmap: Bitmap?): File? {
        val savePath: String
        val filePic: File
        savePath = if (Environment. getExternalStorageState() ==
            Environment.MEDIA_MOUNTED
        ) {
            (Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)
                .toString()
                     + File.separator)
        } else {
            LogUtil.d("saveBitmap: 1return")
            return null
        }
        try {
            filePic = File(savePath + "Pic_" + System.currentTimeMillis() + ".jpg")
            if (!filePic. exists()) {
                filePic.parentFile.mkdirs()
                filePic. createNewFile()
            }
            val fos = FileOutputStream(filePic)
            bitmap?.compress(Bitmap.CompressFormat.JPEG, 100, fos)
            fos. flush()
            fos. close()
        } catch (e: IOException) {
            e. printStackTrace()
            LogUtil.d("saveBitmap: 2return")
            return null
        }
        LogUtil.d("saveBitmap: " + filePic.absolutePath)
        return filePic
    }

    /**
     * Compress Pictures
     *
     * @param image
     * @return
     */
    fun compressImage(image: Bitmap): Bitmap? {
        val baos = ByteArrayOutputStream()
        /** Quality compression method, here 100 means no compression, store the compressed data in baos */
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos)
        /** Store the compressed data baos in ByteArrayInputStream */
        val isBm = ByteArrayInputStream(baos.toByteArray())

        return BitmapFactory. decodeStream(isBm, null, null)
    }

    /**
     * Folder deletion
     */
    fun deleteFile(file: File) {
        if (file. isDirectory) {
            val files = file. listFiles()
            for (i in files. indices) {
                val f = files[i]
               deleteFile(f)
            }
            file.delete() //If you want to keep the folder and only delete the file, please comment this line
        } else if (file. exists()) {
            file. delete()
        }
    }

    /**
     * Compress image files
     */
    fun compressPic(context: Context?, picFile: File, listener: OnCompressListener?) {
        val savePath =
            (Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
                .toString()
                     + File.separator)
        Luban.with(context)
            .load(picFile.path)
            .ignoreBy(100)
            .setTargetDir(savePath)
            .filter(object : CompressionPredicate {
                override fun apply(path: String): Boolean {
                    return !(TextUtils.isEmpty(path) || path.lowercase(Locale.getDefault())
                        .endsWith(".gif"))
                }
            })
            .setCompressListener(listener).launch()
    }
}

2.3 LogUtil tool class

package com.doshare.boardroom.cameratake.utils

import android.text.TextUtils
import android.util.Log

object LogUtil {
    var tagPrefix = ""
    var showV = true
    var showD = true
    var showI = true
    var showW = true
    var showE = true
    var showWTF = true

    /**
     * Get tag (class. method (L: row))
     *
     * @return
     */
    private fun generateTag(): String {
        val stackTraceElement = Thread.currentThread().stackTrace[4]
        var callerClazzName = stackTraceElement. className
        callerClazzName = callerClazzName.substring(callerClazzName.lastIndexOf(".") + 1)
        var tag = "%s.%s(L:%d)"
        tag = String. format(
            tag,
            *arrayOf<Any>(
                callerClazzName,
                stackTraceElement. methodName,
                Integer.valueOf(stackTraceElement.lineNumber)
            )
        )
        //Set prefix for tag
        tag =
            if (TextUtils.isEmpty(tagPrefix)) tag else tagPrefix + ":" + tag
        return tag
    }

    fun v(msg: String?) {
        if (showV) {
            val tag: String = generateTag()
            Log.v(tag, msg!!)
        }
    }

    fun v(msg: String?, tr: Throwable?) {
        if (showV) {
            val tag: String = generateTag()
            Log.v(tag, msg, tr)
        }
    }

    fun d(msg: String?) {
        if (showD) {
            val tag: String = generateTag()
            Log.d(tag, msg!!)
        }
    }

    fun d(msg: String?, tr: Throwable?) {
        if (showD) {
            val tag: String = generateTag()
            Log.d(tag, msg, tr)
        }
    }

    fun i(msg: String?) {
        if (showI) {
            val tag: String = generateTag()
            Log.i(tag, msg!!)
        }
    }

    fun i(msg: String?, tr: Throwable?) {
        if (showI) {
            val tag: String = generateTag()
            Log.i(tag, msg, tr)
        }
    }

    fun w(msg: String?) {
        if (showW) {
            val tag: String = generateTag()
            Log.w(tag, msg!!)
        }
    }

    fun w(msg: String?, tr: Throwable?) {
        if (showW) {
            val tag: String = generateTag()
            Log.w(tag, msg, tr)
        }
    }

    fun e(msg: String?) {
        if (showE) {
            val tag: String = generateTag()
            Log.e(tag, msg!!)
        }
    }

    fun e(msg: String?, tr: Throwable?) {
        if (showE) {
            val tag: String = generateTag()
            Log.e(tag, msg, tr)
        }
    }

    fun wtf(msg: String?) {
        if (showWTF) {
            val tag: String = generateTag()
            Log.wtf(tag, msg)
        }
    }

    fun wtf(msg: String?, tr: Throwable?) {
        if (showWTF) {
            val tag: String = generateTag()
            Log.wtf(tag, msg, tr)
        }
    }
}

2.4 SurfaceViewCallback tool class

package com.doshare.boardroom.cameratake

import android.app.Activity
import android.graphics.*
import android.hardware.Camera
import android.hardware.Camera.CameraInfo
import android.view.Surface
import android.view.SurfaceHolder
import com.doshare.boardroom.cameratake.listener.CameraTakeListener
import com.doshare.boardroom.cameratake.utils.FileUtil
import com.doshare.boardroom.cameratake.utils.LogUtil
import com.doshare.boardroom.view.fragment.ConfigAccessFragment
import top.zibin.luban.OnCompressListener
import java.io.ByteArrayOutputStream
import java.io.File


class SurfaceViewCallback(private val activity: Activity, listener: ConfigAccessFragment) :
    SurfaceHolder. Callback {
    var previewing = false
    private var hasSurface = false
    var mCamera: Camera? = null
    var mCurrentCamIndex = 0

    /** When true, start capturing photos */
    var canTake = false

    /** Camera callback interface */
    var listener: CameraTakeListener

    init {
        this.listener = listener
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        if (!hasSurface) {
            hasSurface = true
            mCamera = openFrontFacingCameraGingerbread()
            if (mCamera == null) {

                listener.onFail("No camera available")
                return
            }

            mCamera!!.setPreviewCallback { bytes, camera ->

                LogUtil.i("onPreviewFrame $canTake")
                if (canTake) {
                    getSurfacePic(bytes, camera)
                    canTake = false
                }
            }

        }
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
        if (previewing) {
            mCamera!!.stopPreview()
            previewing = false
        }
        try {
            mCamera!!.setPreviewDisplay(holder)
            mCamera!!.startPreview()
            previewing = true
            setCameraDisplayOrientation(activity, mCurrentCamIndex, mCamera)
        } catch (_: Exception) {
        }
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        if (!previewing) return
        holder. removeCallback(this)
        mCamera!!.setPreviewCallback(null)
        mCamera!!.stopPreview()
        mCamera!!.lock()
        mCamera!!. release()
        mCamera = null
    }

    /**
     * Set the direction of camera playback
     */
    private fun setCameraDisplayOrientation(activity: Activity, cameraId: Int, camera: Camera?) {
        val info = CameraInfo()
        Camera. getCameraInfo(cameraId, info)
        val rotation = activity.windowManager.defaultDisplay.rotation
        /** The angle by which the picture is rotated clockwise. Valid values are 0, 90, 180 and 270 */
        /** The starting position is 0 (horizontal) */
        var degrees = 0
        when (rotation) {
            Surface.ROTATION_0 -> degrees = 0
            Surface.ROTATION_90 -> degrees = 90
            Surface.ROTATION_180 -> degrees = 180
            Surface.ROTATION_270 -> degrees = 270
        }
        var result: Int
        if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
            result = (info. orientation + degrees) % 360
            result = (360 - result) % 360 // compensate the mirror
        } else {
            /** back side */
            result = (info. orientation - degrees + 360) % 360
        }
        camera!!.setDisplayOrientation(result)
    }

    /**
     * Open the camera panel
     */
    private fun openFrontFacingCameraGingerbread(): Camera? {
        var cameraCount = 0
        var cam: Camera? = null
        val cameraInfo = CameraInfo()
        cameraCount = Camera. getNumberOfCameras()
        for (camIdx in 0 until cameraCount) {
            Camera. getCameraInfo(camIdx, cameraInfo)
            try {
                cam = Camera. open(camIdx)
                mCurrentCamIndex = camIdx
            } catch (e: RuntimeException) {
                LogUtil.e("Camera failed to open: " + e.localizedMessage)
            }
        }
        return cam
    }

    /**
     * get photo
     */
    fun getSurfacePic(data: ByteArray?, camera: Camera) {
        val size = camera.parameters.previewSize
        val image = YuvImage(data, ImageFormat. NV21, size. width, size. height, null)
        if (image != null) {
            val stream = ByteArrayOutputStream()
            image.compressToJpeg(Rect(0, 0, size.width, size.height), 80, stream)
            val bmp = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size())
            /** Because the picture will be rotated freely, the picture should be rotated to the same direction as the phone */
            rotateMyBitmap(bmp)
        }
    }

    /**
     * Rotate picture
     */
    fun rotateMyBitmap(bmp: Bitmap) {
        val matrix = Matrix()
        matrix. postRotate(0f)
        val nbmp2 = Bitmap.createBitmap(bmp, 0, 0, bmp.width, bmp.height, matrix, true)
        saveMyBitmap(FileUtil.compressImage(nbmp2))
    }

    /**
     * save Picture
     */
    fun saveMyBitmap(mBitmap: Bitmap?) {
        if (FileUtil. getAvailableSize() > 512) {
            val filePic: File? = FileUtil. saveBitmap(mBitmap)
            if (filePic == null) {
                /** Image failed to save */
                listener.onFail("Image save failed")
                return
            }
            FileUtil.compressPic(activity, filePic, object : OnCompressListener {
                override fun onStart() {
                    // TODO Called before the compression starts, you can start the loading UI in the method
                }

                override fun onSuccess(file: File?) {
                    // TODO is called after the compression is successful, and returns the compressed image file
                    FileUtil.deleteFile(filePic)
                    listener.onSuccess(filePic, mBitmap)
                }

                override fun onError(e: Throwable?) {
                    // TODO called when there is a problem with the compression process
                    LogUtil.e("compressPic error")
                }
            })
        } else {
            listener.onFail("The storage space is less than 512M, the picture cannot be saved normally")
        }
    }

    /**
     * Get the current photo of the camera
     */
    fun takePhoto() {
        canTake = true
    }

    /**
     * freed
     */
    fun destroy() {
        hasSurface = false
    }
}

3. Fragment’s java file

package com.doshare.boardroom.view.fragment

import android.Manifest
import android.graphics.Bitmap
import android.os.Bundle
import android.view.LayoutInflater
import android.view.SurfaceHolder
import android.view.View
import android.view.ViewGroup
import androidx.core.app.ActivityCompat
import butterknife. ButterKnife
import butterknife. Unbinder
import cn.finalteam.galleryfinal.permission.EasyPermissions
import com.doshare.boardroom.R
import com.doshare.boardroom.cameratake.SurfaceViewCallback
import com.doshare.boardroom.cameratake.listener.CameraTakeListener
import com.doshare.boardroom.cameratake.utils.LogUtil
import com.doshare.boardroom.view.activity.IMainActivity
import com.doshare.boardroom.view.enums.PageEnum
import kotlinx.android.synthetic.main.fragment_config_access.*
import java.io.File


class ConfigAccessFragment : BaseFragment(), CameraTakeListener {

 
    var unbinder: Unbinder? = null
    /** Permission related */
    private val GETPERMS = 100

    private var perms = arrayOfNulls<String>(3)

    var surfaceHolder: SurfaceHolder? = null

    lateinit var surfaceViewCallback: SurfaceViewCallback


    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        // Inflate the layout for this fragment
     
        return inflater.inflate(R.layout.fragment_config_access, container, false)
    }


    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

        unbinder = activity?.let { ButterKnife.bind(it) }

        perms = arrayOf(
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.CAMERA
        )

        surfaceViewCallback = activity?.let { SurfaceViewCallback(activity!!, this) }!!

        surfaceHolder = congig_access_SurfaceView.holder

        surfaceHolder?.addCallback(surfaceViewCallback)

        surfaceHolder?.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)

        checkPermission()

   

        btn_one.setOnClickListener{
                /** Click to take a photo to get a photo */
            this. takePhoto()
        }

        super.onViewCreated(view, savedInstanceState)
    }

    override fun onDestroy() {
        super. onDestroy()
        unbinder!!.unbind()
        this. destroy()
    }
    /**
     * Get the current photo of the camera
     */
    fun takePhoto() {
        surfaceViewCallback. takePhoto()
    }

    fun destroy() {
        surfaceViewCallback.destroy()
    }

    override fun onSuccess(bitmapFile: File?, mBitmap: Bitmap?) {
        iv_photo.setImageBitmap(mBitmap)
        if (bitmapFile != null) {

            tv_pic_dir.setText("picture path:" + bitmapFile.path)
        }
    }

    override fun onFail(error: String?) {
        LogUtil.e(error)
    }

    fun checkPermission() {
        //Determine whether there are relevant permissions and apply for permissions
        if (!EasyPermissions. hasPermissions(context, *perms)) {
            activity?.let {
                ActivityCompat. requestPermissions(it, perms, GETPERMS)
            }
        }
    }

    @Deprecated("Deprecated in Java")
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String?>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

    }



}

Note: The code in this article is quoted from the Internet, and the original text link is attached: Android uses SurfaceView + Camera to realize no-stop photography (acquisition and storage of camera preview images) – Nuggets (juejin.cn)y The original text is to implement the camera in the activity The preview function uses JAVA language, and after converting to Kotlin language, there will be a black screen problem when used in the Fragment interface, so I rewritten it