一、加载图片
很多时候我们都需要上传头像进行展示,拍照,图库选择 我们会得到一个Uri,然后我们根据这个Uri去获取Bitmap资源。我这里用的实现方式是AsyncTask。加载图片的操作是在ImageCropActivity中去操作的。加载图片的思路是先去获取图片的大小,然后根据需要对图片进行采样缩放,最后将得到的Bitmap设置给我们今天的主角ImageCropView。
private class LoadImageAsyncTask extends AsyncTask<Uri, Void, Bitmap> {
protected Bitmap doInBackground(Uri... uris) {
Uri imageUri = uris[0];
// 先去加载图片的大小
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
loadImage(imageUri, options);
// 根据需要对图片进行缩放,这里的取值是960
int maxSize = ImageCropView.MAX_CROP_SIZE;
int sampleSize = 1;
int width = options.outWidth;
int height = options.outHeight;
while (width > maxSize && height > maxSize) {
sampleSize *= 2;
width /= 2;
height /= 2;
}
Log.d(TAG, "sample size is " + sampleSize);
options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
return loadImage(imageUri, options);
}
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if (bitmap == null) {
Toast.makeText(ImageCropActivity.this, "加载图片失败", Toast.LENGTH_LONG).show();
finish();
}
// 将Bitmap设置给我们的自定义控件ImageCropView
mImageCropView.setBitMap(bitmap);
}
}
private Bitmap loadImage(Uri uri, BitmapFactory.Options options) {
InputStream is = null;
try {
is = getContentResolver().openInputStream(uri);
if (is != null) {
return BitmapFactory.decodeStream(is, null, options);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
二、显示图片
首先,图片处于最下层,然后上面是一层带有透明度的黑色蒙层,但最神奇的是这个蒙层在中间被挖出了个圆形,使得这部分区域不会被遮住。细心的你可能还会发现,图片的高度是跟裁剪区域的高度一致的。所以,这部分最关键的地方就是如何实现在蒙层中间去挖一个洞出来,处理好这部分之后,画个Bitmap还不是一个方法调用而已。
在蒙层中挖一个洞出来这个需求,只要知道了原理之后,其实也并不难,而这就需要我们利用混合模式来实现了。我们可以先画上蒙层,然后利用SRC_OUT
模式来挖洞。
// 为了方便看效果,将颜色设置为白色
mMaskColor = getResources().getColor(R.color.white);
mMaskXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT);
mMaskPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mMaskPaint.setColor(Color.TRANSPARENT);
// 将Style设置为Fill模式,不能设置为Stroke
mMaskPaint.setStyle(Paint.Style.FILL);
protected void onDraw(Canvas canvas) {
int sc = canvas.saveLayer(0, 0, mWidth, mHeight, null, Canvas.ALL_SAVE_FLAG);
// 画上蒙层
canvas.drawColor(mMaskColor);
mMaskPaint.setXfermode(mMaskXfermode);
// 利用SRC_OUT模式在中间挖一个圆出来
canvas.drawOval(mCropRectF, mMaskPaint);
mMaskPaint.setXfermode(null);
canvas.restoreToCount(sc);
// 画上白色圆环
canvas.drawOval(mCropRectF, mRingPaint);
}
首先Activity的背景是黑色的,然后我们在画布画上了白色,然后利用SRC_OUT
模式让挖走中间这一块,就能够得到我们想要的效果,如图所示:
解决了蒙层的难点之后,我们再来显示图片,我们想要达到的效果是图片较小的边可以和裁剪区域一样大,这样就可以保证整张图片可以覆盖到整个裁剪区域了,所以,我们需要对图片进行缩放和位移的处理先。我们需要借助Matrix这个类来帮助我们操作图片。
/**
* 首次加载调整图片的显示
*/
private void fixImageSize() {
mMatrix.reset();
// 拿到裁剪区域的大小
float cropSize = mCropRectF.width();
int width = mSrcBitmap.getWidth();
int height = mSrcBitmap.getHeight();
int minSide = Math.min(width, height);
// 对较小的边进行缩放计算,
// 使得较小的一边宽度与裁剪框相等
if (minSide < cropSize) {
mInitScale = cropSize / minSide;
} else {
mInitScale = minSide / cropSize;
}
Log.d(TAG, "bitmap initialize scale is " + mInitScale);
float offsetX = 0;
float offsetY = 0;
// 计算图片的偏移值,使得图片相对于裁剪区域居中
if (minSide == width) {
offsetY = (height * mInitScale - cropSize) / 2;
} else {
offsetX = (width * mInitScale - cropSize) / 2;
}
// 对图片进行缩放
mMatrix.postScale(mInitScale, mInitScale);
// 居中显示
mMatrix.postTranslate(mCropRectF.left - offsetX, mCropRectF.top - offsetY);
invalidate();
}
在fixImageSize
这个方法里,我们先是拿到了裁剪区域的边长,然后拿图片较小的边来和它相比得到缩放倍数,接着再计算出另一边需要偏移多少,最后将这些操作设置给Matrix,然后触发重绘。
拿到Matrix后,就可以绘制图片了。
protected void onDraw(Canvas canvas) {
if (mSrcBitmap != null) {
canvas.drawBitmap(mSrcBitmap, mMatrix, null);
}
// 画完图片后绘制蒙层,代码不再重复给出了。
}
END