Android生成二维码--拍照或从相册选取图片

旧街凉风 提交于 2020-01-09 20:33:38

    拍照或从相册选择图片是我们日常开发中经常使用到的,可以说是必须掌握的东西。上一篇我介绍了如何生成自定义二维码《Android生成自定义二维码》,其中logo和代替黑色色块的图片都是写死的,所以现在我们就来实现拍照或者从相册选取图片这个功能。 

先看效果图:

      

      

 

 

拍照

1.启动相机程序 

   拍照可以直接启动系统的相机程序,代码如下

Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, TAKE_PHOTO);

    这里我们利用一个隐式Intent来启动相机程序,其中action类型:android.media.action.IMAGE_CAPTURE 表示启动相机应用并请求一张图片。创建了Intent对象,还需指定图片的保存路径,调用Intent的putExtra()方法并传入保存路径即可,最后调用startActivityForResult启动活动,重写onActivityResult()方法就能得到返回值。

 

2.指定保存路径

    上面的intent中指定了保存路径,也就是代码中的imageUri。首先需要创建一个File对象用来存放图片,并使用getExternalCacheDir()方法将图片放入SD卡当前应用包名下的缓存目录中。
    接着需要将File对象转换成Uri对象,需要进行版本判断,7.0以下直接调用Uri的fromFile方法,得到的是图片本地真实路径。7.0以上直接使用真实路径会被认为不安全,会抛出异常,所以需要使用特殊的内容提供器FileProvider,它是ContentProvider的一个子类,作用便是将受限的Uri转换为可共享的Uri。
    既然使用内容提供器,当然需要进行注册了,AndroidManifest.xml中加入:

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.example.xch.generateqrcode.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

    meta-data标签中的内容是用来添加一个共享目录的,引用了一个resource资源,所以需要在 res/xml 目录下新建这个 xml 文件,用于存放应用需要共享的目录文件。

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!--path设为空值表示将整个sd卡共享-->
    <external-path
        name="my_images" path="" />
</paths>

    接下来在代码中调用FileProvider的getUriForFile()方法生成Uri对象,需要传入三个参数,第一个参数是上下文,第二个参数便是 Manifest 文件中注册 FileProvider 时设置的 authorities 属性值,第三个参数为要共享的文件,也就是之前创建的File对象。

 

   完整代码

/**
 * 拍照
 */
private void takePhoto() {
    // 创建File对象,用于存储拍照后的图片
    File outputImage = new File(getExternalCacheDir(), "output_image.jpg");
    try {
        if (outputImage.exists()) {
            outputImage.delete();
        }
        outputImage.createNewFile();
    } catch (IOException e) {
        e.printStackTrace();
    }
    if (Build.VERSION.SDK_INT < 24) {
        imageUri = Uri.fromFile(outputImage);
    } else {
        imageUri = FileProvider.getUriForFile(MainActivity.this, "com.example.xch.generateqrcode.fileprovider", outputImage);
    }
    // 启动相机程序
    Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
    intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
    startActivityForResult(intent, TAKE_PHOTO);
}

 

获取拍照结果:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case TAKE_PHOTO:
            if (resultCode == RESULT_OK) {
                try {
                    // 读取拍照结果
                    logoBitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            break;
        default:
            break;
    }
}

    读取拍照结果利用ContentResolver的openInputStream()方法,并传入刚才图片的保存路径即可获取图片字节流,再利用BitmapFactory的decodeStream()方法转为Bitmap格式,如果担心OOM,可先进行压缩。

 

    最后别忘了在AndroidManifest.xml中加入权限

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

 

从相册选择图片

  • 打开相册 
    同样用Intent,action类型为android.intent.action.GET_CONTENT ,并给intent设置type为图片,如下:
