android 仿微信选取相册_Android 拍照及相册选取图片功能,已适配Android6.0、7.0、8.0...

功能说明

本文的示例以下图为准:

9ae808db5ed6fa98a2d8496061b4cd29.png

界面只有一个 ImageView,点击 ImageView 弹出 DialogFragment,分别是拍照和相册选择图片功能,其中都带有系统的裁剪功能,将裁剪后的图片显示在 ImageView 上。如果不需要裁剪功能,只需要将代码中的裁剪方法注释掉即可。

6.0 以下版本

1. 权限

<uses-feature android:name="android.hardware.camera" />

<!--相机权限-->

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

<!--写入SD卡的权限:如果你希望保存相机拍照后的照片-->

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

<!--读取SD卡的权限:打开相册选取图片所必须的权限-->

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

2. 拍照代码

/**

* 打开系统相机

*/

private void openSysCamera() {

Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(

new File(Environment.getExternalStorageDirectory(), imgName)));

startActivityForResult(cameraIntent, CAMERA_RESULT_CODE);

}

其中 imgName 是拍照图片的名字,一般以时间戳再加上自定义字符串命名;CAMERA_RESULT_CODE 是自定义的一个常量,作为拍照的请求码。

onActivityResult 处理拍照后的图片

case CAMERA_RESULT_CODE:

tempFile = new File(Environment.getExternalStorageDirectory(), imgName);

cropPic(Uri.fromFile(tempFile));

break;

将拍照后的图片创建成一个 File 对象,用来裁剪,裁剪的功能代码是和相册选取图片通用的。

裁剪代码

/**

* 裁剪图片

*

* @param data

*/

private void cropPic(Uri data) {

if (data == null) {

return;

}

Intent cropIntent = new Intent("com.android.camera.action.CROP");

cropIntent.setDataAndType(data, "image/*");

// 开启裁剪:打开的Intent所显示的View可裁剪

cropIntent.putExtra("crop", "true");

// 裁剪宽高比

cropIntent.putExtra("aspectX", 1);

cropIntent.putExtra("aspectY", 1);

// 裁剪输出大小

cropIntent.putExtra("outputX", 320);

cropIntent.putExtra("outputY", 320);

cropIntent.putExtra("scale", true);

/**

* return-data

* 这个属性决定我们在 onActivityResult 中接收到的是什么数据,

* 如果设置为true 那么data将会返回一个bitmap

* 如果设置为false,则会将图片保存到本地并将对应的uri返回,当然这个uri得有我们自己设定。

* 系统裁剪完成后将会将裁剪完成的图片保存在我们所这设定这个uri地址上。我们只需要在裁剪完成后直接调用该uri来设置图片,就可以了。

*/

cropIntent.putExtra("return-data", true);

// 当 return-data 为 false 的时候需要设置这句

// cropIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);

// 图片输出格式

// cropIntent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());

// 头像识别 会启动系统的拍照时人脸识别

// cropIntent.putExtra("noFaceDetection", true);

startActivityForResult(cropIntent, CROP_RESULT_CODE);

}

其中的 CROP_RESULT_CODE 也是定义的静态常量,同拍照时定义的常量相同的作用。

如果裁剪时将 return-data 设置为 false,那么需要定义一个 Uri 来保存裁剪后的图片路径。

// 裁剪属性 cropIntent.putExtra("return-data", false); 时,使用自定义接收图片的Uri

private static final String IMAGE_FILE_LOCATION = "file:///" + Environment.getExternalStorageDirectory().getPath() + "/temp.jpg";

private Uri imageUri = Uri.parse(IMAGE_FILE_LOCATION);

图片裁剪完成回调

case CROP_RESULT_CODE:

// 裁剪时,这样设置 cropIntent.putExtra("return-data", true); 处理方案如下

if (data != null) {

Bundle bundle = data.getExtras();

if (bundle != null) {

Bitmap bitmap = bundle.getParcelable("data");

imageView.setImageBitmap(bitmap);

// 把裁剪后的图片保存至本地 返回路径

String urlpath = FileUtilcll.saveFile(this, "crop.jpg", bitmap);

L.e("裁剪图片地址->" + urlpath);

}

}

// 裁剪时,这样设置 cropIntent.putExtra("return-data", false); 处理方案如下

// try {

// ivHead.setImageBitmap(BitmapFactory.decodeStream(

// getActivity().getContentResolver().openInputStream(imageUri)));

// } catch (FileNotFoundException e) {

// e.printStackTrace();

// }

break;

FileUtilcll 代码

/**

* 图片文件操作

*/

public class FileUtilcll {

/**

* 将Bitmap 图片保存到本地路径,并返回路径

*

* @param fileName 文件名称

* @param bitmap 图片

* @param资源类型,参照 MultimediaContentType 枚举,根据此类型,保存时可自动归类

*/

public static String saveFile(Context c, String fileName, Bitmap bitmap) {

return saveFile(c, "", fileName, bitmap);

}

public static String saveFile(Context c, String filePath, String fileName, Bitmap bitmap) {

byte[] bytes = bitmapToBytes(bitmap);

return saveFile(c, filePath, fileName, bytes);

}

/**

* Bitmap 转 字节数组

*

* @param bm

* @return

*/

public static byte[] bitmapToBytes(Bitmap bm) {

ByteArrayOutputStream baos = new ByteArrayOutputStream();

bm.compress(CompressFormat.JPEG, 100, baos);

return baos.toByteArray();

}

public static String saveFile(Context c, String filePath, String fileName, byte[] bytes) {

String fileFullName = "";

FileOutputStream fos = null;

String dateFolder = new SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA)

.format(new Date());

try {

String suffix = "";

if (filePath == null || filePath.trim().length() == 0) {

filePath = Environment.getExternalStorageDirectory() + "/cxs/" + dateFolder + "/";

}

File file = new File(filePath);

if (!file.exists()) {

file.mkdirs();

}

File fullFile = new File(filePath, fileName + suffix);

fileFullName = fullFile.getPath();

fos = new FileOutputStream(new File(filePath, fileName + suffix));

fos.write(bytes);

} catch (Exception e) {

fileFullName = "";

} finally {

if (fos != null) {

try {

fos.close();

} catch (IOException e) {

fileFullName = "";

}

}

}

return fileFullName;

}

}

到此,拍照功能就完成了,附带了裁剪。剩下的就是相册选取照片,这个也不难,固定代码,其他功能和拍照时相同的。

/**

* 打开系统相册

*/

private void openSysAlbum() {

Intent albumIntent = new Intent(Intent.ACTION_PICK);

albumIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");

startActivityForResult(albumIntent, ALBUM_RESULT_CODE);

}

其中的 ALBUM_RESULT_CODE 同样是定义的常量。用来在 onActivityResult 中处理返回结果。

case ALBUM_RESULT_CODE:

// 相册

cropPic(data.getData());

break;

和拍照公用裁剪代码,这里直接调用即可,其他逻辑不变。

6.0动态权限适配

除了上述功能保持不变之外,需要在代码中动态申请文件读写权限和相机权限。这里我将申请的时机写在了刚进入页面,而不是刚打开 APP,也不是点击按钮的时候。

逻辑

如果用户点击了拒绝,但没有点击“不再询问”,这个时候再次进入界面继续弹框;

如果用户点击了拒绝,且选择了“不再询问”,那么再次进入此界面将会弹框提示打开 APP 的详情界面,手动开启对应权限。

权限申请代码

/**

* 初始化相机相关权限

* 适配6.0+手机的运行时权限

*/

private void initPermission() {

String[] permissions = new String[]{Manifest.permission.CAMERA,

Manifest.permission.WRITE_EXTERNAL_STORAGE,

Manifest.permission.READ_EXTERNAL_STORAGE};

//检查权限

if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)

!= PackageManager.PERMISSION_GRANTED) {

// 之前拒绝了权限,但没有点击 不再询问 这个时候让它继续请求权限

if (ActivityCompat.shouldShowRequestPermissionRationale(this,

Manifest.permission.CAMERA)) {

Toast.makeText(this, "用户曾拒绝打开相机权限", Toast.LENGTH_SHORT).show();

ActivityCompat.requestPermissions(this, permissions, REQUEST_PERMISSIONS);

} else {

//注册相机权限

ActivityCompat.requestPermissions(this, permissions, REQUEST_PERMISSIONS);

}

}

}

这里我将需要的权限统一写在了一个数组里面。其中的 REQUEST_PERMISSIONS 和上面拍照定义的常量功能相同。

权限申请回调

@Override

public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,

@NonNull int[] grantResults) {

switch (requestCode) {

case REQUEST_PERMISSIONS:

if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

//成功

Toast.makeText(this, "用户授权相机权限", Toast.LENGTH_SHORT).show();

} else {

// 勾选了不再询问

Toast.makeText(this, "用户拒绝相机权限", Toast.LENGTH_SHORT).show();

/**

* 跳转到 APP 详情的权限设置页

*

* 可根据自己的需求定制对话框,点击某个按钮在执行下面的代码

*/

Intent intent = Util.getAppDetailSettingIntent(PhotoFromSysActivity.this);

startActivity(intent);

}

break;

}

}

其中的 getAppDetailSettingIntent()方法代码如下:

/**

* 获取 APP 详情页面intent

*

* @return

*/

public static Intent getAppDetailSettingIntent(Context context) {

Intent localIntent = new Intent();

localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

if (Build.VERSION.SDK_INT >= 9) {

localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");

localIntent.setData(Uri.fromParts("package", context.getPackageName(), null));

} else if (Build.VERSION.SDK_INT <= 8) {

localIntent.setAction(Intent.ACTION_VIEW);

localIntent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails");

localIntent.putExtra("com.android.settings.ApplicationPkgName", context.getPackageName());

}

return localIntent;

}

到这,权限这个适配问题就解决了,接下来该是 Android 7.0 的适配了,这里我们只需要修改拍照的功能即可,相册是没有问题的。

Android7.0 适配

Android 7.0 就是 File 路径的变更,需要使用 FileProvider 来做,下面看拍照的代码。

拍照代码修改

/**

* 打开系统相机

*/

private void openSysCamera() {

Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

// cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(

// new File(Environment.getExternalStorageDirectory(), imgName)));

// File file = new File(Environment.getExternalStorageDirectory(), imgName);

try {

file = createOriImageFile();

} catch (IOException e) {

e.printStackTrace();

}

if (file != null) {

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {

imgUriOri = Uri.fromFile(file);

} else {

imgUriOri = FileProvider.getUriForFile(this, getPackageName() + ".provider", file);

}

cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, imgUriOri);

startActivityForResult(cameraIntent, CAMERA_RESULT_CODE);

}

}

File 对象的创建和 拍照图片的 Uri 对象创建方式更改。创建原图像保存的代码如下:

/**

* 创建原图像保存的文件

*

* @return

* @throws IOException

*/

private File createOriImageFile() throws IOException {

String imgNameOri = "HomePic_" + new SimpleDateFormat(

"yyyyMMdd_HHmmss").format(new Date());

File pictureDirOri = new File(getExternalFilesDir(

Environment.DIRECTORY_PICTURES).getAbsolutePath() + "/OriPicture");

if (!pictureDirOri.exists()) {

pictureDirOri.mkdirs();

}

File image = File.createTempFile(

imgNameOri, /* prefix */

".jpg", /* suffix */

pictureDirOri /* directory */

);

imgPathOri = image.getAbsolutePath();

return image;

}

拍照回调代码修改

case CAMERA_RESULT_CODE:

// tempFile = new File(Environment.getExternalStorageDirectory(), imgName);

// cropPic(Uri.fromFile(tempFile));

// 适配 Android7.0+

cropPic(getImageContentUri(file));

break;

getImageContentUri() 代码如下:

/**

* 7.0以上获取裁剪 Uri

*

* @param imageFile

* @return

*/

private Uri getImageContentUri(File imageFile) {

String filePath = imageFile.getAbsolutePath();

Cursor cursor = getContentResolver().query(

MediaStore.Images.Media.EXTERNAL_CONTENT_URI,

new String[]{MediaStore.Images.Media._ID},

MediaStore.Images.Media.DATA + "=? ",

new String[]{filePath}, null);

if (cursor != null && cursor.moveToFirst()) {

int id = cursor.getInt(cursor

.getColumnIndex(MediaStore.MediaColumns._ID));

Uri baseUri = Uri.parse("content://media/external/images/media");

return Uri.withAppendedPath(baseUri, "" + id);

} else {

if (imageFile.exists()) {

ContentValues values = new ContentValues();

values.put(MediaStore.Images.Media.DATA, filePath);

return getContentResolver().insert(

MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

} else {

return null;

}

}

}

代码适配解决完了,还有一个 FileProvider 问题需要做如下配置。

在 res 目录下创建一个名为 xml 的文件夹,并在其下创建一个名为 file_paths.xml 文件,其内容如下:

<?xml version="1.0" encoding="utf-8"?>

<paths xmlns:android="http://schemas.android.com/apk/res/android">

<files-path

name="images"

path="Android/data/com.example.package.name/files/Pictures/OriPicture/" />

<external-path

name="images"

path="Android/data/com.example.package.name/files/Pictures/OriPicture/" />

<external-files-path

name="images"

path="files/Pictures/OriPicture" />

<root-path

name="images"

path="" />

<root-path

name="images"

path="" />

</paths>

2. 在 AndroidMainfest.xml 中的 application 节点下做如下配置:

1

<!--FileProvider共享文件、缓存-->

<provider

android:name="android.support.v4.content.FileProvider"

android:authorities="com.cxs.yukumenu.provider"

android:exported="false"

android:grantUriPermissions="true">

<meta-data

android:name="android.support.FILE_PROVIDER_PATHS"

android:resource="@xml/file_paths" />

</provider>

其中这里的 android:authorities 属性值要和代码中 FileProvider.getUriForFile()的第二个参数值保持一致。

注意

代码已全部给出,粘贴即可使用

本次测试真机:华为5.1.1,一加8.0.0

总结

机型适配是个细活,但能锻炼排查问题和解决问题能力。

本文的代码有很多是可以抽取的,如果是在项目中使用,那么建议提取出特定功能的代码。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值