使用过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(viewRect, outsideCapturePaint); // 绘制框体外围区域

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

- horStretchArrowsHalfHeigth, captureRect.left

+ horStretchArrowsHalfWidth, yMiddle

+ horStretchArrowsHalfHeigth);

horStretchArrows.draw(canvas);

// 框体右边的箭头

horStretchArrows.setBounds(captureRect.right

- horStretchArrowsHalfWidth, yMiddle

- horStretchArrowsHalfHeigth, captureRect.right

+ horStretchArrowsHalfWidth, yMiddle

+ horStretchArrowsHalfHeigth);

horStretchArrows.draw(canvas);

// 框体上方的箭头

verStretchArrows.setBounds(xMiddle - verStretchArrowsHalfWidth,

captureRect.top - verStretchArrowsHalfHeigth, xMiddle

+ verStretchArrowsHalfWidth, captureRect.top

+ verStretchArrowsHalfHeigth);

verStretchArrows.draw(canvas);

// 框体下方的箭头

verStretchArrows.setBounds(xMiddle - verStretchArrowsHalfWidth,

captureRect.bottom - verStretchArrowsHalfHeigth, xMiddle

+ verStretchArrowsHalfWidth, captureRect.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;

}

分析完毕!

【转】Android仿QQ截图应用测试的更多相关文章

  1. WPF C#截图功能 仿qq截图

    原文:WPF C#截图功能 仿qq截图 先上效果图 源码下载地址:http://download.csdn.net/detail/candyvoice/9788099 描述:启动程序,点击窗口butt ...

  2. Android仿QQ ios dialog,仿QQ退出向上菜单

    Android仿QQ ios dialog,仿QQ退出向上菜单 EasyDialog两种模式 仿QQ退出向上菜单,自己定义向上菜单              github地址:https://gith ...

  3. Android 仿QQ首页的消息和电话的切换,首页的头部(完全用布局控制)

    Android 仿QQ首页的消息和电话的切换,首页的头部(完全用布局控制) 首先贴上七个控制布局代码 1.title_text_sel.xml 字体颜色的切换 放到color文件夹下面 <?xm ...

  4. android 仿QQ手机版

    千人2群开启,欢迎大家围观打酱油,群号145667827     您当前位置 : JavaApk-安卓应用游戏源码服务专家 » QQ » Android项目源码界面超级华丽的仿QQ最新版本 Andro ...

  5. Android仿QQ登录下拉历史列表

    demo中包含了Sqlite数据库增删改查,对存储的账号进行按照最新的时间排序,限制了最多存储5条数据. 效果图: 1.首先创建MyHelper建表: public class MyHelper ex ...

  6. Android仿QQ窗口的抖动的动画效果

    就是仿照QQ窗口的抖动效果,在项目的res下创建anim文件夹,再创建两个xml文件:cycle.xml  . myanim.xml   cycle.xml  :   <?xml version ...

  7. android仿QQ的SlideMenu

    这其实很简单就可以实现,只需要自定义一个View继承自HorizontalScrollView 1,新建一个项目,再新建一个MySlideMenu继承HorizontalScrollView publ ...

  8. android仿qq空间、微信朋友圈图片展示

    废话不多说,先上效果图 由于近期须要做朋友圈功能,所以在此记录一下,事实上非常多人不明确的一点应该是在图片的排列上面吧,不规则的排列,事实上非常easy的.就是一个GridView.然而你xml光光写 ...

  9. Android仿qq聊天记录长按删除功能效果

    最近项目在做IM即时通讯开发,在删除聊天列表的时候跟删除聊天详细信息的时候,产品经理想要跟ios一样,在当前选中行上方弹出一个删除窗口.于是先从网上找demo,找了一个发现是Dialog做的,我感觉没 ...

随机推荐

  1. 对ARM9哈佛结构的认识

    书本上都说ARM是哈佛结构,但是我总感觉好像看不出来.后来针对S3C2440的ARM9核进行分析,我有了自己的见解. 我的结论是“ARM9被称为是哈佛结构是从它拥有指令cache和数据cache”来说 ...

  2. 国内外最全的asp.net开源项目 (转)

    最近一些项目开始用到CMS系统,最开始是研究JAVA的,无奈国内JAVA的CMS开源系统还是比较少,最多最成熟的还是PHP的,当然现在.NET的也不少了,这里做一下汇总备忘,留待学习研究. 国内系统: ...

  3. android开发环境重装系统之后的配置

    前提: 之前配置好的android开发环境:重装系统:压缩了android目录 配置: 安装java环境 安装; 建立JAVA_HOME变量:JAVAHOME添加到path变量 JAVA_HOME=C ...

  4. skiplist 跳表(2)-----细心学习

    快速了解skiplist请看:skiplist 跳表(1) http://blog.sina.com.cn/s/blog_693f08470101n2lv.html 本周我要介绍的数据结构,是我非常非 ...

  5. 关于Java(JDBC连接数据库)

    Processing SQL Statements with JDBC 处理JDBC中的SQL语句 这节主要是 JDBC 与数据库交互的基本步骤 JDBC的基石是DriverManager,通过它,J ...

  6. 第一次写Makefile文件

    test.c文件内容 #include <stdio.h> int main(int argc, char const *argv[]) { printf("hahahah\n& ...

  7. C++的类和对象

    #include <iostream> // 预处理命令 using namespace std; class Student{ // 声明一个类,类名为Student private : ...

  8. BZOJ 1711: [Usaco2007 Open]Dingin吃饭

    Description 农夫JOHN为牛们做了很好的食品,但是牛吃饭很挑食. 每一头牛只喜欢吃一些食品和饮料而别的一概不吃.虽然他不一定能把所有牛喂饱,他还是想让尽可能多的牛吃到他们喜欢的食品和饮料. ...

  9. 反射实体自动生成EasyUi DataGrid模板 第二版--附项目源码

    之前写过一篇文章,地址 http://www.cnblogs.com/Bond/p/3469798.html   大概说了下怎么通过反射来自动生成对应EasyUi datagrid的模板,然后贴了很多 ...

  10. 设计模式之装饰(Decorator)模式 代码详解

    /** * Created with IntelliJ IDEA. * User: HYY * Date: 13-10-27 * Time: 下午10:49 * To change this temp ...