Intent intent = new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
startActivityForResult(intent, CHOOSE_PHOTO);
  • 获取结果 
    在回调onActivityResult即可对返回结果进行处理
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
        case CHOOSE_PHOTO:
            if (resultCode == RESULT_OK) {
                // 判断手机系统版本号
                if (Build.VERSION.SDK_INT >= 19) {
                    // 4.4及以上系统使用这个方法处理图片
                    handleImageOnKitKat(data);
                } else {
                    // 4.4以下系统使用这个方法处理图片
                    handleImageBeforeKitKat(data);
                }
            }
            break;
        default:
            break;
    }
}

    由于Android 4.4开始,不再返回图片真实的Uri,而是一个封装过的Uri,因此需要分别进行处理。Android 4.4之前直接调用getImagePath()方法通过Uri和selection就可获取真实路径,如下

/**
 * 4.4版本以前,直接获取真实路径
 * @param data
 */
private void handleImageBeforeKitKat(Intent data) {
    Uri uri = data.getData();
    String imagePath = getImagePath(uri, null);
    //显示图片
    displayImage(imagePath);
}

private String getImagePath(Uri uri, String selection) {
    String path = null;
    // 通过Uri和selection来获取真实的图片路径
    Cursor cursor = getContentResolver().query(uri, null, selection, null, null);
    if (cursor != null) {
        if (cursor.moveToFirst()) {
            path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
        }
        cursor.close();
    }
    return path;
}

 

    Android 4.4之后,需要解析封装过的Uri,如果Uri是document类型,则通过document id处理,如果是content类型的Uri,则使用普通方式处理,如果是file类型的Uri,直接获取图片路径即可。获取到路径后即可显示,如下

private void handleImageOnKitKat(Intent data) {
    String imagePath = null;
    Uri uri = data.getData();
    if (DocumentsContract.isDocumentUri(this, uri)) {
        // 如果是document类型的Uri,则通过document id处理
        String docId = DocumentsContract.getDocumentId(uri);
        if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
            String id = docId.split(":")[1]; // 解析出数字格式的id
            String selection = MediaStore.Images.Media._ID + "=" + id;
            imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
        } else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
            Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));
            imagePath = getImagePath(contentUri, null);
        }
    } else if ("content".equalsIgnoreCase(uri.getScheme())) {
        // 如果是content类型的Uri,则使用普通方式处理
        imagePath = getImagePath(uri, null);
    } else if ("file".equalsIgnoreCase(uri.getScheme())) {
        // 如果是file类型的Uri,直接获取图片路径即可
        imagePath = uri.getPath();
    }
    displayImage(imagePath); // 根据图片路径显示图片
}

 

显示图片

/**
 * 显示图片
 * @param imagePath 图片路径
 */
private void displayImage(String imagePath) {
    if (imagePath != null) {
       logoBitmap = BitmapFactory.decodeFile(imagePath);
       // 显示图片
       picture_logo.setImageBitmap(logoBitmap);
    } else {
        Toast.makeText(this, "获取图片失败", Toast.LENGTH_SHORT).show();
    }
}

 

动态权限申请

   由于涉及了敏感权限 WRITE_EXTERNAL_STORAGE ,所以需要进行权限的动态申请,如下

//动态权限申请
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
} else {
    //打开相册
    openAlbum();
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    switch (requestCode) {
        case 1:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //打开相册
                openAlbum();
            } else {
                Toast.makeText(this, "你拒绝了权限申请,可能无法打开相册哟", Toast.LENGTH_SHORT).show();
            }
            break;
        default:
    }
}

 

    上面的代码就是动态申请权限的流程,首先判断用户是不是已经给我们权限授权了,使用ContextCompat.checkSelfPermission()方法,第一个参数是Context,第二个参数是具体的权限名称,如果等于PackageManager.PERMISSION_GRANTED表明已授权,不等于就是没有授权。
    如果已授权就直接做后面的操作,如果没有授权,需要调用ActivityCompat.requestPermissions()方法申请授权,第一个参数是当前Activity实例,第二个参数是权限数组,第三个是请求码。
    用户的选择将会回调到onRequestPermissionsResult()方法中,授权结果封装在grantResults参数中,如果被授权,则打开相册,否则提示用户未打开权限无法使用。


    源码已更新至GitHub,地址:https://github.com/yangxch/GenerateQRCode

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!