【转】Android仿QQ截图应用测试

使用过QQ的同学应该都用过QQ截图,Ctrl+Alt+A进入截图操作,通过拉伸,移动高亮区域的框体可以快速截取我们需要的图片。在android应用中,我们也经常需要截图操作,以下实现了一个类似QQ截图的应用。先贴图看看效果: 

图片

图片

图片

图片

图片

实现原理:

自定义CaptureView,在CaptureView上绘制具有一个可拉伸,移动的高亮矩形框,通过FrameLayout布局将这个CaptureView覆盖到需要截图的图片显示控件ImageView上,当点击截图按钮后,计算CaptureView矩形框的坐标值及宽和高读取图片相映区域的像素,并将这些像素通过画布重新绘制成图片。

 

首先先上布局文件:main.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent"

    android:background="#FFFFFFFF"

    android:orientation="vertical" >

 

    <FrameLayout

        android:layout_width="fill_parent"

        android:layout_height="fill_parent"

        android:layout_weight="1" >

 

        <!-- 显示图片 -->

 

        <ImageView

            android:id="@+id/iv_image"

            android:layout_width="fill_parent"

            android:layout_height="fill_parent"

            android:scaleType="fitXY" />

 

        <!-- 自定义的截图View -->

 

        <gwn.test.capture.CaptureView

            android:id="@+id/capture"

            android:layout_width="fill_parent"

            android:layout_height="fill_parent" />

 

        <!-- 截图显示 -->

 

        <ImageView

            android:id="@+id/iv_corp"

            android:layout_width="100dip"

            android:layout_height="100dip"

            android:layout_gravity="right"

            android:background="#50000000"

            android:scaleType="centerInside" />

    </FrameLayout>

 

    <Button

        android:id="@+id/btn_crop"

        android:layout_width="fill_parent"

        android:layout_height="wrap_content"

        android:text="截图" />

 

</LinearLayout>

布局文件很简单,接下来主角就要登场了,当然这就是CaptureViewr的实现。CaptureView需要绘制的部分有三个,分别为整个View可视范围viewRect,矩形框体captureRect,拉伸时的显示箭头。CaptureView的触摸事件类型有三种:无操作、移动、拉伸,代码定义如下:

private enum ActionMode { // 枚举动作类型:无、移动、拉伸

       NoneMoveGrow

    }

 

首先先计算viewRect,captureView的大小,我们在系统给View指派大小的地方初始化这两个区域,即在onLayout()方法中实现。代码如下:

protected void onLayout(boolean changed, int left, int top, int right,

           int bottom) {

       super.onLayout(changed, left, top, right, bottom);

       // 初始化可视范围及框体大小

       viewRect = new Rect(left, top, right, bottom);

       int viewWidth = right - left;

       int viewHeight = bottom - top;

       int captureWidth = Math.min(viewWidth, viewHeight) * 3 / 5;

       int captureHeight = viewHeight * 2 / 5;

       // 将框体绘制在可视范围中间位置

       int captureX = (viewWidth - captureWidth) / 2;

       int captureY = (viewHeight - captureHeight) / 2;

       captureRect = new Rect(captureX, captureY, captureX + captureWidth,

              captureY + captureHeight);

    }

 

接下来重写ondraw(Canvas canvas),将可视范围、框体区域,箭头绘制上去:

