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

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

<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()
        }
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,063评论 6 510
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,805评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,403评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,110评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,130评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,877评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,533评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,429评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,947评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,078评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,204评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,894评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,546评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,086评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,195评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,519评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,198评论 2 357

推荐阅读更多精彩内容