在Android中实现一键拍照或从图库选择图片的单一意图。

66

我正在开发适用于Android 2.1及以上版本的应用程序。我想让用户在我的应用程序内选择个人资料图片(我没有使用联系人框架)。

理想的解决方案是触发一个意图,使用户能够从库中选择一张图片,但如果没有合适的图片,则使用相机拍摄照片(或者反过来,即允许用户拍照,但如果他们知道已经有一个合适的图像,就让他们进入库并选择所述图像)。

目前,我只能做到其中一种。

如果我使用MediaStore.ACTION_IMAGE_CAPTURE直接进入相机模式,则没有选项可以进入库。

如果我使用Intent.ACTION_PICK直接进入库,则可以选择图像,但如果我点击库右上角的相机按钮,则会触发新的相机意图。因此,任何拍摄的图片都不会直接返回到我的应用程序。(当然,您可以按下返回按钮回到库中并从那里选择图像,但这是一个额外且不必要的步骤,也不直观)。

那么是否有办法将两者结合起来,或者我必须在我的应用程序中提供菜单来进行其中之一?似乎这将是一个常见的使用情况……我肯定错过了什么?


请检查此答案:https://dev59.com/hW855IYBdhLWcg3wPBz6#43679238 - A-Droid Tech
https://dev59.com/N4bca4cB1Zd3GeqPaMdt - Aditya Vyas-Lakhan
4个回答

120

您可以尝试像这样做:

// ...
// Within your enclosing Class
// ...
private static final int SELECT_PICTURE = 1;

// ... 

Intent pickIntent = new Intent();
pickIntent.setType("image/*");
pickIntent.setAction(Intent.ACTION_GET_CONTENT);

Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

String pickTitle = "Select or take a new Picture"; // Or get from strings.xml
Intent chooserIntent = Intent.createChooser(pickIntent, pickTitle);
chooserIntent.putExtra
(
  Intent.EXTRA_INITIAL_INTENTS, 
  new Intent[] { takePhotoIntent }
);

startActivityForResult(chooserIntent, SELECT_PICTURE);

要了解如何处理活动结果,请参考此问题


注意:一个关键点是如何确定使用相机还是图库。这在这个代码示例中显示:https://dev59.com/hW855IYBdhLWcg3wPBz6#12347567


我认为这应该是最有帮助的答案!我自己尝试过,而且运行得非常顺畅。 - retromuz
1
不幸的是,如果EXTRA_INITIAL_INTENTS解析到多个活动,则此方法无效,因为它们在原始选择器中未合并。相反,在选择器中出现了一个系统项,当选中时,会打开另一个选择器,用户可以选择处理EXTRA_INITIAL_INTENTS的应用程序。由于您无法控制安装在设备上执行某个任务的应用程序数量,因此这种解决方案只是一厢情愿的想法,对所有实际用途都有缺陷。 - Giulio Piancastelli
1
@GiulioPiancastelli 即使您的逻辑是正确的,在实际使用中,这个解决方案也非常有帮助。一个用户可能已经选择了其中一个作为默认摄像头应用程序,然后就不会再得到第二个选择器了。在额外意图是相机的情况下,它符合我的需求。 - Christian
@Macarse,你的问题链接错了,老兄!糟糕透了! :) - Fattie
1
另一个类似但更详细的精彩答案,展示如何确定相机或图库。https://dev59.com/hW855IYBdhLWcg3wPBz6#12347567 - Fattie
有人知道如何避免@GiulioPiancastelli提到的“系统项”吗? 我发现这只是在Android M(6.x)上出现的问题。 任何提示将不胜感激! - Forrest Bice

13

更新:另一个答案使用EXTRA_INITIAL_INTENTS更好。在我写下这个答案时,EXTRA_INITIAL_INTENTS还不存在,因为它是在API级别5中添加的。

那么有没有办法将两者结合起来,或者我必须提供一个菜单来在我的应用程序中执行其中之一?

编写您自己的图库,具有所需的功能。

我认为使用菜单会更简单。

似乎这将是一种常见的用例…… 我肯定错过了什么?

坐在你旁边的开发人员会认为图库应该允许你从本地图库中选择或跳转到Flickr以从那里进行选择。 另一个开发者会认为相机不仅应该通过相机拍摄照片,还可以通过从图库中选择图片来拍摄照片,这与您设想的方式相反。 另一个开发人员会认为图库应该允许从本地图库、Flickr、相机或网络连接的网络摄像头中进行选择。 还有另一个开发人员会认为图库很傻,用户应该通过文件资源管理器选择文件。 等等。

所有这些都发生在一个闪存资源紧缺的环境(移动电话)中。

