Android(安卓)兼容Android(安卓)7拍摄照片/打开相册/选择照片/剪裁照片/显示照片 带demo
16lz
2021-01-26
Android 兼容Android 7拍摄照片/打开相册/选择照片/剪裁照片/显示照片 带demo
- 前言
- 效果
- 上代码
- 共享文件夹
- 配置
- 声明权限
- 创建文件管理工具
- 布局文件
- 活动
- 服务类
- GitHub
- 完事
前言
项目里需要给用户修改头像的功能。就需要能做到标题的功能。
先拟定需要完成的任务
/** * 首页 * ------------------------- * 1、点击拍照,先判断是否有相机权限和文件写入读取权限,没有就请求,有就打开相机 * 2、点击选择照片,先判断是否有文件读取权限,没有就请求,有就打开图册 * 3、拿到照片返回进行剪裁 * 4、剪裁成功后显示 * @author D10NG * @date on 2019-05-15 09:02 */
先上参考文章表示感谢
@那一夜_ ------ 适配Android7.0应用间文件共享FileProvider
@那一夜_ ------ android从相册选择图片和拍照选择图片
效果
GIF图
上代码
共享文件夹
首先在项目res目录下新建xml目录,并新建file_paths.xml,这个文件主要用来配置应用共享文件的路径
<?xml version="1.0" encoding="utf-8"?><paths xmlns:android="http://schemas.android.com/apk/res/android"> <root-path name="root" path="" /> <files-path name="files" path="." /> <cache-path name="cache" path="." /> <external-path name="external" path="." /> <external-files-path name="external_file_path" path="." /> <external-cache-path name="external_cache_path" path="." /></paths>
其中配置的path="."
的意思就是应用可以使用整个目录
配置
在AndroidManifest.xml的application节点下增加FileProvider的声明
<application ... <!-- 适配android 7.0文件访问 org.rydc.phototest是应用的包名 --> <provider android:name="android.support.v4.content.FileProvider" android:authorities="org.rydc.phototest.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> </application>
声明权限
<!-- 相册读取 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <!-- 拍照 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.CAMERA"/>
创建文件管理工具
直接复制下面的即可
package org.rydc.phototest.utils;import android.annotation.SuppressLint;import android.content.ContentUris;import android.content.Context;import android.content.Intent;import android.content.pm.PackageManager;import android.content.pm.ResolveInfo;import android.database.Cursor;import android.net.Uri;import android.os.Build;import android.os.Environment;import android.provider.DocumentsContract;import android.provider.MediaStore;import android.util.Log;import java.io.File;import java.io.IOException;import java.util.List;/** * 适配Android 7 读取文件工具 * * @author D10NG * @date on 2019-05-15 08:45 */public class FileProviderUtils { /** * 获取文件的Uri * @param context * @param file * @return */ public static Uri getUriForFile(Context context, File file) { Uri fileUri = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { fileUri = getUriForFile24(context, file); } else { fileUri = Uri.fromFile(file); } return fileUri; } /** * Android 7 获取文件的Uri * @param context * @param file * @return */ private static Uri getUriForFile24(Context context, File file) { return android.support.v4.content.FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file); } /** * 设定intent的data和type * @param context * @param intent * @param type * @param file * @param writeAble */ public static void setIntentDataAndType(Context context, Intent intent, String type, File file, boolean writeAble) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intent.setDataAndType(getUriForFile(context, file), type); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); if (writeAble) { intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } } else { intent.setDataAndType(Uri.fromFile(file), type); // apk放在cache文件中,需要获取读写权限 chmod("777", file.getAbsolutePath()); } } /** * 修改文件权限 * @param permission * @param path */ public static void chmod(String permission, String path) { try { String command = "chmod " + permission + " " + path; Runtime runtime = Runtime.getRuntime(); runtime.exec(command); } catch (IOException e) { e.printStackTrace(); } } /** * 设定intent的data * @param context * @param intent * @param file * @param writeAble */ public static void setIntentData(Context context, Intent intent, File file, boolean writeAble) { if (Build.VERSION.SDK_INT >= 24) { intent.setData(getUriForFile(context, file)); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); if (writeAble) { intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } } else { intent.setData(Uri.fromFile(file)); } } /** * 授予权限 * @param context * @param intent * @param uri * @param writeAble */ public static void grantPermissions(Context context, Intent intent, Uri uri, boolean writeAble) { int flag = Intent.FLAG_GRANT_READ_URI_PERMISSION; if (writeAble) { flag |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION; } intent.addFlags(flag); List<ResolveInfo> resInfoList = context.getPackageManager() .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo resolveInfo : resInfoList) { String packageName = resolveInfo.activityInfo.packageName; context.grantUriPermission(packageName, uri, flag); } } /** * 根据URI获取文件真实路径(兼容多机型) * @param context * @param uri * @return */ public static String getFilePathByUri(Context context, Uri uri) { // 判断uri的标头是 content 还是 file,分别用不同的方法处理 if ("content".equalsIgnoreCase(uri.getScheme())) { int sdkVersion = Build.VERSION.SDK_INT; if (sdkVersion >= 19) { // api >= 19 return getRealPathFromUriAboveApi19(context, uri); } else { // api < 19 return getRealPathFromUriBelowAPI19(context, uri); } } else if ("file".equalsIgnoreCase(uri.getScheme())) { return uri.getPath(); } return null; } /** * 适配api19及以上,根据uri获取图片的绝对路径 * * @param context 上下文对象 * @param uri 图片的Uri * @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null */ @SuppressLint("NewApi") private static String getRealPathFromUriAboveApi19(Context context, Uri uri) { String filePath = null; if (DocumentsContract.isDocumentUri(context, uri)) { // 如果是document类型的 uri, 则通过document id来进行处理 String documentId = DocumentsContract.getDocumentId(uri); if (isMediaDocument(uri)) { // MediaProvider // 使用':'分割 String type = documentId.split(":")[0]; String id = documentId.split(":")[1]; String selection = MediaStore.Images.Media._ID + "=?"; String[] selectionArgs = {id}; // 判断文件类型 Uri contentUri = null; if ("image".equals(type)) { contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; } else if ("video".equals(type)) { contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; } else if ("audio".equals(type)) { contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; } filePath = getDataColumn(context, contentUri, selection, selectionArgs); } else if (isDownloadsDocument(uri)) { // DownloadsProvider Uri contentUri = ContentUris.withAppendedId( Uri.parse("content://downloads/public_downloads"), Long.valueOf(documentId)); filePath = getDataColumn(context, contentUri, null, null); }else if (isExternalStorageDocument(uri)) { // ExternalStorageProvider final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; if ("primary".equalsIgnoreCase(type)) { filePath = Environment.getExternalStorageDirectory() + "/" + split[1]; } }else { Log.e("FileHandlerUtil", "路径错误"); } } else if ("content".equalsIgnoreCase(uri.getScheme())) { // 如果是 content 类型的 Uri filePath = getDataColumn(context, uri, null, null); } else if ("file".equals(uri.getScheme())) { // 如果是 file 类型的 Uri,直接获取图片对应的路径 filePath = uri.getPath(); } return filePath; } /** * 适配api19以下(不包括api19),根据uri获取图片的绝对路径 * * @param context 上下文对象 * @param uri 图片的Uri * @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null */ private static String getRealPathFromUriBelowAPI19(Context context, Uri uri) { return getDataColumn(context, uri, null, null); } /** * 获取数据库表中的 _data 列,即返回Uri对应的文件路径 * * @return */ private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { String path = null; String[] projection = new String[]{MediaStore.Images.Media.DATA}; Cursor cursor = null; try { cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { int columnIndex = cursor.getColumnIndexOrThrow(projection[0]); path = cursor.getString(columnIndex); } } catch (Exception e) { if (cursor != null) { cursor.close(); } } return path; } /** * @param uri the Uri to check * @return Whether the Uri authority is MediaProvider */ private static boolean isMediaDocument(Uri uri) { return "com.android.providers.media.documents".equals(uri.getAuthority()); } private static boolean isExternalStorageDocument(Uri uri) { return "com.android.externalstorage.documents".equals(uri.getAuthority()); } /** * @param uri the Uri to check * @return Whether the Uri authority is DownloadsProvider */ private static boolean isDownloadsDocument(Uri uri) { return "com.android.providers.downloads.documents".equals(uri.getAuthority()); }}
布局文件
activity_main.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <Button android:id="@+id/btn_take_photo" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="拍照" /> <Button android:id="@+id/btn_choose_photo" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="从相册获取" /> <ImageView android:id="@+id/img_photo" android:layout_width="match_parent" android:layout_height="wrap_content" android:src="@drawable/ic_launcher_background" /></LinearLayout>
活动
MainActivity.java
package org.rydc.phototest;import android.content.Context;import android.content.Intent;import android.content.pm.PackageManager;import android.net.Uri;import android.support.annotation.NonNull;import android.support.annotation.Nullable;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.widget.Toast;import org.rydc.phototest.utils.FileProviderUtils;/** * 首页 * ------------------------- * 1、点击拍照,先判断是否有相机权限和文件写入读取权限,没有就请求,有就打开相机 * 2、点击选择照片,先判断是否有文件读取权限,没有就请求,有就打开图册 * 3、拿到照片返回进行剪裁 * 4、剪裁成功后显示 * @author D10NG * @date on 2019-05-15 09:02 */public class MainActivity extends AppCompatActivity { private Context mContext = this; private MainService mService; public static final int RC_CHOOSE_PHOTO = 10; public static final int RC_TAKE_PHOTO = 11; public static final int RC_CROP_PHOTO = 12; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mService = new MainService(mContext); setContentView(mService.mView.getView()); } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode != RESULT_OK) { Toast.makeText(mContext, "操作取消", Toast.LENGTH_SHORT).show(); return; } switch (requestCode) { case RC_CHOOSE_PHOTO: if (null == data) { Toast.makeText(mContext, "没有拿到图片", Toast.LENGTH_SHORT).show(); return; } Uri uri = data.getData(); if (null == uri) { Toast.makeText(mContext, "没有拿到图片路径", Toast.LENGTH_SHORT).show(); return; } // 剪裁图片 mService.cropPhoto(FileProviderUtils.getFilePathByUri(mContext, uri), 200); break; case RC_TAKE_PHOTO: // 剪裁图片 mService.cropPhoto(mService.tempPhotoPath, 200); break; case RC_CROP_PHOTO: // 显示图片 mService.showPhoto(mService.cropImgUri); break; } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { boolean allPass = true; for (int i = 0; i < permissions.length; i++) { if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { allPass = false; } } if (!allPass) { Toast.makeText(mContext, "没有获得相应权限", Toast.LENGTH_SHORT).show(); return; } switch (requestCode) { case RC_CHOOSE_PHOTO: // 继续去打开图册 mService.choosePhoto(); break; case RC_TAKE_PHOTO: // 继续去拍照 mService.takePhoto(); break; } }}
服务类
MainService.java
package org.rydc.phototest;import android.Manifest;import android.annotation.SuppressLint;import android.app.Activity;import android.content.Context;import android.content.Intent;import android.content.pm.PackageManager;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.net.Uri;import android.os.Environment;import android.os.Handler;import android.os.Message;import android.provider.MediaStore;import android.support.v4.app.ActivityCompat;import android.support.v4.content.ContextCompat;import android.text.TextUtils;import android.util.Log;import org.rydc.phototest.utils.FileProviderUtils;import java.io.File;/** * @author D10NG * @date on 2019-05-15 09:15 */public class MainService { private Context mContext; public MainView mView; /** 拍照输出真实路径 */ public String tempPhotoPath; /** 剪裁输出uri路径 */ public final Uri cropImgUri = Uri.parse("file:///"+Environment.getExternalStorageDirectory()+"/photo_crop.jpg"); public static final int CLICK_VIEW = 1; @SuppressLint("HandlerLeak") private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case CLICK_VIEW: // 页面控件点击事件 switch (msg.arg1) { case R.id.btn_take_photo: takePhoto(); break; case R.id.btn_choose_photo: choosePhoto(); break; } break; } } }; public MainService(Context context) { mContext = context; mView = new MainView(mContext, mHandler); } /** * 打开相机 */ public void takePhoto() { if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(mContext, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { // 未授权,申请授权 ActivityCompat.requestPermissions((Activity)mContext, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, MainActivity.RC_TAKE_PHOTO); return; } // 已授权 Intent intentToTakePhoto = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); // 设置照片输出位置 File photoFile = new File(Environment.getExternalStorageDirectory(), "photo.jpg"); tempPhotoPath = photoFile.getAbsolutePath(); Uri tempImgUri = FileProviderUtils.getUriForFile(mContext, photoFile); intentToTakePhoto.putExtra(MediaStore.EXTRA_OUTPUT, tempImgUri); ((Activity)mContext).startActivityForResult(intentToTakePhoto, MainActivity.RC_TAKE_PHOTO); } /** * 选图 */ public void choosePhoto() { if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { // 未授权,申请授权(从相册选择图片需要读取存储卡的权限) ActivityCompat.requestPermissions((Activity)mContext, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, MainActivity.RC_CHOOSE_PHOTO); return; } // 已授权,获取照片 Intent intentToPickPic = new Intent(Intent.ACTION_PICK, null); intentToPickPic.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); ((Activity)mContext).startActivityForResult(intentToPickPic, MainActivity.RC_CHOOSE_PHOTO); } /** * 剪裁图片 * * @param path * @param size */ public void cropPhoto(String path, int size) { Intent intent = new Intent("com.android.camera.action.CROP"); FileProviderUtils.setIntentDataAndType(mContext, intent, "image/*", new File(path), true); intent.putExtra("crop", "true"); intent.putExtra("aspectX", 1); intent.putExtra("aspectY", 1); intent.putExtra("outputX", size); intent.putExtra("outputY", size); intent.putExtra("scale", true); intent.putExtra("return-data", false); intent.putExtra(MediaStore.EXTRA_OUTPUT, cropImgUri); intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); ((Activity)mContext).startActivityForResult(intent, MainActivity.RC_CROP_PHOTO); } /** * 显示图片 */ public void showPhoto(Uri uri) { String path = FileProviderUtils.getFilePathByUri(mContext, uri); Log.e("main", "path=" + path); if (!TextUtils.isEmpty(path)) { // 从文件路径读取文件 Bitmap bitmap = BitmapFactory.decodeFile(path); mView.setImgPhoto(bitmap); } else { Log.e("main", "没有图片"); } }}
GitHub
demo:D10NGYANG/photoTest
欢迎提供建议或意见
完事
更多相关文章
- Android(安卓)合并AAR踩坑之旅
- Android将so库封装到jar包中并加载其中的so库
- Android(安卓)性能优化之Loading Big Bitmaps
- android和ios,音频互通方案
- android音乐播放器-------使用android系统自带的数据库
- Android的.9.png图片分析
- 我的Android进阶之旅------>Android使用9Patch图片作为不失真背
- 工程中导入sqlite -sqlite 基础教程(1)
- 解决 Android(安卓)Studio 创建项目时极其的慢的尴尬