protected void onDraw(Canvas canvas) {

       // TODO Auto-generated method stub

       super.onDraw(canvas);

       canvas.save();

       Path path = new Path();

       path.addRect(new RectF(captureRect), Path.Direction.CW);// 顺时针闭合框体

       canvas.clipPath(path, Region.Op.DIFFERENCE);

       canvas.drawRect(viewRectoutsideCapturePaint); // 绘制框体外围区域

 

       canvas.drawPath(path, lineCapturePaint); // 绘制框体

       canvas.restore();

       if (mMode == ActionMode.Grow) { // 拉伸操作时,绘制框体箭头

 

           int xMiddle = captureRect.left + captureRect.width() / 2; // 框体中间X坐标

           int yMiddle = captureRect.top + captureRect.height() / 2; // 框体中间Y坐标

 

           // 框体左边的箭头

           horStretchArrows.setBounds(captureRect.left

                  - horStretchArrowsHalfWidth, yMiddle

                  - horStretchArrowsHalfHeigthcaptureRect.left

                  + horStretchArrowsHalfWidth, yMiddle

                  + horStretchArrowsHalfHeigth);

           horStretchArrows.draw(canvas);

 

           // 框体右边的箭头

           horStretchArrows.setBounds(captureRect.right

                  - horStretchArrowsHalfWidth, yMiddle

                  - horStretchArrowsHalfHeigthcaptureRect.right

                  + horStretchArrowsHalfWidth, yMiddle

                  + horStretchArrowsHalfHeigth);

           horStretchArrows.draw(canvas);

 

           // 框体上方的箭头

           verStretchArrows.setBounds(xMiddle - verStretchArrowsHalfWidth,

                  captureRect.top - verStretchArrowsHalfHeigth, xMiddle

                         + verStretchArrowsHalfWidthcaptureRect.top

                         + verStretchArrowsHalfHeigth);

           verStretchArrows.draw(canvas);

 

           // 框体下方的箭头

           verStretchArrows.setBounds(xMiddle - verStretchArrowsHalfWidth,

                  captureRect.bottom - verStretchArrowsHalfHeigth, xMiddle

                         + verStretchArrowsHalfWidthcaptureRect.bottom

                         + verStretchArrowsHalfHeigth);

           verStretchArrows.draw(canvas);

       }

    }

 

重头戏来了,CaptureView的事件监听。首先定义触摸位置及动作,代码:

// 触摸位置及动作

    public static final int GROW_NONE = (1 << 0);//框体外部

    public static final int GROW_LEFT_EDGE = (1 << 1);//框体左边缘

    public static final int GROW_RIGHT_EDGE = (1 << 2);//框体右边缘

    public static final int GROW_TOP_EDGE = (1 << 3);//框体上边缘

    public static final int GROW_BOTTOM_EDGE = (1 << 4);//框体下边缘

    public static final int GROW_MOVE = (1 << 5);//框体移动

 

// 确定触摸位置及动作,分别为触摸框体外围和框体上、下、左、右边缘以及框体内部。

    private int getGrow(float x, float y) {

       final float effectiveRange = 20F; // 触摸的有效范围大小

       int grow = GROW_NONE;

       int left = captureRect.left;

       int top = captureRect.top;

       int right = captureRect.right;

       int bottom = captureRect.bottom;

       boolean verticalCheck = (y >= top - effectiveRange)

              && (y < bottom + effectiveRange);

       boolean horizCheck = (x >= left - effectiveRange)

              && (x < right + effectiveRange);

 

       // 触摸了框体左边缘

       if ((Math.abs(left - x) < effectiveRange) && verticalCheck) {

           grow |= GROW_LEFT_EDGE;

       }

 

       // 触摸了框体右边缘

       if ((Math.abs(right - x) < effectiveRange) && verticalCheck) {

           grow |= GROW_RIGHT_EDGE;

       }

 

       // 触摸了框体上边缘

       if ((Math.abs(top - y) < effectiveRange) && horizCheck) {

           grow |= GROW_TOP_EDGE;

       }

 

       // 触摸了框体下边缘

       if ((Math.abs(bottom - y) < effectiveRange) && horizCheck) {

           grow |= GROW_BOTTOM_EDGE;

       }

 

       // 触摸框体内部

       if (grow == GROW_NONE && captureRect.contains((int) x, (int) y)) {

           grow = GROW_MOVE;

       }

       return grow;

    }

 

如果grow的值不为GROW_NONE,也即用户触摸位置在框体边缘或框体内部,那么就锁定用户本次触摸,直到用户放开触摸释放。判断用户的移动事件是伸缩框体还是移动框体,如果是伸缩框体,则调用growBy()方法拉伸框体,否则调用moveBy()移动框体代码如下:

private void handleMotion(int grow, float dx, float dy) {

       if (grow == GROW_NONE) {

           return;

       } else if (grow == GROW_MOVE) {

           moveBy(dx, dy); // 移动框体

       } else {

           if (((GROW_LEFT_EDGE | GROW_RIGHT_EDGE) & grow) == 0) {

              dx = 0; // 水平不伸缩

           }

 

           if (((GROW_TOP_EDGE | GROW_BOTTOM_EDGE) & grow) == 0) {

                dy = 0; // 垂直不伸缩

           }

           growBy((((grow & GROW_LEFT_EDGE) != 0) ? -1 : 1) * dx,

                  (((grow & GROW_TOP_EDGE) != 0) ? -1 : 1) * dy);

       }

    }

 

