拍照或从相册选择图片是我们日常开发中经常使用到的,可以说是必须掌握的东西。上一篇我介绍了如何生成自定义二维码《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
来源:https://www.cnblogs.com/xch-yang/p/9879819.html