细数图片上传功能用到的知识点(裁剪篇)

作者 信马归风
2017.06.06 02:08 字数 4248 阅读 293评论 3

综述

我们先来看最终效果 效果图

其中涉及到的知识点 * 蒙版的绘制 * 手势拖动,手势放大 * 图片的matrix操作 * 图片的裁剪 下面我就对这每个知识点进行详细的说明

蒙版的绘制

可以看到界面上覆盖了一层半透明的蒙版,中间扣出了一个正方形的区域,为了使边界更加明显我又绘制了一个白色的正方形的线框。 实现方式自定义View 重写ondraw方法绘图作为背景。利用PorterDuffXfermode 中的XOR模式扣掉中间的矩形区域 PorterDuffXfermode 各种模式

       //矩形区域的坐标可根据你的需求定制
       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 ,之后获取当actionMotionEvent.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 进行处理吧。
到这里我们已经看完了裁剪功能的所有重要知识点和代码。但还要亲自尝试才能够明白各种细节的缘由。

发表评论

说点什么吧!留下邮箱让我好回复你。 必填项已用*标注