文章

调用摄像头和相册

创建一个项目,用户可以选择拍照或者是从文件中选择图片,然后显示在ImageView中。

首先修改activity_main.xml中代码:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:id="@+id/takePhotoBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Take Photo" />                                                     
    <Button
        android:id="@+id/fromAlbumBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="From Album" />  
    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />
</LinearLayout>

一、调用摄像头拍照

class MainActivity : AppCompatActivity() {
    val takePhoto = 1
    lateinit var imageUri: Uri
    lateinit var outputImage: File
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        takePhotoBtn.setOnClickListener {
            // 创建File对象,用于存储拍照后的图片
            // 将图片存在当前应用缓存数据位置
            outputImage = File(externalCacheDir, "output_image.jpg")
            if(outputImage.exists()) {
                outputImage.delete()
            }
            outputImage.createNewFile()
            // 用户版本是否大于Android 7.0
            // 从7.0开始,直接使用本地真实路径Uri是不安全的。
            imageUri = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                FileProvider.getUriForFile(this, 
                    "com.example.test.fileprovider",outputImage)
            } else {
                Uri.fromFile(outputImage)
            }
            
            // 启动相机程序
            val intent = Intent("android.media.action.IMAGE_CAPTURE")
            intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)
            startActivityForResult(intent, takePhoto)
        }
    }
    
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode,data)
        when (requestCode) {
            takePhoto -> {
                if (resultCode == Activity.RESULT_OK) {
                    // 将拍摄的照片显示出来
                    val bitmap = BitmapFactory.decodeStream(contentResolver.
                        openInputStream(imageUri))
                    imageView.setImageBitmap(rotateIfRequired(bitmap))
                }
            }
        }
    }
    
    // 拍照可能在一些手机上发生照片旋转的情况
    // 因为手机认为摄像头拍摄时手机就应该是横屏的
    // 因此回到竖屏就会发生90度旋转,这里就判断图片方向
    // 判断图片是否需要旋转。
    private fun rotateIfRequired(bitmap: Bitmap): Bitmap {
        val exif = ExifInterface(outputImage.path)
        val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
            ExifInterface.ORIENTATION_NORMAL)
        return when (orientation) {
            ExifInterface.ORIENTATAION_ROTATE_90 ->
                rotateBitmap(bitmap, 90)
            ExifInterface.ORIENTATAION_ROTATE_180 ->
                rotateBitmap(bitmap, 180)
            ExifInterface.ORIENTATAION_ROTATE_270 ->
                rotateBitmap(bitmap, 270)
        }
    }
    
    private fun rotateBitmap(bitmap: Bitmap, degree: Int): Bitmap {
        val matrix = Matrix()
        matrix.postRotate(degree.toFloat())
        val rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
            bitmap.width, bitmap.height, matrix, true)
        // 将不再需要的Bitmap对象回收
        bitmap.recycle()
        return rotatedBitmap
    }
}

FileProvider是一种特殊的ContentProvider,它使用了和ContentProvider类似的机制来对数据进行保护,可以选择性地将封装过的Uri共享给外部,从而提高了应用的安全性。

不过现在还没结束,刚才提到了ContentProvider,那么我们自然要在AndroidManifest.xml中对它进行注册才行:

<applicaion ...>
    ...
    <provider
        <!-- 固定值 -->
        android:name="androidx.core.content.FileProvider"
        <!-- 对应FileProvider.getUriForFile()第二个参数 -->
        android:authorities="com.example.test.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            <!-- 指定Uri的共享路径 -->
            android:name="android.support.FILE_PROVIDER_PATHS"
            <!-- 引用资源,需要新建 -->
            android:resource="@xml/file_paths" />
    </provider>
</application>

新建file_paths.xml文件,在res目录下新建xml目录,并新建一个file_paths.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 指定Uri共享,name可以随便填,path表示共享的具体路径 -->
    <!-- 这里的单斜线表示将整个SD卡进行共享,也可以仅共享output_image.jpg图片路径 -->
    <external-path name="my_images" path="/" />
</paths>

二、从相册中选择图片

class MainActivity : AppCompatActivity() {
    ...
    val fromAlbum = 2
    
    override fun onCreate(sacedInstanceState: Bundle?) {
        ...
        fromAlbumBtn.setOnClickListener {
            // 打开文件选择器
            val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
            intent.addCategory(Intent.CATEGORY_OPENABLE)
            // 指定只显示图片
            intent.type = "image/*"
            startActivityForResult(intent, fromAlbum)
        }
    }
    
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode,resultCode,data)
        when(requestCode) {
            ...
            fromAlbum -> {
                if(resultCode == Activity.RESULT_OK && data != null) {
                    // 调用intent的getData()方法来获取选中图片uri
                    data.data?.let { uri ->
                        // 将选择的图片显示
                        val bitmap = getBitmapFromUri(uri)
                        imageView.setImageBitmap(bitmap)
                    }
                }
            }
        }
    }
    
    private fun getBitmapFromUri(uri: Uri) = contentResolver
        .openFileDescriptor(uri, "r")?.use {
            BitmapFactory.decodeFileDescriptor(it.fileDescriptor)
    }
    
    ...
}
License:  CC BY 4.0