【Android开发经验】设置用户头像并裁剪,仅仅是这么简单?

在做APP的时候,如果有用户系统功能,那么一般都逃不了这个需求,就是给用户设置头像,而设置头像,又包括从拍照和从相册选取两个方式,而且选择了之后,一般又都会要求对图像进行裁剪,让用户设置头像。今天这篇文章就是介绍如何完成这个需求的。


    我们首先分析一下需求。关于拍照和从相册选取,都可以向系统发送特定的Intent,唤起对应的系统程序,然后在onActivityResult里面,获取我们的数据即可。关于图像裁剪,有两种方式,一种是自己处理,比如利用第三方的开源项目,如Cropper(https://github.com/edmodo/cropper),来完成我们的需求,另外一种,我们可以直接利用系统提供的裁剪功能,实现图像的裁剪。


    在代码实现之前,我们先理理思路。如果是从相册获取的照片,在onActivityResult里面,我们可以获取到两种形式的数据,一种是Bitmap,一种是uri。如果Bitmap对象太大的话,可能就直接把我们的程序搞崩了,所以如果相册中的图片都是300px以下的图片,使用bitmap的方式是允许的,也是安全的,但是这在我们的手机里面也是基本不可能的。所以,我推荐无论大小都直接使用uri方式,因为获取到uri之后,就相当于拿到了图片的指针,想干嘛就干嘛~


    对于直接在相册获取图片来说,并不会出现太多的问题,但是如果你想使用拍照的图片进行处理的话,可能就麻烦一些。使用Intent调用起拍照APP之后,我们在onActivityResult里面也可以获取到bitmap或者是uri,这取决于我们在intent中设置了什么标志。但是,如果你直接获取拍照返回的Bitmap的话,可能并不是你想得到的结果,有可能返回的不是原图,而是模糊的缩略图,这是Android系统为了减少内存使用所做的策略,但是我们拿着这张缩略图是没法直接用的,所以,从拍照获取图片的时候,我们也应该使用uri的方式。


    好了,无论使用哪种方式,我们都获取到了所要处理的图片的uri,那么之后呢?当前是把这个uri作为数据使用Intent发送到进行裁剪的Activity里面,然后裁剪完成之后,在onActivityResult里面,把裁剪之后的bitmap对象设置给ImageView,然后保存起来,上传到服务器即可。


    了解了整个流程之后,我们就可以开始写代码了。

    如果想唤起相册,有两种方式,一种是Intent.ACTION_PICK,还有一种是Intent.ACTION_GET_CONTENT,代码如下,这两种方式都可以获取到图片的uri数据

[java]  view plain  copy
 
  1. //方式1,直接打开图库,只能选择图库的图片  
  2.                         Intent i = new Intent(Intent.ACTION_PICK,  
  3.                                 MediaStore.Images.Media.EXTERNAL_CONTENT_URI);  
  4.                         //方式2,会先让用户选择接收到该请求的APP,可以从文件系统直接选取图片  
  5.                         Intent intent = new Intent(Intent.ACTION_GET_CONTENT,  
  6.                                 MediaStore.Images.Media.EXTERNAL_CONTENT_URI);  
  7.                         intent.setType("image/*");  
  8.                         startActivityForResult(intent, PICK_FROM_FILE);  

     如果我们想从拍照获取,那么我们就可以使用下面的方式,先用一个file对象创建一个uri,然后绑定起来,这样拍照成功,返回之后,我们就可以根据uri获取到照片了,而且在file的路径保存了拍照的结果,下面是代码实现

[java]  view plain  copy
 
  1. Intent intent = new Intent(  
  2.                                 MediaStore.ACTION_IMAGE_CAPTURE);  
  3.                         imgUri = Uri.fromFile(new File(Environment  
  4.                                 .getExternalStorageDirectory(), "avatar_"  
  5.                                 + String.valueOf(System.currentTimeMillis())  
  6.                                 + ".png"));  
  7.                         intent.putExtra(MediaStore.EXTRA_OUTPUT, imgUri);  
  8.                         startActivityForResult(intent, PICK_FROM_CAMERA);  

     ok,获取到uri之后,我们应该怎么办呢?当前是再打开裁剪界面啦~

    打开裁剪界面的Intent是Intent intent = new Intent("com.android.camera.action.CROP");

    intent中得这些参数含义请参考下图

[java]  view plain  copy
 
  1. 附加选项    数据类型    描述  
  2. crop    String  发送裁剪信号  
  3. aspectX int X方向上的比例  
  4. aspectY int Y方向上的比例  
  5. outputX int 裁剪区的宽  
  6. outputY int 裁剪区的高  
  7. scale   boolean 是否保留比例  
  8. return-data boolean 是否将数据保留在Bitmap中返回  
  9. data    Parcelable  相应的Bitmap数据  
  10. circleCrop  String  圆形裁剪区域?  
  11. MediaStore.EXTRA_OUTPUT ("output")  URI 将URI指向相应的file:///...,详见代码示例  

    比较重要的是MediaStore.EXTRA_OUTPUT和return-data这两个选项。
    如果你将return-data设置为“true”,你将会获得一个与内部数据关联的Action,并且bitmap以此方式返回:(Bitmap)extras.getParcelable("data")。注意:如果你最终要获取的图片非常大,那么此方法会给你带来麻烦,所以你要控制outputX和outputY保持在较小的尺寸。 

    如果你将return-data设置为“false”,那么在onActivityResult的Intent数据中你将不会接收到任何Bitmap,相反,你需要将MediaStore.EXTRA_OUTPUT关联到一个Uri,此Uri是用来存放Bitmap的。 

    了解了这些之后,我们再看下面的代码应该就很清楚了。

[java]  view plain  copy
 
  1. private void doCrop() {  
  2.   
  3.         final ArrayList cropOptions = new ArrayList();  
  4.         Intent intent = new Intent("com.android.camera.action.CROP");  
  5.         intent.setType("image/*");  
  6.         List list = getPackageManager().queryIntentActivities(  
  7.                 intent, 0);  
  8.         int size = list.size();  
  9.   
  10.         if (size == 0) {  
  11.             Toast.makeText(this"can't find crop app", Toast.LENGTH_SHORT)  
  12.                     .show();  
  13.             return;  
  14.         } else {  
  15.             intent.setData(imgUri);  
  16.             intent.putExtra("outputX"300);  
  17.             intent.putExtra("outputY"300);  
  18.             intent.putExtra("aspectX"1);  
  19.             intent.putExtra("aspectY"1);  
  20.             intent.putExtra("scale"true);  
  21.             intent.putExtra("return-data"true);  
  22.   
  23.             // only one  
  24.             if (size == 1) {  
  25.                 Intent i = new Intent(intent);  
  26.                 ResolveInfo res = list.get(0);  
  27.                 i.setComponent(new ComponentName(res.activityInfo.packageName,  
  28.                         res.activityInfo.name));  
  29.                 startActivityForResult(i, CROP_FROM_CAMERA);  
  30.             } else {  
  31.                 // many crop app  
  32.                 for (ResolveInfo res : list) {  
  33.                     final CropOption co = new CropOption();  
  34.                     co.title = getPackageManager().getApplicationLabel(  
  35.                             res.activityInfo.applicationInfo);  
  36.                     co.icon = getPackageManager().getApplicationIcon(  
  37.                             res.activityInfo.applicationInfo);  
  38.                     co.appIntent = new Intent(intent);  
  39.                     co.appIntent  
  40.                             .setComponent(new ComponentName(  
  41.                                     res.activityInfo.packageName,  
  42.                                     res.activityInfo.name));  
  43.                     cropOptions.add(co);  
  44.                 }  
  45.   
  46.                 CropOptionAdapter adapter = new CropOptionAdapter(  
  47.                         getApplicationContext(), cropOptions);  
  48.   
  49.                 AlertDialog.Builder builder = new AlertDialog.Builder(this);  
  50.                 builder.setTitle("choose a app");  
  51.                 builder.setAdapter(adapter,  
  52.                         new DialogInterface.OnClickListener() {  
  53.                             public void onClick(DialogInterface dialog, int item) {  
  54.                                 startActivityForResult(  
  55.                                         cropOptions.get(item).appIntent,  
  56.                                         CROP_FROM_CAMERA);  
  57.                             }  
  58.                         });  
  59.   
  60.                 builder.setOnCancelListener(new DialogInterface.OnCancelListener() {  
  61.                     @Override  
  62.                     public void onCancel(DialogInterface dialog) {  
  63.   
  64.                         if (imgUri != null) {  
  65.                             getContentResolver().delete(imgUri, nullnull);  
  66.                             imgUri = null;  
  67.                         }  
  68.                     }  
  69.                 });  
  70.   
  71.                 AlertDialog alert = builder.create();  
  72.                 alert.show();  
  73.             }  
  74.         }  
  75.     }  

     上面的这些代码,很大的一部分在处理有多个可以接受裁剪intent请求的APP上面,如果你只想用默认的第一个APP,那么这些逻辑你可以删除。

    在说完这些东西之后,我们在onActivityResult里面可以这样处理:

[java]  view plain  copy
 
  1. @Override  
  2.     protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
  3.   
  4.         if (resultCode != RESULT_OK) {  
  5.             return;  
  6.         }  
  7.         switch (requestCode) {  
  8.         case PICK_FROM_CAMERA:  
  9.             doCrop();  
  10.             break;  
  11.         case PICK_FROM_FILE:  
  12.             imgUri = data.getData();  
  13.             doCrop();  
  14.             break;  
  15.         case CROP_FROM_CAMERA:  
  16.             if (null != data) {  
  17.                 setCropImg(data);  
  18.             }  
  19.             break;  
  20.         }  
  21.     }  

     不管从哪里选,都需要进入裁剪,然后裁剪结束之后,我们调用setCropImg(),把裁剪之后的结果设置给ImageView就可以了,如下

