Android 兼容Android 7拍摄照片/打开相册/选择照片/剪裁照片/显示照片 带demo

2023-12-28 16:18

本文主要是介绍Android 兼容Android 7拍摄照片/打开相册/选择照片/剪裁照片/显示照片 带demo,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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-pathname="root"path="" /><files-pathname="files"path="." /><cache-pathname="cache"path="." /><external-pathname="external"path="." /><external-files-pathname="external_file_path"path="." /><external-cache-pathname="external_cache_path"path="." /></paths>

其中配置的path="."的意思就是应用可以使用整个目录

配置

在AndroidManifest.xml的application节点下增加FileProvider的声明

   <application...<!-- 适配android 7.0文件访问 org.rydc.phototest是应用的包名 --><providerandroid:name="android.support.v4.content.FileProvider"android:authorities="org.rydc.phototest.fileprovider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid: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 >= 19return getRealPathFromUriAboveApi19(context, uri);} else {// api < 19return 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)) {// DownloadsProviderUri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),Long.valueOf(documentId));filePath = getDataColumn(context, contentUri, null, null);}else if (isExternalStorageDocument(uri)) {// ExternalStorageProviderfinal 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 类型的 UrifilePath = 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"><Buttonandroid:id="@+id/btn_take_photo"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="拍照" /><Buttonandroid:id="@+id/btn_choose_photo"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="从相册获取" /><ImageViewandroid: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;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mService = new MainService(mContext);setContentView(mService.mView.getView());}@Overrideprotected 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;}}@Overridepublic 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() {@Overridepublic 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 兼容Android 7拍摄照片/打开相册/选择照片/剪裁照片/显示照片 带demo的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/546537

相关文章

前端知识点之Javascript选择输入框confirm用法

《前端知识点之Javascript选择输入框confirm用法》:本文主要介绍JavaScript中的confirm方法的基本用法、功能特点、注意事项及常见用途,文中通过代码介绍的非常详细,对大家... 目录1. 基本用法2. 功能特点①阻塞行为:confirm 对话框会阻塞脚本的执行,直到用户作出选择。②

Android开发中gradle下载缓慢的问题级解决方法

《Android开发中gradle下载缓慢的问题级解决方法》本文介绍了解决Android开发中Gradle下载缓慢问题的几种方法,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、网络环境优化二、Gradle版本与配置优化三、其他优化措施针对android开发中Gradle下载缓慢的问

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

解决jupyterLab打开后出现Config option `template_path`not recognized by `ExporterCollapsibleHeadings`问题

《解决jupyterLab打开后出现Configoption`template_path`notrecognizedby`ExporterCollapsibleHeadings`问题》在Ju... 目录jupyterLab打开后出现“templandroidate_path”相关问题这是 tensorflo

Android里面的Service种类以及启动方式

《Android里面的Service种类以及启动方式》Android中的Service分为前台服务和后台服务,前台服务需要亮身份牌并显示通知,后台服务则有启动方式选择,包括startService和b... 目录一句话总结:一、Service 的两种类型:1. 前台服务(必须亮身份牌)2. 后台服务(偷偷干

Android kotlin语言实现删除文件的解决方案

《Androidkotlin语言实现删除文件的解决方案》:本文主要介绍Androidkotlin语言实现删除文件的解决方案,在项目开发过程中,尤其是需要跨平台协作的项目,那么删除用户指定的文件的... 目录一、前言二、适用环境三、模板内容1.权限申请2.Activity中的模板一、前言在项目开发过程中,尤

如何设置vim永久显示行号

《如何设置vim永久显示行号》在Linux环境下,vim默认不显示行号,这在程序编译出错时定位错误语句非常不便,通过修改vim配置文件vimrc,可以在每次打开vim时永久显示行号... 目录设置vim永久显示行号1.临时显示行号2.永www.chinasem.cn久显示行号总结设置vim永久显示行号在li

Python 中 requests 与 aiohttp 在实际项目中的选择策略详解

《Python中requests与aiohttp在实际项目中的选择策略详解》本文主要介绍了Python爬虫开发中常用的两个库requests和aiohttp的使用方法及其区别,通过实际项目案... 目录一、requests 库二、aiohttp 库三、requests 和 aiohttp 的比较四、requ

el-select下拉选择缓存的实现

《el-select下拉选择缓存的实现》本文主要介绍了在使用el-select实现下拉选择缓存时遇到的问题及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录项目场景:问题描述解决方案:项目场景:从左侧列表中选取字段填入右侧下拉多选框,用户可以对右侧

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO