Android开发学习——相机使用。

90 阅读3分钟

官方文档: 跟着官方文档走,踩了不少坑,耽误了蛮久时间。

1.首先是获取相册权限

2.打开相机

新建一个Fragment名字就叫做 CameraFragment

xml布局文件中的完整代码

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <View
        android:id="@+id/overlay"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:foreground="@android:color/transparent" />

    <androidx.camera.view.PreviewView
        android:id="@+id/cameraSurfacePreview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    <ImageButton
        android:id="@+id/capture_button"
        android:layout_width="96dp"
        android:layout_height="96dp"
        android:layout_gravity="bottom|center"
        android:scaleType="fitCenter"
        android:background="@drawable/ic_launcher_background"/>

</FrameLayout>

吧 ic_launcher_background 换成你自己的图标。

CameraFragment

//创建相机时非常重要的元素
lateinit var surfacePreview: PreviewView
//捕捉拍照的图标
lateinit var imageCapture: ImageCapture
override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    Log.i("onCreateView", "onCreateView")
    val view = inflater.inflate(R.layout.fragment_camera, container, false)
    view.findViewById<ImageButton>(R.id.capture_button).setOnClickListener {

    }
    surfacePreview=view.findViewById(R.id.cameraSurfacePreview);
    return view
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    Log.i("onCreateView", "onCreateView")
    val view = inflater.inflate(R.layout.fragment_camera, container, false)
    view.findViewById<ImageButton>(R.id.capture_button).setOnClickListener {

    }
    surfacePreview=view.findViewById(R.id.cameraSurfacePreview);
    return view
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    //获取相册权限
    requestPermissionExt(
        Manifest.permission.CAMERA,
    ){ it ->
        if (it) {

        } else {
            Toast.makeText(context,
                "Permissions not granted by the user.",
                Toast.LENGTH_SHORT).show()

        }
    }
}

下面复制官方代码

private fun startCamera() {
   val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

   cameraProviderFuture.addListener(Runnable {
       // Used to bind the lifecycle of cameras to the lifecycle owner
       val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

       // Preview
       val preview = Preview.Builder()
          .build()
          .also {
              it.setSurfaceProvider(viewFinder.createSurfaceProvider())
          }

       // 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(
               this, cameraSelector, preview)

       } catch(exc: Exception) {
           Log.e(TAG, "Use case binding failed", exc)
       }

   }, ContextCompat.getMainExecutor(this))
}

代码中会出现一个错误

it.setSurfaceProvider(viewFinder.createSurfaceProvider())

viewFinde是什么东西?

我通过查看官方提供的Demo,他是这个。

<com.example.android.camera.utils.AutoFitSurfaceView
    android:id="@+id/view_finder"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

如果想要使用它还需要引入官方Demo代码中的 com.example.android.camera.utils这个Module。

到了这里有点疑惑了,我就想用个相机怎么这么麻烦?不应该是一句代码就能搞定的吗?

然后各种百度,终于找到了

image.png

在 Fragment 中获取他

image.png

然后使用

it.setSurfaceProvider(surfacePreview.createSurfaceProvider())

最后修改请求权限的代码,在授权成功的回调里,调用启动相机,

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

    requestPermissionExt(
        Manifest.permission.CAMERA,
    ){ it ->
        if (it) {
            //授权成功,或者已授权
            context?.let {
                startCamera(it) }
        } else {
            Toast.makeText(context,
                "Permissions not granted by the user.",
                Toast.LENGTH_SHORT).show()

        }
    }
}

使用的是 BottomNavigationView 搭建的项目骨架, 在任意一个页面添加一个按钮,点击跳转过去

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    Log.i("onViewCreated", "onViewCreated")
    //回来的时候展示底部导航栏
    (activity as MainActivity).showBottomNavigationView()
    
    var button = mainView.findViewById<Button>(R.id.me_buttom)
    button.setOnClickListener {
        Log.i("setOnClickListener", "你点我干哈")
        //跳转到相机页面,并隐藏底部导航栏。
        findNavController().navigate(R.id.navigation_camera)
        (activity as MainActivity).hideBottomNavigationView()

    }

}

编译代码跳转到这个新的 相册管理 Fragment ,并同意应用获取相册权限后,摄像头就会被打开了。

3.拍照 获取到图片

和官方代码类似 添加了一个 outputDirectory的初始化过程

outputDirectory = getOutputDirectory(context)
private fun takePhoto(context: Context) {
    // Get a stable reference of the modifiable image capture use case
    val imageCapture = imageCapture ?: return
    outputDirectory = getOutputDirectory(context)
    //创建一个容纳图像的文件。添加时间戳,以避免文件名重复。
    val photoFile = File(
        outputDirectory,
        SimpleDateFormat("yyyy_MM_ddHH_mm_ss", Locale.US
        ).format(System.currentTimeMillis()) + ".jpg")

    // 创建 OutputFileOptions 对象。您可以在此对象中指定有关输出方式的设置。如果您希望将输出内容保存在刚创建的文件中,则添加您的 photoFile。
    val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()

    // 对 imageCapture 对象调用 takePicture()。传入执行程序 outputOptions 以及在保存图像时使用的回调。接下来,您将填写回调。
    imageCapture.takePicture(
        outputOptions, ContextCompat.getMainExecutor(context), object : ImageCapture.OnImageSavedCallback {
            //在图像拍摄失败或图像拍摄结果保存失败的情况下,添加一个错误示例,以记录失败情况。
            override fun onError(exc: ImageCaptureException) {
                Log.e("onError", "Photo capture failed: ${exc.message}", exc)
            }
            //如果拍摄未失败,则表示拍照成功!将照片保存到您先前创建的文件中,显示一个消息框以告知用户操作成功,然后输出日志语句。
            override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                val savedUri = Uri.fromFile(photoFile)
                val msg = "Photo capture succeeded: $savedUri"
                Toast.makeText(context, msg, Toast.LENGTH_SHORT).show()
                Log.d("onImageSaved", msg)
            }
        })
}

然后在 onCreateView中 按钮点击的回调中,调用拍照函数。

image.png

成功之后Toast会弹出拍照图片的存放路径。

我们可以吧这个 图片路径返回上个页面让他展示出来。

界面传值还不会,正在看 EventBus 这个插件。