Android实现图片裁切
介绍
在应用开发中,如果涉及到个人信息,头像一般是不可避免的,类似这种情况,我们就需要用到图片裁切的功能,实现头像裁切,然后上传给服务器。
一般裁切的做法就是图层叠加选取框,然后根据坐标,计算裁切区域,通过图形函数裁切,既然了解大概原理,造轮子的事情就不做了,上github找开源库,发现了一个叫做edmodo/cropper的库,是原生实现的裁切。
地址:https://github.com/edmodo/cropper
但是使用后发现这个库还存以下两个主要问题,体验就很不好了。
1、图片太大会出现无法显示
2、图片过小又无法自适应
难道就没有更好的办法了?
又百度了一下,发现原来android的Intent已经自带有裁切的action了,而且体验非常好,只需要在Intent附上参数就可以实现相册/相机裁切图片。
地址:https://github.com/ryanhoo/PhotoCropper
原理
1、请看参数
2、拍照裁切需要先将结果保存在本地,然后再读取保存的结果裁切,否则默认情况下拍照直接返回的结果是缩略图。
3、通过onActivityResult来处理结果。
实现
参考的项目是经过重构的,但我总觉得看的晕,所以demo就全部放在一个页面,加上了个人理解注释,方便学习。
CropParams类
package com.example.cropimage; import android.graphics.Bitmap; import android.net.Uri; import android.os.Environment; public class CropParams { public static final String CROP_TYPE = "image/*"; public static final String OUTPUT_FORMAT = Bitmap.CompressFormat.JPEG .toString(); public static final int DEFAULT_ASPECT = 1; public static final int DEFAULT_OUTPUT = 300; public Uri uri; public String type; public String outputFormat; public String crop; public boolean scale; public boolean returnData; public boolean noFaceDetection; public boolean scaleUpIfNeeded; public int aspectX; public int aspectY; public int outputX; public int outputY; public CropParams() { uri = Uri.fromFile(Environment.getExternalStorageDirectory()) .buildUpon().appendPath("crop_cache_file.jpg").build(); type = CROP_TYPE; outputFormat = OUTPUT_FORMAT; crop = "true"; scale = true; returnData = false; noFaceDetection = true; scaleUpIfNeeded = true; aspectX = DEFAULT_ASPECT; aspectY = DEFAULT_ASPECT; outputX = DEFAULT_OUTPUT; outputY = DEFAULT_OUTPUT; } }
核心代码
package com.example.cropimage; import java.io.File; import java.io.FileNotFoundException; import android.support.v7.app.ActionBarActivity; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Bundle; import android.provider.MediaStore; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.widget.ImageView; import android.widget.Toast; public class MainActivity extends ActionBarActivity { ImageView imageView1; CropParams mCropParams = new CropParams(); public static final int REQUEST_CROP = 127; public static final int REQUEST_CAMERA = 128; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageView1 = (ImageView) findViewById(R.id.imageView1); // 弹出提示框选择照片 final String[] arrayFruit = new String[] { "拍照", "从相册选择照片" }; Dialog alertDialog = new AlertDialog.Builder(MainActivity.this) .setIcon(R.drawable.ic_launcher) .setItems(arrayFruit, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { switch (which) { case 0: // 进入相机 startActivityForResult( buildCaptureIntent(mCropParams.uri), REQUEST_CAMERA); break; case 1: // 进入相册 startActivityForResult( buildCropFromGalleryIntent(mCropParams), REQUEST_CROP); break; default: break; } } }) .setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub } }).create(); alertDialog.show(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == Activity.RESULT_CANCELED) { Toast.makeText(this, "Crop canceled!", Toast.LENGTH_LONG).show(); } else if (resultCode == Activity.RESULT_OK) { switch (requestCode) { case REQUEST_CROP: Log.d("cropImage", "Photo cropped!"); Toast.makeText(this, "Photo cropped!", Toast.LENGTH_LONG) .show(); imageView1.setImageURI(mCropParams.uri); break; case REQUEST_CAMERA: Intent intent = buildCropFromUriIntent(mCropParams); startActivityForResult(intent, REQUEST_CROP); break; } } super.onActivityResult(requestCode, resultCode, data); } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } /** * 结束后删除临时裁切图片,或者不删除,用来干别的。 */ @Override protected void onDestroy() { File file = new File(mCropParams.uri.getPath()); if (file.exists()) { boolean result = file.delete(); if (result) Log.i("cropImage", "Cached crop file cleared."); else Log.e("cropImage", "Failed to clear cached crop file."); } else { Log.w("cropImage", "Trying to clear cached crop file but it does not exist."); } super.onDestroy(); } /** * 创建裁切Intent * * @param action * 操作 * @param params * 参数 * @return */ public static Intent buildCropIntent(String action, CropParams params) { return new Intent(action, null) .setDataAndType(params.uri, params.type) // .setType(params.type) .putExtra("crop", params.crop).putExtra("scale", params.scale) .putExtra("aspectX", params.aspectX) .putExtra("aspectY", params.aspectY) .putExtra("outputX", params.outputX) .putExtra("outputY", params.outputY) .putExtra("return-data", params.returnData) .putExtra("outputFormat", params.outputFormat) .putExtra("noFaceDetection", params.noFaceDetection) .putExtra("scaleUpIfNeeded", params.scaleUpIfNeeded) .putExtra(MediaStore.EXTRA_OUTPUT, params.uri); } /** * 这一步是在相机拍照完成之后调用,注意action。 * * @param params * @return */ public static Intent buildCropFromUriIntent(CropParams params) { return buildCropIntent("com.android.camera.action.CROP", params); } /** * 创建相册裁切Intent * * @param params * 奥秘全在params里 * @return */ public static Intent buildCropFromGalleryIntent(CropParams params) { return buildCropIntent(Intent.ACTION_GET_CONTENT, params); } /** * 创建相机拍照Intent,由于相机拍照直接返回的是缩略图,所以一般的做法是拍照保存在本地之后,通过uri再读取一次 * * @param uri * 保存路径 * @return */ public static Intent buildCaptureIntent(Uri uri) { return new Intent(MediaStore.ACTION_IMAGE_CAPTURE).putExtra( MediaStore.EXTRA_OUTPUT, uri); } /** * 解析uri成bitmap * * @param context * @param uri * @return */ public static Bitmap decodeUriAsBitmap(Context context, Uri uri) { if (context == null || uri == null) return null; Bitmap bitmap; try { bitmap = BitmapFactory.decodeStream(context.getContentResolver() .openInputStream(uri)); } catch (FileNotFoundException e) { e.printStackTrace(); return null; } return bitmap; } }
完成之后看起来是这样的
demo地址:
链接:http://pan.baidu.com/s/1c0xqxEw 密码:u2ot
参考:
http://ryanhoo.github.io/blog/2014/05/26/the-ultimate-approach-to-crop-photos-on-android-1/
我不怕千万人阻挡,只怕自己投降。