Android实现拍照、选择图片并裁剪图片功能

一、 实现拍照、选择图片并裁剪图片效果

按照之前博客的风格,首先看下实现效果。

    

二、 uCrop项目应用

想起之前看到的Yalantis/uCrop效果比较绚,但是研究源码之后发现在定制界面方面还是有一点的限制,于是在它的基础上做了修改Android-Crop,把定制界面独立出来,让用户去自由设置。下图为使用Android-Crop实现的模仿微信选择图片并裁剪Demo。

    

三、 实现思路

比较简单的选择设备图片裁剪,并将裁剪后的图片保存到指定路径;

调用系统拍照,将拍照图片保存在SD卡,然后裁剪图片并将裁剪后的图片保存在指定路径。
流程图如下所示:

    
 

四、 选择框实现
这里通过PopupWindow来实现,当然也可以根据需求采用其他方式实现。实现效果如下图所示:

    

1. XML布局

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<? xml version = "1.0" encoding = "utf-8" ?>
< RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
  android:layout_width = "fill_parent"
  android:layout_height = "wrap_content"
  android:gravity = "center_horizontal"
  android:orientation = "vertical" >
  
  < LinearLayout
  android:id = "@+id/pop_layout"
  android:layout_width = "match_parent"
  android:layout_height = "wrap_content"
  android:layout_alignParentBottom = "true"
  android:background = "#444"
  android:gravity = "center_horizontal"
  android:orientation = "vertical" >
  
  < Button
  android:id = "@+id/picture_selector_take_photo_btn"
  android:layout_width = "match_parent"
  android:layout_height = "wrap_content"
  android:layout_marginLeft = "10dip"
  android:layout_marginRight = "10dip"
  android:layout_marginTop = "10dp"
  android:background = "#4d69ff"
  android:padding = "10dp"
  android:text = "拍照"
  android:textColor = "#CEC9E7"
  android:textSize = "18sp"
  android:textStyle = "bold" />
  
  < Button
  android:id = "@+id/picture_selector_pick_picture_btn"
  android:layout_width = "match_parent"
  android:layout_height = "wrap_content"
  android:layout_marginLeft = "10dip"
  android:layout_marginRight = "10dip"
  android:layout_marginTop = "5dp"
  android:background = "#4d69ff"
  android:padding = "10dp"
  android:text = "从相册选择"
  android:textColor = "#CEC9E7"
  android:textSize = "18sp"
  android:textStyle = "bold" />
  
  < Button
  android:id = "@+id/picture_selector_cancel_btn"
  android:layout_width = "match_parent"
  android:layout_height = "wrap_content"
  android:layout_marginBottom = "15dip"
  android:layout_marginLeft = "10dip"
  android:layout_marginRight = "10dip"
  android:layout_marginTop = "20dp"
  android:background = "@android:color/white"
  android:padding = "10dp"
  android:text = "取消"
  android:textColor = "#373447"
  android:textSize = "18sp"
  android:textStyle = "bold" />
  </ LinearLayout >
  
</ RelativeLayout >

2. 代码编写

?
1
2
3
4
5
6
7
8
9
10
11
12
public SelectPicturePopupWindow(Context context) {
  super (context);
  LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  mMenuView = inflater.inflate(R.layout.layout_picture_selector, null );
  takePhotoBtn = (Button) mMenuView.findViewById(R.id.picture_selector_take_photo_btn);
  pickPictureBtn = (Button) mMenuView.findViewById(R.id.picture_selector_pick_picture_btn);
  cancelBtn = (Button) mMenuView.findViewById(R.id.picture_selector_cancel_btn);
  // 设置按钮监听
  takePhotoBtn.setOnClickListener( this );
  pickPictureBtn.setOnClickListener( this );
  cancelBtn.setOnClickListener( this );
}

创建SelectPicturePopupWindow的时候设置按钮的监听。这里编写一个选择监听接口:

?
1
2
3
4
5
6
/**
  * 选择监听接口
  */
public interface OnSelectedListener {
  void OnSelected(View v, int position);
}

回调的参数为点击的按钮View以及当前按钮的索引,那么只要在选择监听里面返回接口的回调就可以啦。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
public void onClick(View v) {
  switch (v.getId()) {
  case R.id.picture_selector_take_photo_btn:
  if ( null != mOnSelectedListener) {
  mOnSelectedListener.OnSelected(v, 0 );
  }
  break ;
  case R.id.picture_selector_pick_picture_btn:
  if ( null != mOnSelectedListener) {
  mOnSelectedListener.OnSelected(v, 1 );
  }
  break ;
  case R.id.picture_selector_cancel_btn:
  if ( null != mOnSelectedListener) {
  mOnSelectedListener.OnSelected(v, 2 );
  }
  break ;
  }
}

