头像设置是Android 开发中比较常见的需求,基本内容有:
通过相机拍取一张图片,或者从图库、文件中选择一张图片
对图片进行裁剪
上传图片,监听上传进度
通过相机拍取图片
调用相机拍照需要两个步骤:
应用内生成一个空文件,使用FileProvider获取文件的Uri,照片最后会保存在这个文件里。一定要确保这个文件所在目录在FileProvider的共享目录下
使用Intent启动相机,并将文件Uri传入(相当于告诉相机将照片保存到这个文件里)
创建FileProvider:
在AndroidManifest.xml中添加provider,注意android:authorities属性:
android:name="android.support.v4.content.FileProvider" android:authorities="com.xxx.xxx.fileProvider" android:exported="false" android:grantUriPermissions="true"> android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"/>
在/res/xml/目录下新建file_paths.xml文件,定义FileProvider共享文件的目录:
name="download" path="Download"/> name="my_images" path="Pictures/"/> 具体使用方式参见API FileProvider 创建文件,注意文件一定要在FileProvider共享文件的目录中,否则会报错: public static File createImageFile(Context context) throws IOException { String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); String imageFileName = "IMG_" + timeStamp; File dir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES); return File.createTempFile(imageFileName, ".jpg", dir); } 生成Uri,AUTHORITY即AndroidManifest.xml中android:authorities属性: File imageFile = FileUtil.createImageFile(this); Uri imageUri = FileProvider.getUriForFile(getContext(), AUTHORITY, imageFile); 使用Intent启动相机拍照并传入生成的Uri: Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); if (takePictureIntent.resolveActivity(getPackageManager()) != null) { startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE); } 这样当拍照成功后照片就存储在我们创建的文件中了 从图库或文件选择图片 从图库选择文件: Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); startActivityForResult(intent, REQUEST_IMAGE_PICK); 从图库或文件选择图片: Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null); intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); startActivityForResult(intent, REQUEST_IMAGE_PICK); 获取结果: @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case REQUEST_IMAGE_PICK: if (resultCode == RESULT_OK) { Uri uri = data.getData(); if (uri != null) { // 获得了图片 } else { // TODO 处理异常 } } break; } } 对图片进行裁剪 由于系统提供了图片裁剪的功能,这样就减小了开发者自己实现图片裁剪的难度,这里使用系统提供的图片裁剪功能。和拍照类似,为了获得裁剪后的图片,我们也需要创建一个空文件,并将其传入Intent: void startCropImage(Uri uri) { Intent intent = new Intent("com.android.camera.action.CROP"); intent.setDataAndType(uri, "image/*"); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // 使图片处于可裁剪状态 intent.putExtra("crop", "true"); // 裁剪框的比例(根据需要显示的图片比例进行设置) intent.putExtra("aspectX", 1); intent.putExtra("aspectY", 1); // 让裁剪框支持缩放 intent.putExtra("scale", true); // 裁剪后图片的大小(注意和上面的裁剪比例保持一致) intent.putExtra("outputX", 300); intent.putExtra("outputY", 300); // 传递原图路径 try { cropFile = FileUtil.getCacheImageFile(this); } catch (IOException e) { e.printStackTrace(); // 处理错误 showDialog("打开文件失败"); return; } cropImageUri = FileProvider.getUriForFile(getContext(), AUTHORITY, cropFile); intent.putExtra(MediaStore.EXTRA_OUTPUT, cropImageUri); // 设置裁剪区域的形状,默认为矩形,也可设置为原形 intent.putExtra("circleCrop", false); // 设置图片的输出格式 intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); // return-data=true传递的为缩略图,小米手机默认传递大图,所以会导致onActivityResult调用失败 intent.putExtra("return-data", false); // 是否需要人脸识别 intent.putExtra("noFaceDetection", true); List for (ResolveInfo resolveInfo : resInfoList) { String packageName = resolveInfo.activityInfo.packageName; grantUriPermission(packageName, cropImageUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); } if (resInfoList.size() > 0) { startActivityForResult(intent, REQUEST_IMAGE_CROP); } } 好了,现在来梳理下拍照、选图片、裁剪的流程: 通过Initent启动系统功能并传入处理后图片的保存位置(如果需要) 通过onActivityResult获取调用是否成功,并做进一步处理 注意: 因为系统权限的限制,可能需要对目标程序加入对应的读写权限 选择图片返回的Uri直接做为图片裁剪的源文件可能导致出错,这个时候需要将原来的Uri复制到我们自己生成的文件中,再启动图片裁剪 图片上传并显示进度 图片上传本质是文件上传,这里使用Retrofit 添加进度回调接口: public interface ProgressRequestListener { void onRequestProgress(long bytesSend, long contentLength, boolean done); } 编写ProgressRequestBody类实现插入进度回调: public class ProgressRequestBody extends RequestBody { private final RequestBody requestBody; private final List private BufferedSink bufferedSink; public ProgressRequestBody(RequestBody requestBody, List this.requestBody = requestBody; this.listeners = listeners; } @Override public MediaType contentType() { return requestBody.contentType(); } @Override public long contentLength() throws IOException { return requestBody.contentLength(); } @Override public void writeTo(BufferedSink sink) throws IOException { //包装 bufferedSink = Okio.buffer(sink(sink)); //写入 requestBody.writeTo(bufferedSink); //必须调用flush,否则最后一部分数据可能不会被写入 bufferedSink.flush(); } private Sink sink(Sink sink) { return new ForwardingSink(sink) { //当前写入字节数 long bytesWritten = 0L; //总字节长度,避免多次调用contentLength()方法 long contentLength = 0L; @Override public void write(Buffer source, long byteCount) throws IOException { super.write(source, byteCount); if (contentLength == 0) { //获得contentLength的值,后续不再调用 contentLength = contentLength(); } //增加当前写入的字节数 bytesWritten += byteCount; //回调 for (ProgressRequestListener listener : listeners) { listener.onRequestProgress(bytesWritten, contentLength, bytesWritten == contentLength); } } }; } } 编写服务器接口: @Multipart @POST("xxx") Observable 服务器接口调用: public Observable RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), imageFile); ProgressRequestBody progressRequestBody = new ProgressRequestBody(requestBody, requestListeners); MultipartBody.Part body = MultipartBody.Part.createFormData("name", "filename", progressRequestBody); // 根据需要填写名字 return serverApi.uploadAvatar(body); } 好了,我们只需要把裁剪后的图片上传,并添加ProgressRequestListener,头像设置就搞定了~ 参考文章 官方培训 Android 启动系统相机,相册,裁剪图片及6.0权限管理 你需要知道的Android拍照适配方案 Android之图片选择与裁剪 Retrofit 2.0 超能实践(三),轻松实现多文件/图片上传/Json字符串/表单 OkHttp与Retrofit上传文件详解 Retrofit Multipart多文件上传
龙盛乾园
为什么会有雀斑