因此,在我看来,核心Android团队决定为您提供构建块,让您根据自己的需求进行组装,而不是试图适应每种可能的模式,这一点并不完全令人震惊。


好的,谢谢马克。作为你《CommonsWare》系列书籍的忠实读者,我会把这个当做明确的答复。现在,我会在我的应用程序中实现一个菜单,并祈祷G能够在预计的下个月的Froyo发布中为我提供一些东西。2.1上的Gallery应用程序非常好,我很想能够重用/自定义它来适应这种情况。 - Damian
是的,你的想法(ACTION_PICK后跟相机图标会拍照,添加并返回图片)绝对不是疯狂的。你可能需要仔细研究一下早期Froyo版本的Gallery代码,看看他们在那里做了什么。 - CommonsWare
4
通常,当你想说“我希望用户为我选择某种类型的X内容”时,你会使用 GET_CONTENT。例如,在这里,你会使用带有 type image/* 的 GET_CONTENT,它会首先向用户呈现一个列表,供他们选择图片(相册、相机和其他拥有可供选择的图像的应用程序)。 - hackbod
6
除此之外,这样做不提供使用相机的选项。拍照需要使用MediaStore.ACTION_IMAGE_CAPTURE操作,选择现有图像需要Intent.ACTION_PICK操作...因此,在不执行CommonsWare建议的操作的情况下,它们基本上变得互斥。 - Justin

10

在您的Activity中,您可以按照以下方式进行:

private static final int REQUEST_CODE_PICTURE= 1;

    /**
     * Click on View to change photo. Sets into View of your layout, android:onClick="clickOnPhoto"
     * @param view View
     */
    public void clickOnPhoto(View view) {
        Intent pickIntent = new Intent();
        pickIntent.setType("image/*");
        pickIntent.setAction(Intent.ACTION_GET_CONTENT);
        Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        String pickTitle = "Take or select a photo";
        Intent chooserIntent = Intent.createChooser(pickIntent, pickTitle);
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { takePhotoIntent });
        startActivityForResult(chooserIntent, REQUEST_CODE_PICTURE);
    }

然后,在您的Activity中添加方法onActivityResult:

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_CODE_PICTURE && resultCode == Activity.RESULT_OK) {
            if (data == null) {
                return;
            }
            try {
                InputStream inputStream = getContentResolver().openInputStream(data.getData());
                Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                imgPhoto.setImageBitmap(bitmap);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

5
在“onActivityResult”中,仅适用于“pickIntent”,但不适用于“takePhotoIntent”。 - sky91
使用这个,我怎样才能让照片以竖屏模式显示?如果我在竖屏模式下拍照,它会以横屏模式显示。 - LizG
使用Glide或Picasso以正确的方式显示图像。 - Dario Brux

3

我的答案与@Macarse的解决方案几乎相同,但我还添加了一个额外的意图以显示图库应用程序(例如:Google照片),并且是用Kotlin编写的:

val REQUEST_CODE_GET_IMAGE = 101

private fun addProfileImage() {
    val pickImageFileIntent = Intent()
    pickImageFileIntent.type = "image/*"
    pickImageFileIntent.action = Intent.ACTION_GET_CONTENT

    val pickGalleryImageIntent = Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI)

    val captureCameraImageIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)

    val pickTitle = "Capture from camera or Select from gallery the Profile photo"
    val chooserIntent = Intent.createChooser(pickImageFileIntent, pickTitle)
    chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(captureCameraImageIntent, pickGalleryImageIntent))
    startActivityForResult(chooserIntent, REQUEST_CODE_GET_IMAGE)
}

从结果意图中提取图像:
private var imageTempFile: File? = null
private var imageMimeType: String? = null

private fun extractImage(intent: Intent?) {
    val imageUri = intent?.data
    imageUri?.let {
        Glide.with(this)
                .load(imageUri)
                .into(profileImageCiv)
        imageTempFile = MediaUtils.copyContentFromUriToCacheFile(this, imageUri, Settings.DIRECTORY_CACHE_TEMP_PROFILE_IMAGE)
        imageMimeType = MediaUtils.getMimeType(this, imageUri)
    } ?: run {
        intent?.extras?.get("data")?.let { bitmap ->    // Bitmap was returned as raw bitmap
            Glide.with(this)
                    .load(bitmap)
                    .into(profileImageCiv)
            imageTempFile = MediaUtils.writeBitmapToCacheFile(this, bitmap as Bitmap, Settings.DIRECTORY_CACHE_TEMP_PROFILE_IMAGE)
            imageMimeType = "image/jpeg"    // The bitmap was compressed as JPEG format. The bitmap itself doesn't have any format associated to it
        } ?: run {
            imageTempFile = null
            imageMimeType = null
            Log.e("Intent data is null.")
            Log.d("Error during photo selection")
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接