PopupWindow的初始化创建、监听设置好之后,只要提供显示与隐藏两个方法就可以了。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
  * 把一个View控件添加到PopupWindow上并且显示
  *
  * @param activity
  */
public void showPopupWindow(Activity activity) {
  popupWindow = new PopupWindow(mMenuView, // 添加到popupWindow
  ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
  popupWindow.setBackgroundDrawable( new ColorDrawable(Color.TRANSPARENT));
  popupWindow.showAtLocation(activity.getWindow().getDecorView(), Gravity.CENTER | Gravity.BOTTOM, 0 , 0 );
  popupWindow.setAnimationStyle(android.R.style.Animation_InputMethod); // 设置窗口显示的动画效果
  popupWindow.setFocusable( false ); // 点击其他地方隐藏键盘 popupWindow
  popupWindow.update();
}
?
1
2
3
4
5
6
7
8
9
/**
  * 移除PopupWindow
  */
public void dismissPopupWindow() {
  if (popupWindow != null && popupWindow.isShowing()) {
  popupWindow.dismiss();
  popupWindow = null ;
  }
}

OK,到这里选择框的实现就完成了。

五、使用选择框

通过我们上面对选择框的封装,使用起来就比较简单了,只需要初始化及设置选择的监听就可以啦。

1.初始化选择框

?
1
2
mSelectPicturePopupWindow = new SelectPicturePopupWindow(mContext);
mSelectPicturePopupWindow.setOnSelectedListener( this );

2.设置选择框监听

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void OnSelected(View v, int position) {
  switch (position) {
  case 0 :
  // TODO: "拍照"按钮被点击了
  break ;
  case 1 :
  // TODO: "从相册选择"按钮被点击了
  break ;
  case 2 :
  // TODO: "取消"按钮被点击了
  break ;
  }
}

然后在Fragment上进行封装,我们取名为PictureSelectFragment。

六、拍照并保存图片

调用系统的拍照,并把拍摄的图片保存到指定位置。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void OnSelected(View v, int position) {
  switch (position) {
  case 0 :
  // "拍照"按钮被点击了
  mSelectPicturePopupWindow.dismissPopupWindow();
  Intent takeIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
  //下面这句指定调用相机拍照后的照片存储的路径
  takeIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile( new File(mTempPhotoPath)));
  startActivityForResult(takeIntent, CAMERA_REQUEST_CODE);
  break ;
  case 1 :
  // TODO: "从相册选择"按钮被点击了
  break ;
  case 2 :
  // TODO: "取消"按钮被点击了
  break ;
  }
}

这里的指定位置为sd卡本目录下
mTempPhotoPath = Environment.getExternalStorageDirectory() + File.separator + "photo.jpeg"; 

当拍摄照片完成时会回调到onActivityResult,我们在这里处理图片的裁剪就可以了。

?
1
2
3
4
5
6
7
8
9
10
11
@Override
public void onActivityResult( int requestCode, int resultCode, Intent data) {
  if (resultCode == mActivity.RESULT_OK) {
  switch (requestCode) {
  case CAMERA_REQUEST_CODE:
  // TODO: 调用相机拍照
  break ;
  }
  }
  super .onActivityResult(requestCode, resultCode, data);
}

七、相册选择图片

调用系统的选择图片

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public void OnSelected(View v, int position) {
  switch (position) {
  case 0 :
  // "拍照"按钮被点击了
  mSelectPicturePopupWindow.dismissPopupWindow();
  Intent takeIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
  // 下面这句指定调用相机拍照后的照片存储的路径
  takeIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile( new File(mTempPhotoPath)));
  startActivityForResult(takeIntent, CAMERA_REQUEST_CODE);
  break ;
  case 1 :
  // "从相册选择"按钮被点击了
  mSelectPicturePopupWindow.dismissPopupWindow();
  Intent pickIntent = new Intent(Intent.ACTION_PICK, null );
  // 如果限制上传到服务器的图片类型时可以直接写如:"image/jpeg 、 image/png等的类型"
  pickIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*" );
  startActivityForResult(pickIntent, GALLERY_REQUEST_CODE);
  break ;
  case 2 :
  // TODO: "取消"按钮被点击了
  break ;
  }
}

当拍选择图片完成时会回调到onActivityResult,在这里处理选择的返回结果。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void onActivityResult( int requestCode, int resultCode, Intent data) {
  if (resultCode == mActivity.RESULT_OK) {
  switch (requestCode) {
  case CAMERA_REQUEST_CODE:
  // TODO: 调用相机拍照
  break ;
  case GALLERY_REQUEST_CODE:
  // TODO: 直接从相册获取
  break ;
  }
  }
  super .onActivityResult(requestCode, resultCode, data);
}

八、使用Crop裁剪图片

裁剪图片,这里设置宽高比为1:1,最大尺寸为512*512,当然可以根据自己的需求来设置。

