美文网首页Android技术开发
前戏:一文读懂Android系统的相机使用

前戏:一文读懂Android系统的相机使用

作者: 一点墨汁 | 来源:发表于2019-08-07 16:04 被阅读2次

    如果我们的应用必须在有相机的设备上才能正常使用,那么如何限制只在有相机的设备上安装应用?

    <manifest ... >
        <uses-feature android:name="android.hardware.camera"
                      android:required="true" />
        ...
    </manifest>
    

    如果我们不进行上面的设置或者设置了android:required ="false",那么我们在使用的过程中就要进行动态判断,通过hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)进行判断,如果相机不可用,则禁用相应的功能。

    如何通过系统相机来拍一张照片出来

    val REQUEST_IMAGE_CAPTURE = 1
    
    private fun dispatchTakePictureIntent() {
        Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
            takePictureIntent.resolveActivity(packageManager)?.also {
                startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
            }
        }
    }
    

    分三步来调用系统相机拍照:
    第一步:创建启动相机的意图Intent。
    第二步:通过resolveActivity来判断是否能找到处理该意图Intent的组件。
    第三步:通过startActivityForResult启动改组件。
    拍完照片以后,android相机应用会将拍摄的照片进行返回,我们通过下面的方式来获取返回的数据

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
            if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK) {
                /**
                 * 其中data中返回的是拍摄照片的缩略图
                 */
                val imageBitmap = data?.extras?.get("data") as Bitmap
                imageView.setImageBitmap(imageBitmap)
            }
        }
    

    那么,如果能保存一张全尺寸的图片呢?就需要我们提供一个文件路径给相机应用来进行图片的保存。
    图片保存的位置有两种类型,一种是保存的图片可以被其他应用共享([getExternalStoragePublicDirectory()](https://developer.android.com/reference/android/os/Environment.html#getExternalStoragePublicDirectory(java.lang.String)),
    ),另一种是应用内私有文件目录(getExternalFilesDir())。
    涉及到文件的读写,就需要进行权限的处理。
    如果我们选择是在应用内部私有目录进行文件的保存,也就是使用getExternalFilesDir()。针对这个目录,Android 4.3 以及更低的系统中,需要请求WRITE_EXTERNAL_STORAGE权限,Android4.4开始就不再需要申请该权限。所以只需要在4.3及以下的版本中请求改权限。

    <manifest ...>
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                                     android:maxSdkVersion="18" />
        ...
    </manifest>
    

    如果决定要保存一张图片,可以尝试通过时间戳来为图片进行唯一的命名。

        /**
         * 保存图片
         */
        @Throws(IOException::class)
        private fun createImageFile(): File {
            val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
            val storageDir: File = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
            return File.createTempFile(
                "jpeg_$timeStamp",
                ".jpeg",
                storageDir
            ).apply {
                currentPhotoPath = this.absolutePath
            }
        }
    

    当我们保存了一张照片以后,如何才能访问它呢?Android7.0以后,使用FileProvider
    来进行配置。
    配置如下:

    <application>
       ...
      <provider
              android:authorities="com.happy.camera.fileprovider"
              android:name="androidx.core.content.FileProvider"
              android:exported="false"
              android:grantUriPermissions="true"
              tools:replace="android:authorities">
    
                <meta-data
                      android:name="android.support.FILE_PROVIDER_PATHS"
                      android:resource="@xml/file_paths"
                      tools:replace="android:resource"/>
            </provider>
        ...
    </application>
    

    然后在res/xml/file_paths.xml文件中进行配置

    <?xml version="1.0" encoding="utf-8"?>
    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <external-path name="my_images" path="Android/data/com.happy.camera/files/Pictures"/>
    </paths>
    

    配置完FileProvider,我们看一下如何调用?

    Intent(MediaStore.ACTION_IMAGE_CAPTURE)?.also { takePictureIntent ->
                takePictureIntent.resolveActivity(packageManager)?.also {
                    // 创建图片要保存的文件
                    val photoFile: File? = try {
                        createImageFile()
                    } catch (e: Exception) {
                        null
                    }
    
                    photoFile?.also { file ->
    
                        val photoURI = if (Build.VERSION.SDK_INT >= 24) {
                            FileProvider.getUriForFile(
                                this, "com.happy.camera.fileprovider", file
                            )
                        } else {
                            Uri.fromFile(file)
                        }
    
                        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
                        startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
                    }
                }
            }
    

    如果我们直接处理多张全尺寸的图片会非常消耗内存。所以我们在显示图片的时候都需要对图片的尺寸进行合适的裁剪。

    private fun setPic(){
            // 获得imageView的宽度和高度
            val targetW:Int = imageView.width
            val targetH:Int = imageView.height
    
            val bmOptions = BitmapFactory.Options().apply{
                inJustDecodeBounds = true
                val photoW:Int = outHeight
                val photoH:Int = outWidth
                // 获得图片的裁剪比例
                val scaleFactor:Int = Math.min(photoW/targetW,photoH/targetH)
                inJustDecodeBounds = false
                inSampleSize = scaleFactor
            }
    
            BitmapFactory.decodeFile(currentPhotoPath,bmOptions)?.also {
                bitmap ->
                imageView.setImageBitmap(bitmap)
            }
        }
    

    我们在来看一下onActivityResult()方法,如果我们自定义了Uri,那么data:Intent拿到的data数据就会返回null,我们就需要从当前图片的路径来获取图片资源。

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {}
    

    当我们使用相机拍摄照片后,经常会遇到显示的图片相对于实际拍摄照片的进行旋转,这就需要我们在显示图片的时候对图片的角度进行一下判断和处理。

     /**
      * 首先,读取照片的角度
     */
    private fun readPictureDegree(path:String):Int{
            var degree = 0
            val exifInterface = ExifInterface(path)
            when(exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL)){
                ExifInterface.ORIENTATION_ROTATE_90  ->
                    degree = 90
                ExifInterface.ORIENTATION_ROTATE_180 ->
                    degree = 180
                ExifInterface.ORIENTATION_ROTATE_270 ->
                    degree = 270
            }
            return degree
    }
    
    /**
     * 然后,对图片进行旋转
     */
     private fun rotateBitmap(angle: Int, bitmap: Bitmap): Bitmap {
            var returnBmp: Bitmap? = null
            var matrix = Matrix()
            matrix.postRotate(angle.toFloat())
            try {
                returnBmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
            } catch (e: Exception) {
            }
            if (returnBmp == null) {
                returnBmp = bitmap
            }
            if (bitmap != returnBmp) {
                bitmap.recycle()
            }
            return returnBmp
        }
    

    最后补充一下onActivityResult()方法

    
        override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
            super.onActivityResult(requestCode, resultCode, data)
            if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK) {
                setPic()
            }
        }
    
        private fun setPic() {
            // 获得imageView的宽度和高度
            val targetW: Int = imageView.width
            val targetH: Int = imageView.height
    
            val bmOptions = BitmapFactory.Options().apply {
                inJustDecodeBounds = true
                val photoW: Int = outHeight
                val photoH: Int = outWidth
                // 获得图片的裁剪比例
                val scaleFactor: Int = Math.min(photoW / targetW, photoH / targetH)
                inJustDecodeBounds = false
                inSampleSize = scaleFactor
            }
    
            BitmapFactory.decodeFile(currentPhotoPath, bmOptions)?.also { bitmap ->
                val degree = readPictureDegree(currentPhotoPath)
                if (degree != 0) {
                    // 旋转图像
                    val resultBitmap = rotateBitmap(degree, bitmap)
                    imageView.setImageBitmap(resultBitmap)
                } else {
                    imageView.setImageBitmap(bitmap)
                }
            }
        }
    

    如何添加拍摄的照片到相册?(如果是保存在getExternalFilesDir()目录下,那么media scanner就不能访问该文件,因为这些照片是app私有的)

    fun galleryAddPic(view: View) {
            Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent ->
                val f = File(currentPhotoPath)
                mediaScanIntent.data = Uri.fromFile(f)
                sendBroadcast(mediaScanIntent)
            }
        }
    

    通过上面的步骤,我们知道了如何通过系统相机来拍摄一张照片并保存在手机上,那么如何通过系统相机来录制一段视频呢?
    首先,也是要先确认我们有没有添加摄像头的feature

    <uses-feature
                android:name="android.hardware.camera"
                android:required="true" />
    

    然后,我们使用一个VideoView来简单展示我们录制的视频

        /**
         * 调用系统相机开始录制
         */
        fun captureVideoBtn(view: View) {
    
            Intent(MediaStore.ACTION_VIDEO_CAPTURE).also { takeVideoIntent ->
                takeVideoIntent.resolveActivity(packageManager)?.also {
                    startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE)
                }
            }
        }
    
        /**
         * 对返回的数据进行使用
         */
        override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
    
            if (requestCode == REQUEST_VIDEO_CAPTURE && resultCode == Activity.RESULT_OK) {
                val videoUri = intent?.data
                showVideoView.setVideoURI(videoUri)
                showVideoView.start()
                showVideoView.requestFocus()
            }
        }
    

    相关文章

      网友评论

        本文标题:前戏:一文读懂Android系统的相机使用

        本文链接:https://www.haomeiwen.com/subject/rudxdctx.html