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

综述

图片选取&拍照篇链接 细数图片上传功能用到的知识点-图片选取&拍照篇

我们先来看最终效果


效果图

其中涉及到的知识点

  • 蒙版的绘制
  • 手势拖动,手势放大
  • 图片的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 进行处理吧。
到这里我们已经看完了裁剪功能的所有重要知识点和代码。但还要亲自尝试才能够明白各种细节的缘由。

细数图片上传功能用到的知识点(图片选取&拍照篇)
细数图片上传功能用到的知识点(裁剪篇)
细数图片上传功能用到的知识点(图片压缩篇)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容