目录
完成毕业设计的需要,Android11要求实现图片拍照并裁剪,例如头像的选取。真机redmi k20;Android studio2021.1.1 java编程。
1. 文件权限问题
这是Android11在拍照裁剪最大的问题。Android11对app存储权限设置了分区,app默认只能访问自己app内的文件,而公共区域的文件则需要添加provider来支持访问,而其他app内部的文件访问权限则需要目的app开启权限,一般情况下都无法访问其他app的私有文件。
我当前实现的拍照及裁剪都是Android系统自带的功能,利用intent调用。因为两个功能都是公共的存储区域文件,则需要添加provider,在清单文件中添加如下代码到application标签中。
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="自己的包名"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
这里要求有一个file_paths的xml文件,可以使用报红提示自动生成文件,或者在res文件夹下新建xml文件夹,新建一个xml文件
添加代码:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<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>
</resources>
2. 拍照权限和布局文件
在清单文件中添加以下权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA"/>
第一天读取外部存储,第二条相机权限。
Android11自然也是需要动态获取拍照的权限,代码:
private void permission() {
int REQUEST_CODE_CONTACT = 101;
String[] strPermissions = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA};
for (String strPermission : strPermissions) {
if (checkSelfPermission(strPermission) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(strPermissions, REQUEST_CODE_CONTACT);
}
}
}
记得调用该函数。读写权限都要。
布局文件很简单,一个按钮一个Imageview就行了。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="60dp"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/pick"
android:layout_width="160dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="选择图片"
android:textSize="20sp" />
<ImageView
android:layout_gravity="center"
android:id="@+id/image_view"
android:layout_width="400dp"
android:layout_height="400dp"/>
</LinearLayout>
全局变量://根据自己喜好设置,RESULT_OK不能改
private static final int CAMERA_CODE = 20;
private static final int CROP_CODE = 19;
private static final int RESULT_OK = -1;
String path;
Uri uri;
绑定控件:
private void init() {
picture = findViewById(R.id.image_view);
picker = findViewById(R.id.pick);
//自定义的图片保存路径
path = getFilesDir().getPath() + "/icon.jpeg";
//uri用以存放android裁剪后自动保存的路径
//保存在公共区域
File uriFile = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
//UriUtils是关于路径的操作
uri = UriUtils.getUriForFile(main, uriFile);
}
点击函数中内容://暂时不想写相册获取,原理都差不多
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(intent, CAMERA_CODE);
直接利用Intent跳转到拍照界面,然后在onActivityResult函数中做结果处理代码如下:
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Intent intent;
if (requestCode == CAMERA_CODE) {
mDialog.dismiss();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
//PhotoUtils存放关于照片的一些操作
intent = PhotoUtils.startPhotoZoom(uri, uri, 2000, 2000);
} else {
intent = PhotoUtils.startPhotoZoom(uri, Uri.parse(path), 2000, 2000);
}
startActivityForResult(intent, CROP_CODE);
}
if (requestCode == CROP_CODE) {
new Thread(() -> {
String outputPath = UriUtils.getFileAbsolutePath(this, uri);
System.out.println(outputPath);
}).start();
bitmap = decodeUriAsBitmap(uri);
picture.setImageBitmap(bitmap);
PhotoUtils.save(bitmap, new File(path), Bitmap.CompressFormat.JPEG, false);
}
}
在完成拍照并回调后直接利用Intent跳转到裁剪界面,裁剪完成后获取图片bitmap,并显示在Imageview空间上。
3. 一些用的上的工具函数
UriUtils.getUriForFile:
/**
* 获取uri
*
* @param context context
* @param file 文件路径
* @return
*/
public static Uri getUriForFile(Context context, File file) {
Uri fileUri = null;
String filePath = file.getAbsolutePath();
Cursor cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[]{"_id"}, "_data=? ", new String[]{filePath}, null);
if (cursor != null && cursor.moveToFirst()) {
int id = cursor.getInt(cursor.getColumnIndex("_id"));
Uri baseUri = Uri.parse("content://media/external/images/media");
fileUri = Uri.withAppendedPath(baseUri, "" + id);
} else if (file.exists()) {
ContentValues values = new ContentValues();
values.put("_data", filePath);
fileUri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
} else {
fileUri = null;
}
return fileUri;
}
PhotoUtils:
/**
* 调用系统裁剪<br>
*
* @param uri 需要裁剪的图片路径
* @param mImagePath 图片输出路径
* @param sizeX 裁剪x
* @param sizeY 裁剪y
*/
public static Intent startPhotoZoom(Uri uri, Uri mImagePath, int sizeX, int sizeY) {
Intent intent = new Intent("com.android.camera.action.CROP");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//添加这一句表示对目标应用临时授权该Uri所代表的文件
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
//裁剪后输出路径
intent.putExtra(MediaStore.EXTRA_OUTPUT, mImagePath);
//输入图片路径
intent.setDataAndType(uri, "image/*");
intent.putExtra("crop", "true");
intent.putExtra("circleCrop", "true");
//华为的手机原型裁剪适应大小
if (Build.MANUFACTURER.equals("HUAWEI")) {
intent.putExtra("aspectX", 9998);
intent.putExtra("aspectY", 9999);
} else {
intent.putExtra("aspectX", sizeX);
intent.putExtra("aspectY", sizeY);
}
//输出的图片size越大越清晰,但是过大可能会导致程序崩溃
intent.putExtra("outputX", sizeX);
intent.putExtra("outputY", sizeY);
intent.putExtra("scale", true);
intent.putExtra("scaleUpIfNeeded", true);
//设置不返回数据,而是返回JPEG格式,在函数中调用编码函数再显示,这样图片更清晰
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("return-data", false);
return intent;
}
/**
* 保存图片到文件File。
*
* @param src 源图片
* @param file 要保存到的文件
* @param format 格式
* @param recycle 是否回收
* @return true 成功 false 失败
*/
public static boolean save(Bitmap src, File file, Bitmap.CompressFormat format, boolean recycle) {
if (src == null)
return false;
OutputStream os;
boolean ret = false;
try {
os = new BufferedOutputStream(new FileOutputStream(file));
ret = src.compress(format, 100, os);
if (recycle && !src.isRecycled())
src.recycle();
} catch (IOException e) {
e.printStackTrace();
}
return ret;
}
decodeUriAsBitmap:
private Bitmap decodeUriAsBitmap(Uri uri) {
Bitmap bitmap = null;
try {
// 先通过getContentResolver方法获得一个ContentResolver实例,
// 调用openInputStream(Uri)方法获得uri关联的数据流stream
// 把上一步获得的数据流解析成为bitmap
bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri));
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
return bitmap;
}
4. 结果展示
代码若有疑问欢迎批评
暂时还未上传代码到git,后续可能会添加。