当前项目用到了拍照/从相册选取照片并裁剪展示到ImageView上的功能,在网上找到很多资料,却发现大同小异,知道我看到Ryan Hoo大神的文章 茅塞顿开,在此记录一下原理以及实现步骤.
一.点击相应Button弹出拍照/从相册选取/取消布局(如图)
实现方式很多 可以用Dialogfragment 我的做法是PopWindow,代码贴上:
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.PopupWindow;
import android.widget.TextView;
/** * PopWindow */
public class SelectPicturePopupWindow extends PopupWindow implements View.OnClickListener, PopupWindow.OnDismissListener {
private View mContentView;
private Context mContext;
private PictureCallBack mPictureCallBack;
public SelectPicturePopupWindow(Context context, PictureCallBack pictureCallBack) {
super(context);
this.mContext = context;
this.mPictureCallBack = pictureCallBack;
init(context);
}
private void init(Context context) {
mContentView = LayoutInflater.from(context).inflate(R.layout.popup_select_picture,null);
mContentView.setOnClickListener(this); // 设置SelectPicPopupWindow的View
this.setContentView(mContentView); // 设置SelectPicPopupWindow弹出窗体的宽
this.setWidth(ViewGroup.LayoutParams.MATCH_PARENT); // 设置SelectPicPopupWindow弹出窗体的高
this.setHeight(ViewGroup.LayoutParams.MATCH_PARENT); // 刷新状态
this.update(); // 实例化一个ColorDrawable颜色为半透明
ColorDrawable dw = new ColorDrawable(0000000000); // 点back键和其他地方使其消失,设置了这个才能触发OnDismisslistener ,设置其他控件变化等操作
this.setBackgroundDrawable(dw);
TextView cancel = (TextView) mContentView.findViewById(R.id.popup_cancel);
TextView gallery = (TextView) mContentView.findViewById(R.id.popup_select_from_gallery);
TextView takePicture = (TextView) mContentView.findViewById(R.id.popup_take_picture);
cancel.setOnClickListener(this);
gallery.setOnClickListener(this);
takePicture.setOnClickListener(this);
setOnDismissListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.popup_cancel: //取消
break;
case R.id.popup_select_from_gallery: //图片图库
if (mPictureCallBack != null) {
mPictureCallBack.onSelectFromGallery();
}
break;
case R.id.popup_take_picture: //拍照
if (mPictureCallBack != null) {
mPictureCallBack.onTakePicture();
}
break;
}
dismiss();
}
@Override
public void onDismiss() {
dismiss();
}
public interface PictureCallBack{
void onTakePicture();
void onSelectFromGallery();
}
/**
* 显示popupWindow
* @param parent
*/
public void show(View parent) {
if (!this.isShowing()) {
// 以下拉方式显示popupwindow
showAtLocation(parent, Gravity.NO_GRAVITY,0,0);
} else {
this.dismiss();
}
}
}
布局文件:
注释也很清晰 在此不做过多赘述,接下来进入重点,首先是拍照截图:
(一).在Button点击事件中弹出刚自定义的Popwindow:
@Override
public void onClick(View view) {
if (view.getId() == R.id.btn_hey) {
//弹出PopWindow
if (mSelectPicturePopup == null) {
mSelectPicturePopup = new SelectPicturePopupWindow(this, this);
}
mSelectPicturePopup.show(view);
}
}
(二).准备好使用到的Uri:
private static final String IMAGE_FILE_LOCATION = "file:///sdcard/test.jpg";
Uri imageUri = Uri.parse(IMAGE_FILE_LOCATION);
(三).在回调中调用Camera程序进行拍照:
//拍照 调用相机
@Override
public void onTakePicture() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, CODE_TAKE_PICTURE);
}
(四). onActivityResult中拿到返回的数据,并传递给截图的程序:
case CODE_TAKE_PICTURE:
if(resultCode == RESULT_OK){
cutPhoto(2,1,280,140,imageUri,TAKE_CUT);
}
break;
使用系统自带的裁剪程序的方法我封装好了一个方法:
Tip:
intent.putExtra("return-data", false);
false代表不返回数据,返回url
true代表返回数据,bitmap
private void cutPhoto(int aspectX,int aspectY,int outputX,int outputY,Uri uri,int requestCode) {
// 裁剪图片意图
Intent in = new Intent("com.android.camera.action.CROP");
in.setDataAndType(uri, "image/*");
in.putExtra("crop", "true");
// 裁剪框的比例,2:1
in.putExtra("aspectX", aspectX);
in.putExtra("aspectY", aspectY);
// 裁剪后输出图片的尺寸大小
in.putExtra("outputX", outputX);
in.putExtra("outputY", outputY);
in.putExtra("scale", true);
in.putExtra(MediaStore.EXTRA_OUTPUT, uri);
in.putExtra("return-data", true);
in.putExtra("outputFormat", "PNG");// 图片格式
in.putExtra("noFaceDetection", true);// 取消人脸识别
startActivityForResult(in, requestCode);// 开启一个带有返回值的Activity
}
(五).最后一步,处理返回的Bitmap:
case TAKE_CUT:
//拍照 拿到剪切数据
Bitmap bmap2 = data.getParcelableExtra("data");
iv_photo.setImageBitmap(bmap2);
如果你在裁剪意图中返回的是url,那么只需这样转换就好:
if(imageUri != null){
Bitmap bitmap = decodeUriAsBitmap(imageUri);
iv_photo.setImageBitmap(bitmap);
}
private Bitmap decodeUriAsBitmap(Uri uri){
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri));
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
return bitmap;
}
贴上效果图:
从相册图库选取只有意图和url不一样:
//从相册选取 调用android的图库
@Override
public void onSelectFromGallery() {
Intent i = new Intent(Intent.ACTION_PICK,MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(i, CODE_FROM_GALLERY);}
在onActivityResult中
case CODE_FROM_GALLERY:
if (data != null) {
Uri uri = data.getData();
if (uri != null) {
cutPhoto(3,2,280,140,uri,PHOTO_REQUEST_CUT);
}
}
至此功能已经完成 拿到bitmap之后,我们要上传到服务器,我这边要求是Base64,那么也很简单:
public String bitmaptoString(Bitmap bitmap) {
String s = null;
// 将Bitmap转换成Base64字符串
ByteArrayOutputStream bStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, bStream);
byte[] bytes = bStream.toByteArray();
s = Base64.encodeToString(bytes, Base64.DEFAULT);
return s;
}
bug以及我不理解的地方:
1.点击裁剪界面的取消按钮程序会直接退出,不知道怎么控制.
2(已解决,方法:将裁剪图片方法中返回数据改为false,使用url进行操作,因为直接返回bitmap,一张相片3M多,会造成OOM)
如果我将裁剪的尺寸设置为宽800 高400,那么裁剪界面的确定取消按钮都会无反应
如果我将裁剪的尺寸设置为宽560 高280那么程序会蹦,报这个错:
System: stat file error, path is /data/app/com.lxd.photocut-2/lib/arm64, exception is android.system.ErrnoException: stat failed: ENOENT (No such file or directory)
3.拍照后并没有保存到我们的手机相册中,我看了下qq微信,拍照后相册中也会有,我在查阅第一行代码(第二版)之后,按照郭霖大神的思路去写 发现无效.代码如下:
private Uri imageUri;
//创建File对象 用于存储拍照后的图片
File outputImage = new File(getExternalCacheDir(),"test111.jpg");
//如果文件存在 先删除
try {
if(outputImage.exists()){
outputImage.delete();
}
outputImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
if(Build.VERSION.SDK_INT >= 24){
imageUri = FileProvider.getUriForFile(MainActivity.this,"com.lxd.photocut.fileprovider",outputImage);
}else {
imageUri = Uri.fromFile(outputImage);
}
//调用相机
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, CODE_TAKE_PICTURE);
思路是这样的:首先创建File对象,放到了SD卡的应用关联缓存目录下(因为6.0系统开始读写SD卡也被列为危险权限),接着将File对象转换成uri对象.我觉得是因为我手机没有装SD卡 那么相片存储的位置该怎么获取到呢?
以上.希望大神交流指点~