Android开发之设置用户头像

现在市场上的App,一般都有用户中心这个小模块,其中也就涉及到了修改头像的功能。一般来说,我们可通过直接拍照或从相册选择照片的方式来进行设置。对于从相册选择照片,基本上不会出现什么问题,毕竟图片已经是存在的,后面我们简单说一下。这里我们重点说一下通过拍照来设置图片的过程。

拍照方式设置头像

先说下基本的思路:调用系统相机,打开相机App(这里会弹出你的手机上所有的相机App供选择)进行拍照,拍照完成后通过onActivityResult返回数据。这样我们就拿到了拍摄的照片路径。

// 调用系统相机
startActivityForResult(new Intent(MediaStore.ACTION_IMAGE_CAPTURE), REQUEST_CODE_TAKE_PHOTO);

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    super.onActivityResult(requestCode, resultCode, intent);
    if(intent != null) {
        switch (requestCode) {
            case REQUEST_CODE_TAKE_PHOTO:
                  // 获取拍照的照片的路径 
                  iconUrl = intent.getData();
                break;
            default:
                break;
        }
    }
}

看似我们已经拿到了数据,可以进一步操作了,然而现实总是残酷的。这种方式在以前,应该还是很好使的。毕竟当时的手机基本都是1G内存,为了防止传送大图片出现OOM,在拍照截图的过程中默认传递的都是缩略图,这样获取的图片质量都比较低。但是随着手机内存容量的不断升级,这些ROM定制厂商为了提高用户的体验,有些就直接采用了传递大图的方式。而传递大图的方式与传递缩略图有着本质的区别,具体表现为以下两点:

  1. 传递缩略图时,直接将缩略图通过intent来进行传递,而传递大图的方式,返回的intent不携带任何数据,这就使得之前的代码无法执行;
  2. 传递大图时,需要设置图片的路径。其基本思路是,先设置一个图片的路径,然后进行拍照,拍照完成后将图片保存到改路径,然后回调onActivityResult,这样我们就可以直接操作之前设置的路径对应的文件了。

既然某些手机已经采用了传递大图,导致我们无法获取缩略图,而传递缩略图又会损耗图片的质量。那么我们就应该统一使用传递大图的形式,再对其进行下一步的操作。具体代码如下:

打开相机:

// 构造图片的保存路径
Date date = new Date();
String fileName = "IMG_" + new SimpleDateFormat("yyyyMMddHHmmss").format(date);
File file = new File(AppInstances.getPathManager().getImgCacheDir() + fileName);
iconUri = Uri.fromFile(file);
// 启动相机
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// 设置输出路径
intent.putExtra(MediaStore.EXTRA_OUTPUT, iconUri);
startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO);

拍照完成后进行下一步操作:

@Override               
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    super.onActivityResult(requestCode, resultCode, intent);
    if(intent == null) {
         if (requestCode == REQUEST_CODE_TAKE_PHOTO && iconUri != null) {
            File file = new File(iconUri.getPath());
            // 打开相机页面后,如果按返回键也会回调,所以需要判断是否拍摄了照片
            if (file.exists()) {
                    // 裁剪图片
                startCropImage(iconUri);
            }
        }
    }
}

选择照片设置头像

打开相册选择照片

Intent intent = new Intent(Intent.ACTION_PICK, null);
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
startActivityForResult(intent, REQUEST_CODE_CHOOSE_IMAGE);

开始操作选择的照片

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    super.onActivityResult(requestCode, resultCode, intent);
    if(intent != null) {
        switch (requestCode) {
            case REQUEST_CODE_CHOOSE_IMAGE:
                if (intent.getData() != null) {
                    iconUri = intent.getData();
                    startCropImage(iconUri);
                }
                break;
            default:
                break;
        }
    }
}

裁剪图片

在拍照或选择照片后,一般设置头像之前我们还需要对照片进行裁剪,以减小图片大小,提高图片的上传速度(毕竟头像一般都显示的比较小,没必要上传一张大图)。为了方便,裁剪图片我们还是调用系统提供的裁剪页面,与启动相机和相册不同的是,裁剪页面有许多参数可供选择,可定制度比较高。这里列出了常用的参数,直接将其封装成了一个方法,便于各种方式的图片裁剪。

注意:与调用相机传递图片一样,许多手机在默认情况下传递的是缩略图,导致本来就小的图片裁剪后更模糊,这里我们依然采用传递大图的形式,需要注意的是,裁剪后的图片的保存路径和裁剪之前的原图应该区分一下,不要直接设置成原图的路径。在开始的时候,我就直接设置成了原图路径,结果在一款国产的平板上出现了裁剪失败的情况。

调用系统裁剪页面

public void startCropImage(Uri uri) {
    Intent intent = new Intent("com.android.camera.action.CROP");
    intent.setDataAndType(uri, "image/*");
    // 图片处于可裁剪状态
    intent.putExtra("crop", "true");
    // aspectX aspectY 是宽高的比例
    intent.putExtra("aspectX", 1);
    intent.putExtra("aspectY", 1);
    // 是否之处缩放
    intent.putExtra("scale", true);
    // 设置图片的输出大小, 对于普通的头像,应该设置一下,可提高头像的上传速度
    intent.putExtra("outputX", 300);
    intent.putExtra("outputY", 300);
    // 以Uri的方式传递照片
    File cropFile = new File(AppInstances.getPathManager().getImgCacheDir() + "crop_image.jpg");
    cropImageUri = Uri.fromFile(cropFile);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, cropImageUri);
    // 设置图片输出格式
    intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
    intent.putExtra("return-data", false);
    // 关闭人脸识别
    intent.putExtra("noFaceDetection", false);
    startActivityForResult(intent, REQUEST_CODE_CROP_IMAGE);
}

其实到这一步,我们已经获取到了裁剪后的图片路径了,就是前面设置的cropImageUri, 接下来我们只需要在回调中上传图片即可:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    super.onActivityResult(requestCode, resultCode, intent);
    if(intent != null) {
        LogUtil.i("intent not null requestCode=" + requestCode);
        switch (requestCode) {
            case REQUEST_CODE_CROP_IMAGE:
                    // 上传图片到服务器
                uploadIcon();
                break;
            default:
                break;
        }
    }
}

关于图片上传,有很多种方式,具体实现代码与所使用的网络框架有关,这里贴一下OkHttp的简单实现方式:

public void uploadIcon(String url, String filePath) {
    File file = new File(filePath);
    if (file.exists()) {
        MultipartBuilder builder = new MultipartBuilder();
        builder.type(MultipartBuilder.FORM);
        builder.addFormDataPart("file", file.getName(), RequestBody.create(MediaType.parse("image/jpeg;charset=utf-8"), file));
        RequestBody requestBody = builder.build();
        Request request = new Request.Builder()
                .url(url)
                .post(requestBody)
                .build();
        OkHttpClient httpClient = new OkHttpClient();
        httpClient.newCall(request).enqueue(new MyCallBack());
    } else {
        LogUtil.i("文件不存在");
    }
}

至此,修改头像的数据上传部分就完成了,接下来只需要更新页面上的头像即可。对于修改头像功能,存在许多兼容性问题,如红米1手机,在调用相机或裁剪图片页面时,会经常销毁页面,导致设置的图片路径数据丢失,对于这种情况,我们需要在onSaveInstanceState中保存这些路径,然后在onSaveInstanceState中进行恢复,否则经常遇到头像裁剪失败。所以,对于这个小功能,应该在上线前进行机型的兼容性测试,以保证功能的完整性。

,