zoukankan      html  css  js  c++  java
  • 【转】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;

        }

    分析完毕!

  • 相关阅读:
    一般 武胜
    Export/Import CSV files with MySQL 武胜
    关于Python中的for循环控制语句
    Linux下libxml2的使用
    Foxit PDF Reader能有效升级日文包
    Dependency introduction
    eclipse 插件更新站点
    TypeError: 'module' object is not callable 原因分析
    ubuntu11.10 安装reportlab出现“Python.h: 没有那个文件或目录”
    Windows 7 下如何调整网卡的优先级
  • 原文地址:https://www.cnblogs.com/exmyth/p/5125239.html
Copyright © 2011-2022 走看看