?
1
2
3
4
5
6
7
8
9
10
11
12
/**
  * 裁剪图片方法实现
  *
  * @param uri
  */
public void startCropActivity(Uri uri) {
  UCrop.of(uri, mDestinationUri)
  .withAspectRatio( 1 , 1 )
  .withMaxResultSize( 512 , 512 )
  .withTargetActivity(CropActivity. class )
  .start(mActivity, this );
}

CropActiivty裁剪完成时会回调到onActivityResult,在这里处理选择的返回结果。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
public void onActivityResult( int requestCode, int resultCode, Intent data) {
  if (resultCode == mActivity.RESULT_OK) {
  switch (requestCode) {
  case CAMERA_REQUEST_CODE: // 调用相机拍照
  File temp = new File(mTempPhotoPath);
  startCropActivity(Uri.fromFile(temp));
  break ;
  case GALLERY_REQUEST_CODE: // 直接从相册获取
  startCropActivity(data.getData());
  break ;
  case UCrop.REQUEST_CROP:
  // TODO: 裁剪图片结果
  break ;
  case UCrop.RESULT_ERROR:
  // TODO: 裁剪图片错误
  break ;
  }
  }
  super .onActivityResult(requestCode, resultCode, data);
}

CropActivity的界面如下所示:

当然也可以轻松设计成如下两图:

1. XML布局

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
< RelativeLayout xmlns:android = "http://schemas.android.com/apk/res/android"
  android:layout_width = "match_parent"
  android:layout_height = "match_parent"
  android:clipToPadding = "true"
  android:fitsSystemWindows = "true" >
  
  < include layout = "@layout/toolbar_layout" />
  
  < FrameLayout
  android:layout_width = "match_parent"
  android:layout_height = "match_parent"
  android:layout_below = "@+id/toolbar"
  android:background = "#000" >
  
  < com.kevin.crop.view.UCropView
  android:id = "@+id/weixin_act_ucrop"
  android:layout_width = "match_parent"
  android:layout_height = "match_parent"
  android:visibility = "invisible" />
  
  </ FrameLayout >
  
  < android.support.design.widget.CoordinatorLayout
  android:layout_width = "match_parent"
  android:layout_height = "match_parent" >
  
  < android.support.design.widget.FloatingActionButton
  android:id = "@+id/crop_act_save_fab"
  android:layout_width = "wrap_content"
  android:layout_height = "wrap_content"
  android:layout_gravity = "bottom|right"
  android:layout_margin = "@dimen/fab_margin"
  android:src = "@mipmap/ic_done_white"
  fab:fabSize = "normal" />
  </ android.support.design.widget.CoordinatorLayout >
  
  
</ RelativeLayout >

可以发现非常简单,只有一个主要的CropView,这就是uCrop框架为我们提供的。

