安卓使用Kotlin在Fragment中使用SurfaceView+Camera实现相机预览并拍照保存显示的功能

1.fragment的xml文件中所需的控件

<Button
        android:id="@+id/btn_one"
        app:backgroundTint="@color/white"
        android:text="点击拍照"
        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="图片路径:"
        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.创建工具类和接口

2.1 CameraTakeListener 接口

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工具类

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卡大小相关变量
    //获得Sdcard上每个block的size
    //获取可供程序使用的Block数量
    //计算标准大小使用:1024,当然使用1000也可以
    /**
     * 计算Sdcard的剩余大小
     *
     * @return MB
     */
    fun  getAvailableSize():Long
         {
            //sd卡大小相关变量
            val statFs: StatFs
            val file = Environment.getExternalStorageDirectory()
            statFs = StatFs(file.path)
            //获得Sdcard上每个block的size
            val blockSize = statFs.blockSize.toLong()
            //获取可供程序使用的Block数量
            val blockavailable = statFs.availableBlocks.toLong()
            //计算标准大小使用:1024,当然使用1000也可以
            return blockSize * blockavailable / 1024 / 1024
        }//获得sdcard上 block的总数
    //获得sdcard上每个block 的大小
    //计算标准大小使用:1024,当然使用1000也可以

    /**
     * SDCard 总容量大小
     *
     * @return MB
     */
    val totalSize: Long
        get() {
            val statFs: StatFs
            val file = Environment.getExternalStorageDirectory()
            statFs = StatFs(file.path)
            //获得sdcard上 block的总数
            val blockCount = statFs.blockCount.toLong()
            //获得sdcard上每个block 的大小
            val blockSize = statFs.blockSize.toLong()
            //计算标准大小使用:1024,当然使用1000也可以
            return blockCount * blockSize / 1024 / 1024
        }

    /**
     * 保存bitmap到本地
     *
     * @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
    }

    /**
     * 压缩图片
     *
     * @param image
     * @return
     */
    fun compressImage(image: Bitmap): Bitmap? {
        val baos = ByteArrayOutputStream()
        /** 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 */
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos)
        /** 把压缩后的数据baos存放到ByteArrayInputStream中 */
        val isBm = ByteArrayInputStream(baos.toByteArray())

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

    /**
     * 文件夹删除
     */
    fun deleteFile(file: File) {
        if (file.isDirectory) {
            val files = file.listFiles()
            for (i in files.indices) {
                val f = files[i]
               deleteFile(f)
            }
            file.delete() //如要保留文件夹,只删除文件,请注释这行
        } else if (file.exists()) {
            file.delete()
        }
    }

    /**
     * 压缩图片文件
     */
    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 工具类

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

    /**
     * 得到tag(所在类.方法(L:行))
     *
     * @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)
            )
        )
        //给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 工具类

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

    /** 为true时则开始捕捉照片 */
    var canTake = false

    /** 拍照回调接口 */
    var listener: CameraTakeListener

    init {
        this.listener = listener
    }

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

                listener.onFail("没有可用的摄像头")
                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
    }

    /**
     * 设置照相机播放的方向
     */
    private fun setCameraDisplayOrientation(activity: Activity, cameraId: Int, camera: Camera?) {
        val info = CameraInfo()
        Camera.getCameraInfo(cameraId, info)
        val rotation = activity.windowManager.defaultDisplay.rotation
        /** 度图片顺时针旋转的角度。有效值为0、90、180和270 */
        /** 起始位置为0(横向) */
        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 {
            /** 背面 */
            result = (info.orientation - degrees + 360) % 360
        }
        camera!!.setDisplayOrientation(result)
    }

    /**
     * 打开摄像头面板
     */
    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
    }

    /**
     * 获取照片
     */
    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())
            /** 因为图片会放生旋转,因此要对图片进行旋转到和手机在一个方向上 */
            rotateMyBitmap(bmp)
        }
    }

    /**
     * 旋转图片
     */
    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))
    }

    /**
     * 保存图片
     */
    fun saveMyBitmap(mBitmap: Bitmap?) {
        if (FileUtil.getAvailableSize() > 512) {
            val filePic: File? = FileUtil.saveBitmap(mBitmap)
            if (filePic == null) {
                /** 图片保存失败 */
                listener.onFail("图片保存失败")
                return
            }
            FileUtil.compressPic(activity, filePic, object : OnCompressListener {
                override fun onStart() {
                    // TODO 压缩开始前调用,可以在方法内启动 loading UI
                }

                override fun onSuccess(file: File?) {
                    // TODO 压缩成功后调用,返回压缩后的图片文件
                    FileUtil.deleteFile(filePic)
                    listener.onSuccess(filePic, mBitmap)
                }

                override fun onError(e: Throwable?) {
                    // TODO 当压缩过程出现问题时调用
                    LogUtil.e("compressPic error")
                }
            })
        } else {
            listener.onFail("存储空间小于512M,图片无法正常保存")
        }
    }

    /**
     * 获取相机当前的照片
     */
    fun takePhoto() {
        canTake = true
    }

    /**
     * 释放
     */
    fun destroy() {
        hasSurface = false
    }
}

3. fragment的java文件

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
    /** 权限相关 */
    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{
                /** 点击拍照获取照片 */
            this.takePhoto()
        }

        super.onViewCreated(view, savedInstanceState)
    }

    override fun onDestroy() {
        super.onDestroy()
        unbinder!!.unbind()
        this.destroy()
    }
    /**
     * 获取相机当前的照片
     */
    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("图片路径:" + bitmapFile.path)
        }
    }

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

    fun checkPermission() {
        //判断是否有相关权限,并申请权限
        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)

    }



}

注:文中代码引用自互联网,附原文链接:Android使用SurfaceView+Camera实现无卡顿拍照(相机预览图像的获取与保存) - 掘金 (juejin.cn)y原文为是在activity实现相机预览功能,使用的是JAVA语言,且在转为Kotlin语言后,用在Fragment界面中时会出现黑屏问题,故我进行了改写

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小趴菜8227

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值