下面是贴上这两个方法,有关说明见注释

private void moveBy(float dx, float dy) {

       Rect invalRect = new Rect(captureRect);

       captureRect.offset((int) dx, (int) dy);

       captureRect.offset(Math.max(0, viewRect.left - captureRect.left),

              Math.max(0, viewRect.top - captureRect.top));

       captureRect.offset(Math.min(0, viewRect.right - captureRect.right),

              Math.min(0, viewRect.bottom - captureRect.bottom));

 

       //清除移动滞留的痕迹

       invalRect.union(captureRect);//更新围绕本身区域和指定的区域,

       invalRect.inset(-100, -100);

       invalidate(invalRect); // 重绘指定区域

    }

 

    private void growBy(float dx, float dy) {

       float widthCap = 50F;       //captureRect最小宽度

       float heightCap = 50F;      //captureRect最小高度

      

       RectF r = new RectF(captureRect);

      

       //当captureRect拉伸到宽度 = viewRect的宽度时,则调整dx的值为 0

       if (dx > 0F && r.width() + 2 * dx >= viewRect.width()) {

           dx = 0F;

       }

       //同上

       if (dy > 0F && r.height() + 2 * dy >= viewRect.height()) {

           dy = 0F;

       }

 

       r.inset(-dx, -dy); // 框体边缘外移

 

      

       //当captureRect缩小到宽度 = widthCap时

       if (r.width() <= widthCap) {

           r.inset(-(widthCap - r.width()) / 2F, 0F);

       }

 

       //同上

       if (r.height() <= heightCap) {

           r.inset(0F, -(heightCap - r.height()) / 2F);

       }

 

       if (r.left < viewRect.left) {

           r.offset(viewRect.left - r.left, 0F);

       } else if (r.right > viewRect.right) {

           r.offset(-(r.right - viewRect.right), 0);

       }

       if (r.top < viewRect.top) {

           r.offset(0F, viewRect.top - r.top);

       } else if (r.bottom > viewRect.bottom) {

           r.offset(0F, -(r.bottom - viewRect.bottom));

       }

 

       captureRect.set((int) r.left, (int) r.top, (int) r.right,

              (int) r.bottom);

       invalidate();

    }

 

接下来看下截图操作,由于ImageView显示的图片跟原始图片有比例上的区别,因此,先取得调整比例的图片,代码说明:

// ImageView中的图像是跟实际的图片有比例缩放,因此需要调整图片比例

    private Bitmap regulationBitmap(Bitmap bitmap) {

       int ivWidth = ivImage.getWidth();

       int ivHeight = ivImage.getHeight();

 

       int bmpWidth = bitmap.getWidth();

       int bmpHeight = bitmap.getHeight();

 

       // 宽和高的比例

       float scaleWidth = (float) ivWidth / bmpWidth;

       float scaleHeight = (float) ivHeight / bmpHeight;

       Matrix matrix = new Matrix();

       matrix.postScale(scaleWidth, scaleHeight);

       Bitmap resizeBmp = Bitmap.createBitmap(bitmap, 0, 0, bmpWidth,

              bmpHeight, matrix, true);

 

       return resizeBmp;

    }

 

截图代码:

private Bitmap cropImage(){

       Rect cropRect = mCaptureView.getCaptureRect();

       int width = cropRect.width();

       int height = cropRect.height();

 

       Bitmap croppedImage = Bitmap.createBitmap(width,

              height, Bitmap.Config.ARGB_8888);

       Canvas canvas = new Canvas(croppedImage);

       Rect dstRect = new Rect(0, 0, width, height);

 

       // 调整图片显示比例

       mBitmap = regulationBitmap(mBitmap);

 

       canvas.drawBitmap(mBitmap, cropRect, dstRect, null);

       return croppedImage;

    }

分析完毕!

原文地址:https://www.cnblogs.com/exmyth/p/5125239.html