Android中头像选择,图片上传等功能几乎是每一个APP必备的功能,那么关于怎么使用相机,如何进行照片选择,以及选择后的图片裁剪,这一系列的问题都需要逐一解决。这也是本篇文章的主要内容。
一、应用场景
微信朋友圈上传图片,头像上传等功能,经常就会用到以上功能。
二、业务逻辑
主要分为两种业务逻辑:拍照,选择图片。
拍照逻辑:
1.A 界面,点击按钮调用相机拍照;
2.拍照界面拍照后,点击确认得到拍完照片,跳转到 B 界面进行预览;
3.B 界面进行图片裁剪,裁剪后确认,返回A界面进行图片回显;
选择图片逻辑:
1.A界面,点击按钮调用相册选择图片;
2.相册界面选择图片后,跳转到B界面进行预览;
3.B 界面进行图片裁剪,裁剪后确认,返回A界面进行图片回显;
从上面可以清楚地看出,两种方式的主要区别在第一步上面,一种是选择调用相机,另一种选择是调用相册。
下面我们来介绍具体代码逻辑。
三、拍照具体实现
以如下使用场景为例:
头像上传的使用。
1.调用相机
Android 程序上实现拍照功能的方式分为两种:第一种是利用相机的 API 来自定义相机,第二种是利用 Intent 调用系统指定的相机拍照。下面讲的内容都是针对第二种实现方式的应用。
简单使用
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
Uri fileUri = Uri.fromFile(mPhotoFile);
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);
startActivityForResult(captureIntent, CAPTURE_PHOTO_REQUEST_CODE);
很简单,通过上述四行代码就实现了调用系统相机。
加入MediaStore.EXTRA_OUTPUT,使得拍照后的图片输出到对应路径下。
然而由于Android手机的碎片化,我们之前调用系统指定的相机app来拍照,有些手机可能会没有这个app,所以在使用之前要检查是否有系统相机。
/**
* 判断系统中是否存在可以启动的相机应用
*
* @return 存在返回true,不存在返回false
*/
public boolean hasCamera() {
PackageManager packageManager = mActivity.getPackageManager();
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
List list = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
return list.size() > 0;
}
2.照片预览及图片裁剪
由于之前使用startActivityForResult方式调用系统相机,那么在拍照完成后,会返回到上述页面,这时候需要重写onActivityResult方法,根据之前传入的mPhotoFile路径,就获取了图片所在地,因为拍照后的图片就存在该路径下。
然后我们只需要在开启一个照片预览Activity,进行后续裁剪就可以了。
因为这里我们调用系统裁剪,所以就不设置预览Activity,直接跳转到裁剪页面就可以了。
//拍照完成后 获取目标文件 跳转到裁剪页面
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CapturePhotoHelper.CAPTURE_PHOTO_REQUEST_CODE) {
//获取拍照后图片路径
File photoFile = mCapturePhotoHelper.getPhoto();
if (photoFile != null) {
if (resultCode == RESULT_OK) {
Uri uri = Uri.fromFile(photoFile);
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(uri, "image/*");
//intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", 300);
intent.putExtra("outputY", 300);
intent.putExtra("scale", true);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
intent.putExtra("return-data", false);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true); // no face detection
intent = Intent.createChooser(intent, "裁剪图片");
startActivityForResult(intent, REQUEST_PICKER_AND_CROP);
} else {
if (photoFile.exists()) {
photoFile.delete();
}
}
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
其中要注意几个 extra 字段:
注意:return-data: 设为 true 的时候,在 onActivityResult() 中可以直接通过 data.getParcelableExtra("data") 得到裁剪后的 Bitmap 对象。但是当 Bitmap 过大时,就不能使用这种方法了,容易出现OOM现象,需要通过获取文件,然后先缩放,再加载。
如下图所示:
3.回显图片
如上如所示,修剪图片完成后,点击确定,然后就可以编写回显逻辑。
同样在onActivityReuslt方法中
if(requestCode ==REQUEST_PICKER_AND_CROP){
File photoFile = mCapturePhotoHelper.getPhoto();
//存放到相册
BitmapUtils.displayToGallery(this, photoFile);
//更新UI 显示图像
InformationBean informationBean = mList.get(0);
informationBean.setContent(photoFile.getAbsoluteFile().toString());
informationBean.setSet(true);
mAdapter.notifyItemChanged(0);
}
上述采用了RecyclerView,具体更新头像逻辑在Adapter中。
不过同样由于Android碎片化问题,图片会产生各种各样的问题,比如:拍出来的照片“歪了”,拍完照怎么闪退了,图片无法显示。
以上这些问题都比较坑,不过不要紧,已经有前人为我们趟出一条血路,全部都处理好了,详细代码及具体实现参考:你需要知道的Android拍照适配方案。
这里我们只负责应用就好了:
String content = bean.getContent();
final File file = new File(content);
((SpecialViewHolder) holder).mImageView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
((SpecialViewHolder) holder).mImageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
mWidth = ((SpecialViewHolder) holder).mImageView.getMeasuredWidth();
mHeight = ((SpecialViewHolder) holder).mImageView.getMeasuredHeight();
Bitmap bitmap = BitmapUtils.decodeBitmapFromFile(file, mWidth, mHeight);
if (bitmap != null) {
//检查是否有被旋转,并进行纠正
System.out.println("文件所占空间:"+"file.getTotalSpace()");
int degree = BitmapUtils.getBitmapDegree(file.getAbsolutePath());
if (degree != 0) {
bitmap = BitmapUtils.rotateBitmapByDegree(bitmap, degree);
}
((SpecialViewHolder) holder).mImageView.setImageBitmap(bitmap);
}
}
});
先获取控件的宽高,进行压缩,避免图片无法显示的问题。
然后检查有没有被旋转,如果旋转,那么通过矩阵矫正,避免照片"歪了"的问题。
至于拍完照片后闪退,可以通过重写onSaveInstanceState 和 onRestoreInstanceState 来实现。
回显结果:
四、选择图片具体实现
由于和上述方式只是第一步有区别,我们就具体看看第一步的实现。
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
intent.putExtra(MediaStore.EXTRA_OUTPUT,uri);
startActivityForResult(intent, REQUEST_PICK_IMAGE);
直接开启系统图片选择应用即可,不用额外设置MediaStore.EXTRA_OUTPUT,因为图片已经保存在数据库内了,可直接获取,如下所示。
然后在onActivityResult中,获取图片存储路径,跳转到裁剪页面。
if (requestCode == REQUEST_PICK_IMAGE) {
//获取选择图片后图片路径
if (resultCode == RESULT_OK) {
Uri uri = data.getData();
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(uri, "image/*");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", 200);
intent.putExtra("outputY", 200);
intent.putExtra("scale", true);
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(tempFile));
intent.putExtra("return-data", false);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true); // no face detection
intent = Intent.createChooser(intent, "裁剪图片");
startActivityForResult(intent, REQUEST_PICKER_AND_CROP_2);
}
}
后面回显步骤和上述一致。
五、总结
上述介绍了拍照,照片选择以及图片剪裁的使用,由于使用的是系统自带的应用,所以可能出现一些意想不到的适配问题,还有待解决。另外,由于是系统自带,功能比较单一,且无法使用个性化,如有这方面的需求,可以使用一些流行的第三方库,Github上有很多优秀的实现。