相册获取、相机拍摄,裁剪圆形头像
2016-12-04 20:30
351 查看
相册获取、相机拍摄,裁剪圆形头像
应用场景
很多应用都有个人中心,个人中心就会有头像,现在一般都流行圆形头像,那么怎么设置呢使用步骤
这里参考了网上各位大神的文章,因为中途遇到几个坑,折磨了一天,快要疯了,因为本人也是菜鸟,所以被虐是必然了,当然我们看到新人遇到问题时,不要觉得那个问题是简单的,当初如果你没有遇到这种需求或没接触过那些坑时,或许也会感到不简单的。因为遇到的坑较多,忘记了参考那些文章,反正是东看看西看看,然后拼凑到一起,稍微总结一些坑
废话不多,先分析一下步骤
要将图片设置成圆形,需要自定义展示图片的控件
获取相册的意图
获取相机的意图。
裁剪程序
保存裁剪后的图片
设置展示圆形图片
遇到的坑
拍照时返回的data获取不到uri,这是个坑
在onActivityResult方法中,如果判断data==null,就返回,则拍照时就不能通过,这个搞不懂啥原因
拍照后,想通过保存图片的路径去获取图片并裁剪,结果失败,可能直接这么获取得到的图片太大,而通过api获取相册的方式,返回的图片其实是被压缩过的,所以不用担心OOM
AlertDialog如果是自定义视图,在每次启动时需要将视图的父容器移除,否则挂掉。
ViewGroup p = (ViewGroup) mView.getParent(); if (p != null) { p.removeAllViewsInLayout(); }
读取相册时,当android大于4.4版本时需要处理,否则挂掉。
//android大于4.4版本处理,否则挂掉 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { String url = PhotoClipperUtil.getPath(context, uri); intent.setDataAndType(Uri.fromFile(new File(url)), "image/*"); }
实现步骤
1.添加权限
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
2.自定义控件
package skxy.dev.safehttps.view; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.NinePatchDrawable; import android.util.AttributeSet; import android.widget.ImageView; import skxy.dev.safehttps.R; /** * ClassName : RoundImageView * Created by: skxy on 2016/12/4. * DES :自定义圆形图片类 */ public class RoundImageView extends ImageView { private int mBorderThickness = 0; private Context mContext; private int defaultColor = 0xFFFFFFFF; // 如果只有其中一个有值,则只画一个圆形边框 private int mBorderOutsideColor = 0; private int mBorderInsideColor = 0; // 控件默认长、宽 private int defaultWidth = 0; private int defaultHeight = 0; public RoundImageView(Context context) { super(context); mContext = context; } public RoundImageView(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; setCustomAttributes(attrs); } public RoundImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mContext = context; setCustomAttributes(attrs); } private void setCustomAttributes(AttributeSet attrs) { TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.roundedimageview); mBorderThickness = a.getDimensionPixelSize(R.styleable.roundedimageview_border_thickness, 0); mBorderOutsideColor = a.getColor(R.styleable.roundedimageview_border_outside_color,defaultColor); mBorderInsideColor = a.getColor(R.styleable.roundedimageview_border_inside_color, defaultColor); } @Override protected void onDraw(Canvas canvas) { Drawable drawable = getDrawable() ; if (drawable == null) { return; } if (getWidth() == 0 || getHeight() == 0) { return; } this.measure(0, 0); if (drawable.getClass() == NinePatchDrawable.class) return; Bitmap b = ((BitmapDrawable) drawable).getBitmap(); Bitmap bitmap = b.copy(Bitmap.Config.ARGB_8888, true); if (defaultWidth == 0) { defaultWidth = getWidth(); } if (defaultHeight == 0) { defaultHeight = getHeight(); } int radius = 0; if (mBorderInsideColor != defaultColor && mBorderOutsideColor != defaultColor) {// 定义画两个边框,分别为外圆边框和内圆边框 radius = (defaultWidth < defaultHeight ? defaultWidth : defaultHeight) / 2 - 2 * mBorderThickness; // 画内圆 drawCircleBorder(canvas, radius + mBorderThickness / 2,mBorderInsideColor); // 画外圆 drawCircleBorder(canvas, radius + mBorderThickness + mBorderThickness / 2, mBorderOutsideColor); } else if (mBorderInsideColor != defaultColor && mBorderOutsideColor == defaultColor) {// 定义画一个边框 radius = (defaultWidth < defaultHeight ? defaultWidth : defaultHeight) / 2 - mBorderThickness; drawCircleBorder(canvas, radius + mBorderThickness / 2, mBorderInsideColor); } else if (mBorderInsideColor == defaultColor && mBorderOutsideColor != defaultColor) {// 定义画一个边框 radius = (defaultWidth < defaultHeight ? defaultWidth : defaultHeight) / 2 - mBorderThickness; drawCircleBorder(canvas, radius + mBorderThickness / 2, mBorderOutsideColor); } else {// 没有边框 radius = (defaultWidth < defaultHeight ? defaultWidth : defaultHeight) / 2; } Bitmap roundBitmap = getCroppedRoundBitmap(bitmap, radius); canvas.drawBitmap(roundBitmap, defaultWidth / 2 - radius, defaultHeight / 2 - radius, null); } /** * 获取裁剪后的圆形图片 * @param radius 半径 */ public Bitmap getCroppedRoundBitmap(Bitmap bmp, int radius) { Bitmap scaledSrcBmp; int diameter = radius * 2; // 为了防止宽高不相等,造成圆形图片变形,因此截取长方形中处于中间位置最大的正方形图片 int bmpWidth = bmp.getWidth(); int bmpHeight = bmp.getHeight(); int squareWidth = 0, squareHeight = 0; int x = 0, y = 0; Bitmap squareBitmap; if (bmpHeight > bmpWidth) {// 高大于宽 squareWidth = squareHeight = bmpWidth; x = 0; y = (bmpHeight - bmpWidth) / 2; // 截取正方形图片 squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth, squareHeight); } else if (bmpHeight < bmpWidth) {// 宽大于高 squareWidth = squareHeight = bmpHeight; x = (bmpWidth - bmpHeight) / 2; y = 0; squareBitmap = Bitmap.createBitmap(bmp, x, y, squareWidth,squareHeight); } else { squareBitmap = bmp; } if (squareBitmap.getWidth() != diameter || squareBitmap.getHeight() != diameter) { scaledSrcBmp = Bitmap.createScaledBitmap(squareBitmap, diameter,diameter, true); } else { scaledSrcBmp = squareBitmap; } Bitmap output = Bitmap.createBitmap(scaledSrcBmp.getWidth(), scaledSrcBmp.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(output); Paint paint = new Paint(); Rect rect = new Rect(0, 0, scaledSrcBmp.getWidth(),scaledSrcBmp.getHeight()); paint.setAntiAlias(true); paint.setFilterBitmap(true); paint.setDither(true); canvas.drawARGB(0, 0, 0, 0); canvas.drawCircle(scaledSrcBmp.getWidth() / 2, scaledSrcBmp.getHeight() / 2, scaledSrcBmp.getWidth() / 2, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(scaledSrcBmp, rect, rect, paint); bmp = null; squareBitmap = null; scaledSrcBmp = null; return output; } /** * 边缘画圆 */ private void drawCircleBorder(Canvas canvas, int radius, int color) { Paint paint = new Paint(); /* 去锯齿 */ paint.setAntiAlias(true); paint.s eac1 etFilterBitmap(true); paint.setDither(true); paint.setColor(color); /* 设置paint的style为STROKE:空心 */ paint.setStyle(Paint.Style.STROKE); /* 设置paint的外框宽度 */ paint.setStrokeWidth(mBorderThickness); canvas.drawCircle(defaultWidth / 2, defaultHeight / 2, radius, paint); } }
3.自定义控件相关属性,在res/values/目录下创建attr文件
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="roundedimageview"> <attr name="border_thickness" format="dimension" /> <attr name="border_inside_color" format="color" /> <attr name="border_outside_color" format="color"></attr> </declare-styleable> </resources>
4.相关处理工具类
package skxy.dev.safehttps; import android.annotation.TargetApi; import android.content.ContentUris; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.provider.DocumentsContract; import android.provider.MediaStore; /** * ClassName : PhotoClipperUtil * Created by: skxy on 2016/12/3. * DES :图片路径获取工具类 */ public class PhotoClipperUtil { @TargetApi(Build.VERSION_CODES.KITKAT) public static String getPath(final Context context, final Uri uri) { final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; // DocumentProvider if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { // ExternalStorageProvider if (isExternalStorageDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; if ("primary".equalsIgnoreCase(type)) { return Environment.getExternalStorageDirectory() + "/" + split[1]; } } // DownloadsProvider else if (isDownloadsDocument(uri)) { final String id = DocumentsContract.getDocumentId(uri); final Uri contentUri = ContentUris.withAppendedId( Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); return getDataColumn(context, contentUri, null, null); } // MediaProvider else if (isMediaDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); final String[] split = docId.split(":"); final String type = split[0]; Uri contentUri = null; if ("image".equals(type)) { contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; } else if ("video".equals(type)) { contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; } else if ("audio".equals(type)) { contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; } final String selection = "_id=?"; final String[] selectionArgs = new String[] { split[1] }; return getDataColumn(context, contentUri, selection, selectionArgs); } } // MediaStore (and general) else if ("content".equalsIgnoreCase(uri.getScheme())) { // Return the remote address if (isGooglePhotosUri(uri)) return uri.getLastPathSegment(); return getDataColumn(context, uri, null, null); } // File else if ("file".equalsIgnoreCase(uri.getScheme())) { return uri.getPath(); } return null; } /** * Get the value of the data column for this Uri. This is useful for * MediaStore Uris, and other file-based ContentProviders. * * @param context The context. * @param uri The Uri to query. * @param selection (Optional) Filter used in the query. * @param selectionArgs (Optional) Selection arguments used in the query. * @return The value of the _data column, which is typically a file path. */ public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { Cursor cursor = null; final String column = "_data"; final String[] projection = { column }; try { cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { final int index = cursor.getColumnIndexOrThrow(column); return cursor.getString(index); } } finally { if (cursor != null) cursor.close(); } return null; } /** * @param uri The Uri to check. * @return Whether the Uri authority is ExternalStorageProvider. */ public static boolean isExternalStorageDocument(Uri uri) { return "com.android.externalstorage.documents".equals(uri.getAuthority()); } /** * @param uri The Uri to check. * @return Whether the Uri authority is DownloadsProvider. */ public static boolean isDownloadsDocument(Uri uri) { return "com.android.providers.downloads.documents".equals(uri.getAuthority()); } /** * @param uri The Uri to check. * @return Whether the Uri authority is MediaProvider. */ public static boolean isMediaDocument(Uri uri) { return "com.android.providers.media.documents".equals(uri.getAuthority()); } /** * @param uri The Uri to check. * @return Whether the Uri authority is Google Photos. */ public static boolean isGooglePhotosUri(Uri uri) { return "com.google.android.apps.photos.content".equals(uri.getAuthority()); } }
5.基本准备好了,现在来设置要显示的控件布局
<skxy.dev.safehttps.view.RoundImageView android:id="@+id/iv" android:layout_width="100dp" android:layout_height="100dp" android:layout_gravity="center" android:src="@mipmap/ic_launcher"/>
6.处理点击和显示的逻辑
package skxy.dev.safehttps;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import skxy.dev.safehttps.view.RoundImageView;
public class PhontoActivity extends AppCompatActivity implements View.OnClickListener {
private static final int REQUEST_CODE_FROM_PHOTO = 0;
private static final int SELECT_CLIPPER_PIC = 1;
private static final int RESULT_CAMERA_ONLY = 100;
private Uri imageUri;
public RoundImageView mImageView;
public LinearLayout mView;
public TextView mFromPhoto;
public TextView mFromCamera;
public AlertDialog mDialog;
public String mFileName;
public Uri mImageCropUri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_phonto);
initView();
initUri();
}
private void initView() {
mImageView = (RoundImageView) findViewById(R.id.iv);
mView = (LinearLayout) View.inflate(this, R.layout.dialogview, null);
mFromPhoto = (TextView) mView.findViewById(R.id.fromphoto);
mFromCamera = (TextView) mView.findViewById(R.id.fromcamera);
mImageView.setOnClickListener(this);
mFromCamera.setOnClickListener(this);
mFromPhoto.setOnClickListener(this);
}
private void initUri() {
File file;
File cropFile;
//必须在点击选择相册或相机之前初始化
if (hasSdcard()) {
File rootFile = new File(Environment.getExternalStorageDirectory() + "/DCIM/Camera");
if (!rootFile.exists()) {
rootFile.mkdir();
}
file = new File(rootFile + "/temp.jpg");
cropFile = new File(rootFile + "/temp_crop.jpg");
} else {
//如果没有外部存储卡,只能使用临时目录
file =new File(getCacheDir()+"/temp.jpg") ;
cropFile = new File(getCacheDir() + "/temp_crop.jpg");
}
imageUri = Uri.fromFile(file);
mImageCropUri = Uri.fromFile(cropFile);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.iv:
showDialog();
break;
case R.id.fromcamera:
takeCameraOnly();
break;
case R.id.fromphoto:
goAlbums();
break;
}
}
private void showDialog() {
//因为每次点击后,这个布局视图就有了父容器,重新加载时就会附着到新的容器中,导致挂掉
//因此每次都先将父容器清空
ViewGroup p = (ViewGroup) mView.getParent(); if (p != null) { p.removeAllViewsInLayout(); }AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setView(mView);
mDialog = builder.create();
mDialog.setCanceledOnTouchOutside(true);
mDialog.show();
}
//从相机获取
private void takeCameraOnly() {
if (hasSdcard()) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra("return-data", false);
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("noFaceDetection", true);
startActivityForResult(intent, RESULT_CAMERA_ONLY);
} else {
Toast.makeText(PhontoActivity.this, "没有可用的外部存储设备", Toast.LENGTH_SHORT).show();
}
}
//判断SD卡
private boolean hasSdcard() {
String state = Environment.getExternalStorageState();
if (state.equals(Environment.MEDIA_MOUNTED)) {
return true;
}
return false;
}
//从相册获取
private void goAlbums() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
startActivityForResult(intent, REQUEST_CODE_FROM_PHOTO);
}
/**
* 裁剪大图
*
* @param context
* @param uri
*/
private void clipperBigPic(Context context, Uri uri) {
if (null == uri) {
return;
}
Intent intent = new Intent("com.android.camera.action.CROP");
//android大于4.4版本处理,否则挂掉 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { String url = PhotoClipperUtil.getPath(context, uri); intent.setDataAndType(Uri.fromFile(new File(url)), "image/*"); }
//发送裁剪命令
intent.putExtra("crop", true);
//X方向上的比例
intent.putExtra("aspectX", 1);
//Y方向上的比例
intent.putExtra("aspectY", 1);
// 裁剪后输出图片的宽高像素
//裁剪区的宽
intent.putExtra("outputX", 120);
//裁剪区的高
intent.putExtra("outputY", 120);
//是否保留比例
intent.putExtra("scale", true);
//返回数据
intent.putExtra("return-data", true);
intent.putExtra("noFaceDetection", true);
//输出图片格式
intent.putExtra("outputFormat", Bitmap.CompressFormat.PNG.toString());
//裁剪图片保存位置
intent.putExtra(MediaStore.EXTRA_OUTPUT,mImageCropUri);
startActivityForResult(intent, SELECT_CLIPPER_PIC);
}
//处理返回的相片
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != Activity.RESULT_OK)
return;
switch (requestCode) {
case REQUEST_CODE_FROM_PHOTO://相册选择
//获取图片后裁剪图片
clipperBigPic(this, data.getData());
break;
case SELECT_CLIPPER_PIC:
Bundle extras = data.getExtras();
if (extras != null) {
try {
//重新读取保存好的照片,已经裁剪过的,可以上传或直接设置到控件上
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(mImageCropUri));
mImageView.setImageBitmap(bitmap);
mDialog.dismiss();
} catch (Exception e) {
e.printStackTrace();
}
}
break;
case RESULT_CAMERA_ONLY:
//相机返回的data获取不到uri,这是个坑
clipperBigPic(this, imageUri);
break;
}
}
/**
* 保存图片
*
* @param data
*/
private void saveBitmap(Intent data) {
Bundle bundle = data.getExtras();
if (bundle != null) {
Bitmap bitmap = bundle.getParcelable("data");
//存储图片的名字
mFileName = System.currentTimeMillis() + ".png";
File file = new File(Environment.getExternalStorageDirectory() + "/DCIM/Camera", mFileName);
try {
file.createNewFile();
FileOutputStream fileOutputStream = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
fileOutputStream.flush();
fileOutputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
7.最终效果,这里没有做Aandroid6.0的权限适配,需要的自己添加一下。
8.最后感谢那些大神提供的文章,这里列举几篇
http://blog.csdn.net/chenguang79/article/details/52230507 http://blog.csdn.net/harvic880925/article/details/43163175 http://www.2cto.com/kf/201401/270144.html
相关文章推荐
- Android之圆形头像(实现相机拍摄+相册选择+图片裁剪功能)
- Android之圆形头像(实现相机拍摄+相册选择+图片裁剪功能)
- 超实用的Andoird圆形头像设置 —— 实现相机、相册选择并裁剪尽在一行代码之间(兼容Android6.0/7.0)
- Android圆形头像设置(实现相机、相册选择并裁剪)兼容6.0/7.0
- android圆形头像:相机裁剪+相册选择
- 圆形头像__调取相机相册赋值,并更新接口
- iOS 从相机或相册获取图片并裁剪
- Android上传头像代码,相机,相册,裁剪
- iOS从相册获取以及用相机拍摄视频并缓存到沙盒
- 更换头像用相机拍摄或者从相册选择
- 打开相机和获取相册图片后实现裁剪
- Android打开系统相机、相册,并进行裁剪图片的工具类CameraUtil,以及相册获取图片注意要点。
- Android从相机或相册获取图片裁剪
- Android中调用系统相机、系统相册来获取图片,并裁剪图片。
- 用户头像重相册或相机里获取
- [iOS]从相册获取以及用相机拍摄视频并缓存到沙盒
- iOS 相机拍照、相册获取照片(仿微信) 一一 从相册获取图片、图片裁剪
- Android 从相册获取最近拍摄的多张照片(获取相机拍照所存储的照片)
- iOS开发swift如何调用系统相册和相机获取图片设置用户头像
- iOS 从相机或相册获取图片并裁剪