[java]  view plain  copy
 
  1. private void setCropImg(Intent picdata) {  
  2.         Bundle bundle = picdata.getExtras();  
  3.         if (null != bundle) {  
  4.             Bitmap mBitmap = bundle.getParcelable("data");  
  5.             mImageView.setImageBitmap(mBitmap);  
  6.             saveBitmap(Environment.getExternalStorageDirectory() + "/crop_"  
  7.                     + System.currentTimeMillis() + ".png", mBitmap);  
  8.         }  
  9.     }  

     saveBitmap是干嘛的?当然是把裁剪之后的Bitmap存起来了!就像下面这样

[java]  view plain  copy
 
  1. public void saveBitmap(String fileName, Bitmap mBitmap) {  
  2.         File f = new File(fileName);  
  3.         FileOutputStream fOut = null;  
  4.         try {  
  5.             f.createNewFile();  
  6.             fOut = new FileOutputStream(f);  
  7.             mBitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut);  
  8.             fOut.flush();  
  9.         } catch (Exception e) {  
  10.             e.printStackTrace();  
  11.         } finally {  
  12.             try {  
  13.                 fOut.close();  
  14.                 Toast.makeText(this"save success", Toast.LENGTH_SHORT).show();  
  15.             } catch (IOException e) {  
  16.                 e.printStackTrace();  
  17.             }  
  18.         }  
  19.   
  20.     }  

     好了,现在存起来了,就可以上传到服务器,完工~

    记得加权限

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

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

代码下载:https://github.com/ZhaoKaiQiang/CropImageDemo

你可能感兴趣的:(【Android开发经验】设置用户头像并裁剪,仅仅是这么简单?)