2. 代码编写

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
protected void initViews() {
  initToolBar();
  
  mGestureCropImageView = mUCropView.getCropImageView();
  mOverlayView = mUCropView.getOverlayView();
  
  // 设置允许缩放
  mGestureCropImageView.setScaleEnabled( true );
  // 设置禁止旋转
  mGestureCropImageView.setRotateEnabled( false );
  // 设置外部阴影颜色
  mOverlayView.setDimmedColor(Color.parseColor( "#AA000000" ));
  // 设置周围阴影是否为椭圆(如果false则为矩形)
  mOverlayView.setOvalDimmedLayer( false );
  // 设置显示裁剪边框
  mOverlayView.setShowCropFrame( true );
  // 设置不显示裁剪网格
  mOverlayView.setShowCropGrid( false );
  
  final Intent intent = getIntent();
  setImageData(intent);
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
private void setImageData(Intent intent) {
  Uri inputUri = intent.getParcelableExtra(UCrop.EXTRA_INPUT_URI);
  mOutputUri = intent.getParcelableExtra(UCrop.EXTRA_OUTPUT_URI);
  
  if (inputUri != null && mOutputUri != null ) {
  try {
  mGestureCropImageView.setImageUri(inputUri);
  } catch (Exception e) {
  setResultException(e);
  finish();
  }
  } else {
  setResultException( new NullPointerException( "Both input and output Uri must be specified" ));
  finish();
  }
  
  // 设置裁剪宽高比
  if (intent.getBooleanExtra(UCrop.EXTRA_ASPECT_RATIO_SET, false )) {
  float aspectRatioX = intent.getFloatExtra(UCrop.EXTRA_ASPECT_RATIO_X, 0 );
  float aspectRatioY = intent.getFloatExtra(UCrop.EXTRA_ASPECT_RATIO_Y, 0 );
  
  if (aspectRatioX > 0 && aspectRatioY > 0 ) {
  mGestureCropImageView.setTargetAspectRatio(aspectRatioX / aspectRatioY);
  } else {
  mGestureCropImageView.setTargetAspectRatio(CropImageView.SOURCE_IMAGE_ASPECT_RATIO);
  }
  }
  
  // 设置裁剪的最大宽高
  if (intent.getBooleanExtra(UCrop.EXTRA_MAX_SIZE_SET, false )) {
  int maxSizeX = intent.getIntExtra(UCrop.EXTRA_MAX_SIZE_X, 0 );
  int maxSizeY = intent.getIntExtra(UCrop.EXTRA_MAX_SIZE_Y, 0 );
  
  if (maxSizeX > 0 && maxSizeY > 0 ) {
  mGestureCropImageView.setMaxResultImageSizeX(maxSizeX);
  mGestureCropImageView.setMaxResultImageSizeY(maxSizeY);
  } else {
  Log.w(TAG, "EXTRA_MAX_SIZE_X and EXTRA_MAX_SIZE_Y must be greater than 0" );
  }
  }
}

以上为CropView的配置,更多配置请参考项目源码。

最重要的,裁剪保存图片:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void cropAndSaveImage() {
  OutputStream outputStream = null ;
  try {
  final Bitmap croppedBitmap = mGestureCropImageView.cropImage();
  if (croppedBitmap != null ) {
  outputStream = getContentResolver().openOutputStream(mOutputUri);
  croppedBitmap.compress(Bitmap.CompressFormat.JPEG, 85 , outputStream);
  croppedBitmap.recycle();
  
  setResultUri(mOutputUri, mGestureCropImageView.getTargetAspectRatio());
  finish();
  } else {
  setResultException( new NullPointerException( "CropImageView.cropImage() returned null." ));
  }
  } catch (Exception e) {
  setResultException(e);
  finish();
  } finally {
  BitmapLoadUtils.close(outputStream);
  }
}

PictureSelectFragment处理裁剪成功的返回值

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
  * 处理剪切成功的返回值
  *
  * @param result
  */
private void handleCropResult(Intent result) {
  deleteTempPhotoFile();
  final Uri resultUri = UCrop.getOutput(result);
  if ( null != resultUri && null != mOnPictureSelectedListener) {
  Bitmap bitmap = null ;
  try {
  bitmap = MediaStore.Images.Media.getBitmap(mActivity.getContentResolver(), resultUri);
  } catch (FileNotFoundException e) {
  e.printStackTrace();
  } catch (IOException e) {
  e.printStackTrace();
  }
  mOnPictureSelectedListener.onPictureSelected(resultUri, bitmap);
  } else {
  Toast.makeText(mContext, "无法剪切选择图片" , Toast.LENGTH_SHORT).show();
  }
}

处理裁剪失败的返回值

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
  * 处理剪切失败的返回值
  *
  * @param result
  */
private void handleCropError(Intent result) {
  deleteTempPhotoFile();
  final Throwable cropError = UCrop.getError(result);
  if (cropError != null ) {
  Log.e(TAG, "handleCropError: " , cropError);
  Toast.makeText(mContext, cropError.getMessage(), Toast.LENGTH_LONG).show();
  } else {
  Toast.makeText(mContext, "无法剪切选择图片" , Toast.LENGTH_SHORT).show();
  }
}

这里设置了选择的回调接口,便于封装抽取。

?
1
2
3
4
5
6
7
8
9
10
11
12
/**
  * 图片选择的回调接口
  */
public interface OnPictureSelectedListener {
  /**
  * 图片选择的监听回调
  *
  * @param fileUri
  * @param bitmap
  */
  void onPictureSelected(Uri fileUri, Bitmap bitmap);
}

经过五、六、七步骤,我们的PictureSelectFragment就搞定了,在使用的时候只要继承它,几行代码就搞定了。

九、PictureSelectFragment使用

?
1
2
3
4
5
6
7
// 设置图片点击监听
mPictureIv.setOnClickListener( new View.OnClickListener() {
  @Override
  public void onClick(View v) {
  selectPicture();
  }
});
?
1
2
3
4
5
6
7
8
9
10
11
// 设置裁剪图片结果监听
setOnPictureSelectedListener( new OnPictureSelectedListener() {
  @Override
  public void onPictureSelected(Uri fileUri, Bitmap bitmap) {
  mPictureIv.setImageBitmap(bitmap);
  
  String filePath = fileUri.getEncodedPath();
  String imagePath = Uri.decode(filePath);
  Toast.makeText(mContext, "图片已经保存到:" + imagePath, Toast.LENGTH_LONG).show();
  }
});

OK,经过我们上面的封装及基类抽取,在使用的时候还是非常简单的。

十、下载

源码及示例
用到的Android-Crop库

更多内容大家可以参考专题《Android图片处理》进行学习。

以上就是本文的全部内容,希望对大家学习Android软件编程有所帮助。

原文链接:http://blog.csdn.net/xuehuayous/article/details/51324656

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值