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