综述
图片选取&拍照篇链接 细数图片上传功能用到的知识点-图片选取&拍照篇
我们先来看最终效果
其中涉及到的知识点
- 蒙版的绘制
- 手势拖动,手势放大
- 图片的matrix操作
- 图片的裁剪
下面我就对这每个知识点进行详细的说明
蒙版的绘制
可以看到界面上覆盖了一层半透明的蒙版,中间扣出了一个正方形的区域,为了使边界更加明显我又绘制了一个白色的正方形的线框。
实现方式自定义View 重写ondraw
方法绘图作为背景。利用PorterDuffXfermode
中的XOR
模式扣掉中间的矩形区域
//矩形区域的坐标可根据你的需求定制
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
canvas.drawRect(mleft, mtop, mright, mbottom, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
paint.setColor(Color.parseColor("#FFFFFF"));
paint.setStrokeWidth(2f);
paint.setStyle(Paint.Style.STROKE);
canvas.drawRect(mleft - 2, mtop - 2, mright + 2, mbottom + 2, paint);
手势拖动与手势放大
这里有个业务需求,无论图片怎么样变化都必须保证图片的边界在矩形区域之外。另外结合我对于图片展示的需求,我决定自定义一个处理这些业务的imageView
。
先说拖动手势的获取。重写onTouchEvent
方法。记录回传坐标的变化,move事件中获得x和y的偏移量。拖动所需的数据很简单就能拿到。而手势放大需要我们支持多点触控的记录。要想获取的多点触控的数据,需要根据event.getAction() & MotionEvent.ACTION_MASK
来判断action ,之后获取当action
为MotionEvent.ACTION_POINTER_DOWN
时的触摸事件,该事件即为第二只手指的事件。
那么如何在move
事件中区分放大和拖动呢?我们这个时候就需要一个状态量来记录当前手势状态。回调ACTION_DOWN
后为拖动状态。回调ACTION_POINTER_DOWN
之后则为放大状态。
//mode 为状态量,记录手势状态
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
//拖动
mode = DRAG;
currentMaritx.set(this.getImageMatrix());
startPoint.set(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE:
//拖动
if (mode == DRAG) {
dragDo(event);
}
//放大
else if (mode == ZOOM) {
zoomDo(event);
}
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_POINTER_UP:
break;
case MotionEvent.ACTION_POINTER_DOWN:
//判断第二个手指与第一个手指的位置。设置阈值避免一个手指两个触摸点的情况。
startDis = distance(event);
if (startDis > 10f) {
mode = ZOOM;
midPoint = mid(event);
currentMaritx.set(this.getImageMatrix());
}
break;
}
return true;
dragDo()
函数能够拿到手指拖动偏移量,处理图片拖动事件。而zoomDo()
可以根据两个点距离变化倍率获得应该放大的倍率
//获取两个手的距离
float endDis = distance(event);
//当前距离除以初始距离
float scale = endDis / startDis;
private float distance(MotionEvent event) {
float dx = event.getX(1) - event.getX(0);
float dy = event.getY(1) - event.getY(0);
return (float) Math.sqrt(dx * dx + dy * dy);
}
图片的matrix操作
图片的移动和放大等操作,我们选择利用imageView的matrix属性进行改变。
Matrix matrix=getImageMatrix();
//偏移操作,参数为想x,y的偏移量
matrix.postTranslate(realdx, realdy);
//缩放操作,参数为 x,y的放大量,和缩放中心
matrixteamp.postScale(scale, scale, midPoint.x, midPoint.y);
别忘了,我们有业务需求,要保证图片的边界在矩形区域之外。
首先我们要对传入的图片进行预处理,因为图片一开始的位置就可能出现越界的情况,另外我们需要合理的放大缩小图片来让用户的裁剪功能更加的快捷。
//图片位置预制
private void PreDealPic() {
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
if ((float) (bitmapWidth) / bitmapHeight - ViewMaxWidth/ ViewMaxHeight > 0) {
// 宽图 宽/高 大于视图的宽/高
float bili = ViewMaxWidth/ bitmapWidth;
//超宽图 宽占满后,图片上下边越界情况
if (bitmapHeight * bili < maxHeight) {
//放大至
float bei = maxWidth / (float) bitmapHeight;
final Matrix matrix = new Matrix();
matrix.postScale(bei , bei );
//将其移动到边界
matrix.postTranslate(left , top);
moveImage.setScaleType(ImageView.ScaleType.MATRIX);
moveImage.setImageMatrix(matrix);
}
} else {
//长图 宽/高 小于视图的宽/高
float bili = ViewMaxHeight/ bitmapHeight;
//超长图 高占满后,图片左右边越界情况
if (bitmapWidth * bili < maxWidth) {
//放大至
float bei = maxWidth /bitmapWidth;
Matrix matrix = new Matrix();
matrix.postScale(bei , bei );
//移动到边界
matrix.postTranslate(left, top);
moveImage.setScaleType(ImageView.ScaleType.MATRIX);
moveImage.setImageMatrix(matrix);
}
}
我们先来说拖动,直接将偏移量传参给偏移函数即可。让用户直接拖动是无法避免越界操作的。如此我们便需要对手势后图片的坐标进行判断,若本次操作会导致越界,则不予执行。但仅仅这样是不够的,如果总是不执行越界操作的话,那么图片拖动到边界会留下一个小区域拖动不过去(拖过去就越界了)。这样的用户体验并不友好。我们应该判断,如果本次操作会导致越界,那么我们只需要将图片移动到边界即可。
要判断是否越界,我们需要拿到当前图片四个角的坐标。正确的获取方式为
//获取图片四个坐标
RectF rectF = new RectF();
//传递bitmap本身的宽高
rectF.right = bitmapWidth;
rectF.bottom = bitmapHeight;
matrixteamp.mapRect(rectF);
既然我们拿到了四个角坐标那么就可以们判断图片是否越界了
//判断图片是否越界
if (maxleft > left && maxtop > top && right > maxright && bottom > maxbottom) {
//执行拖动操作
}
else
{
//修正拖动
transLateDragToRight();
}
//矫正拖动位置
private void transLateDragToRight(float left, float right, float bottom, float top, Matrix matrix, RectF currentRect) {
float realdx = dx;
float realdy = dy;
if (maxleft < left) {
realdx = maxleft-currentRect.left;
}
if (maxtop < top) {
realdy = maxtop - currentRect.top;
}
if (right < maxright) {
realdx = maxright-currentRect.right;
}
if (bottom < maxbottom) {
realdy = maxbottom - currentRect.bottom;
}
matrix.postTranslate(realdx, realdy);
}
放大也是同样的道理,需要进行修正。但注意放大与拖动存在不同,如果缩小后存在有一个边小于限制大小,那么位移修正是没有意义的。我们需要对其不操作,或者再次放大。
所以需要判断
if (right - left >= maxright - maxleft && bottom - top >= maxbottom - maxtop)
//矫正放大位置
//矫正放大位置
private void transLateToRight(float left, float right, float bottom, float top, Matrix matrix) {
float realdx = dx;
float realdy = dy;
if (maxleft < left) {
realdx = maxleft - left;
}
if (maxtop < top) {
realdy = maxtop - top;
}
if (right < maxright) {
realdx = -right + maxright;
}
if (bottom < maxbottom) {
realdy = maxbottom - bottom;
}
matrix.postTranslate(realdx, realdy);
}
这样处理过后即便图片紧靠边界,也能够进行缩小操作。直到缩小会导致有一个边小于限制。能够提供最佳的图片裁剪操作。
图片的裁剪
最后我们需要处理用户点击完成后的裁剪事件,裁剪事件我选择了获取当前跟view的视图,获取截图,并根据我的限制坐标对图片进行裁剪。
//我的根布局
relativeLayout.setDrawingCacheEnabled(true);
relativeLayout.buildDrawingCache();
Bitmap bitmap = relativeLayout.getDrawingCache();
// 传入left top right bottom 来创建新的图片。
Bitmap realfinBitamp = Bitmap.createBitmap(bitmap, (int) (picCultBackground.getMleft()), (int) (picCultBackground.getMtop()), (int) (picCultBackground.getMwidth()), (int) (picCultBackground.getMheight()));
bitmap.recycle();
relativeLayout.setDrawingCacheEnabled(false);
realfinBitamp 就是用户裁剪得到的图片。根据业务需求再对bitmap 进行处理吧。
到这里我们已经看完了裁剪功能的所有重要知识点和代码。但还要亲自尝试才能够明白各种细节的缘由。
细数图片上传功能用到的知识点(图片选取&拍照篇)
细数图片上传功能用到的知识点(裁剪篇)
细数图片上传功能用到的知识点(图片压缩篇)