Android 从相册选择照片,或者拍照

文章目录

    之前实现了 Android 拍照获取图片,现在需要再加上从相册选择照片。

    交互

    其实有多种交互方式:

    1. 界面上直接放两个按钮:一个是拍照,一个是从相册选取。简单直接。
    2. 点击拍照按钮,弹出一个选择框,提供两个选项,一拍照,二从相册选择

    对于界面空间富裕的场景,我觉得第一种方式就非常方便了。当然,我也懒得搞那么复杂。。。

    可是微信里面那个相机为啥那么方便呢?是否是自定义实现的相机,而不是调用的默认的相机应用。

    kotlin registerForActivityResult 实现,目前推荐

    registerForActivityResult 并没有 startActivityForResult 的 requestCode 来判定从哪个 activity 返回的,
    但是可以通过定义多个 launch 来区分,以使用不同的回调处理函数。

    目前只是猜测,需要看看官方是否有使用说明,以及 github 上的开源代码来做参考。

    https://stackoverflow.com/questions/62387789/capture-image-from-camera-gallery-and-display-in-activity-fragment-using-kotlin

    看,确实跟我猜测的一样。

    startActivityForResult 配合 onActivityResult,需要 requestCode 是因为没法指定回调函数,
    只能使用固定的 onActivityResult 来处理,所以必须配一个 requestCode。
    而 registerForActivityResult 则可以自由地生成 N 个对应的处理回调,那么确实就不需要 requestCode 了。

    最终实现:

    val pickLauncher = registerForActivityResult(ActivityResultContracts.GetContent()){ uri: Uri? ->
        uri?.let { it ->
            Log.d("tag", it.toString())
            val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                ImageDecoder.decodeBitmap(ImageDecoder.createSource(this.contentResolver, it))
            } else {
                MediaStore.Images.Media.getBitmap(this.contentResolver, it)
            }
            handleCameraImage(bitmap)
        }
    }
    
    val btnPickPicture = findViewById<Button>(R.id.btnPickPicture)
    btnPickPicture.setOnClickListener {
        pickLauncher.launch("image/*")
    }
    

    registerForActivityResult 的实现非常贴心,之前是用通用的方法ActivityResultContracts.StartActivityForResult(),这里用的是更简化的 ActivityResultContracts.GetContent(),可以直接获取到 Uri。

    旧的代码实现 startActivityForResult, 已废弃

    可以参考这里的做法:

    https://stackoverflow.com/questions/10165302/dialog-to-pick-image-from-gallery-or-from-camera

    Intent takePicture = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    startActivityForResult(takePicture, 0);//zero can be replaced with any action code (called requestCode)
    
    
    Intent pickPhoto = new Intent(Intent.ACTION_PICK,
               android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    startActivityForResult(pickPhoto , 1);//one can be replaced with any action code
    
    
    protected void onActivityResult(int requestCode, int resultCode, Intent imageReturnedIntent) { 
        super.onActivityResult(requestCode, resultCode, imageReturnedIntent); 
        switch(requestCode) {
        case 0:
            if(resultCode == RESULT_OK){  
                Uri selectedImage = imageReturnedIntent.getData();
                imageview.setImageURI(selectedImage);
            }
    
        break; 
        case 1:
            if(resultCode == RESULT_OK){  
                Uri selectedImage = imageReturnedIntent.getData();
                imageview.setImageURI(selectedImage);
            }
        break;
        }
    }
    

    registerForActivityResult 的介绍

    Android 官方虽然有介绍,但是太简陋了,没有更多的细节和使用教程。

    https://developer.android.com/training/basics/intents/result

    各种 Intent 的区别

    Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)

    弹出选择框,相册,或是文件管理。而文件管理中,没有文件。

    ACTION_GET_CONTENT 与 ACTION_OPEN_DOCUMENT 的区别

    https://stackoverflow.com/questions/36182134/what-is-the-real-difference-between-action-get-content-and-action-open-document

    《第一行代码》里是用的 ACTION_OPEN_DOCUMENT 配合 intent type image 来实现的相册图片选择。

    ACTION_OPEN_DOCUMENT is not intended to be a replacement for ACTION_GET_CONTENT. The one you should use depends on the needs of your app:

    • Use ACTION_GET_CONTENT if you want your app to simply read/import data. With this approach, the app imports a copy of the data, such as an image file.
    • Use ACTION_OPEN_DOCUMENT if you want your app to have long term, persistent access to documents owned by a document provider. An example would be a photo-editing app that lets users edit images stored in a document provider.

    暂时没有必要纠结,可以参考网上怎么实现的选取图片并上传,来判定该使用哪个 action。

    uri 的格式

    content://com.android.providers.media.documents/document/image%3A213678
    content://com.miui.gallery.open/raw/%2Fstorage%2Femulated%2F0%2FDCIM%2FScreenshots%2FScreenshot_2022-04-03-10-57-43-787_com.tencent.mm.jpg
    

    会有两种格式:

    • 第一种是直接在弹出的选择框里选择图片
    • 第二种是进入相册,选择的图片

    通过 Uri 初始化一个 Bitmap

    Java 代码:

    Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), imageUri);
    

    Kotlin 代码:

    val bitmap = MediaStore.Images.Media.getBitmap(this.contentResolver, imageUri)
    

    但是 getBitmap 又废弃了。。。

    ‘getBitmap(ContentResolver!, Uri!): Bitmap!’ is deprecated. Deprecated in Java

    拼凑了一个可以执行的代码:

    val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    	ImageDecoder.decodeBitmap(ImageDecoder.createSource(this.contentResolver, imageUri))
    } else {
    	MediaStore.Images.Media.getBitmap(this.contentResolver, imageUri)
    }
    

    参考

    https://stackoverflow.com/questions/3879992/how-to-get-bitmap-from-an-uri

    关于作者 🌱

    我是来自山东烟台的一名开发者,有感兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊,或者关注我的个人公众号“大象工具”, 查看更多联系方式