前言

最近有需求要做一个画布,这个画布以一个图片为背景,可以实现缩放,涂鸦以及贴纸的功能,缩放和涂鸦要兼顾,于是就想到了可以加入手势和多点触控,大致就是两只手指头可以拖动或者旋转或者放大,单只手指可以涂鸦画东西之类的,恩,具体的需求在这里先描述了,然后看下大致的实现。

效果展示

自定义view.gif

思路

思考一

通过继承ImageView,类似PhotoView 的实现,因为photoivew 已经实现了旋转和缩放的功能,在其基础上继承拓展,只需要复写onDraw方法,将触摸的轨迹转化为Path 直接draw到canvas上即可。可以实现的,但是要注意一点,那就是坐标转化:你的单个手指移动的轨迹坐标点们是相对于这个view的位置的,当你旋转或者缩放这个view 的时候,结果是先前保存的坐标轨迹是无法匹配到当前旋转或缩放处理后的view,这个时候就需要你将坐标轨迹进行映射处理。

思 考二

则是直接复写View控件,通过将图片直接转换为bitmap后,draw到view 的画布上。整个过程就是先在bitmap上新建一个画布,然后将轨迹坐标draw到这个bitmap的canvas上,也就是这个bitmap上,最后在onDraw的回调里面,将这个bitmap 画到整个View 的canvas上,当然,最后要自行实现bitmap的缩放,旋转等坐标转换功能,好处是先前的涂鸦会一直保持。

预先准备

这个时候就必须要提一下Martix,Andorid 贴心的给我们提供了这样一个工具类,我们完全可以摆脱坐标点计算之苦啦。

在这里强烈推荐大家看下 android matrix 最全方法详解与进阶(完整篇),原理以及api介绍的相当详细。

实现

考虑到要有贴图,并且贴图支持大小缩放的功能,拖动功能,采用了第二种方式,其实感觉采用第一种方式应该会更简单点(微笑脸),好了,下面介绍下具体实现

首先要处理这个view 的touch事件:

if (actionMode == ACTION_DRAG) {

onDragAction(curX - preX, curY - preY, event);//拖动监听

} else if (actionMode == ACTION_ROTATE) {

onRotateAction(curPhotoRecord);//旋转监听

} else if (actionMode == ACTION_SCALE) {

mScaleGestureDetector.onTouchEvent(event); //缩放监听

}

涂鸦

就是将拇指略过之处的所以坐标连接起来,而这个坐标id呢,不是绝对坐标,而是对于这个view 的相对坐标(毕竟还要支持缩放和撤销操作的),单是缩放则不用过多约束,只要将path画到bitmap Canvas上,显示出来即可,但是需要支持撤销,这就要求必须要保持每一个笔画的坐标点组啦,缩放或者旋转时,相对于这个view 的坐标肯定会发生变化,大致给下代码:

//缩放处理描点位置

private void convertDrawedPoiontsPosition(float scaleX, float scaleY, float x, float y) {

curTextSize = curTextSize * scaleX;

textPaint.setTextSize(curTextSize);

Matrix pointsMatrix = new Matrix();

pointsMatrix.postScale(scaleX,scaleY,x,y); //scaleX 为 x方向缩放参数,scaleY为y轴缩放参数,(x,y)为缩放中心点坐标

for( Object object :curSketchData.drawPathList){//drawPathList为存放坐标的数组

if(object instanceof SketchData.Angle){

SketchData.Angle angle = (SketchData.Angle)object;

float[] photoCornersSrc = new float[6];

float[] photoCorners = new float[6];

photoCornersSrc[0] = angle.start.x;

photoCornersSrc[1] = angle.start.y;

photoCornersSrc[2] = angle.middle.x;

photoCornersSrc[3] = angle.middle.y;

photoCornersSrc[4] = angle.end.x;

photoCornersSrc[5] = angle.end.y;

//angle.matrix.mapPoints(photoCorners, photoCornersSrc);

pointsMatrix.mapPoints(photoCorners, photoCornersSrc);

angle.start.x = photoCorners[0];

angle.start.y = photoCorners[1];

angle.middle.x = photoCorners[2];

angle.middle.y = photoCorners[3];

angle.end.x = photoCorners[4];

angle.end.y = photoCorners[5];

}else if(object instanceof SketchData.Length){

SketchData.Length length = (SketchData.Length)object;

float[] photoCornersSrc = new float[4];

float[] photoCorners = new float[4];

photoCornersSrc[0] = length.start.x;

photoCornersSrc[1] = length.start.y;

photoCornersSrc[2] = length.end.x;

photoCornersSrc[3] = length.end.y;

//angle.matrix.mapPoints(photoCorners, photoCornersSrc);

pointsMatrix.mapPoints(photoCorners, photoCornersSrc);

length.start.x = photoCorners[0];

length.start.y = photoCorners[1];

length.end.x = photoCorners[2];

length.end.y = photoCorners[3];

}

}

drawDrawedPosition();

}

缩放

那么如何得到缩放的中心点呢?实现ScaleGestureDetector 实例,调用onTouchEvent,此时会回调onScale(ScaleGestureDetector detector),我们来看下使用这个detector 的具体逻辑

private void onScaleAction(ScaleGestureDetector detector) {

Log.e("shang", "onscale :" + detector.getScaleFactor());

float[] photoCorners = calculateBgCorners(backgroundSrcRect);//获取现阶段底图的标志坐标点

//目前图片对角线长度

float len = (float) Math.sqrt(Math.pow(photoCorners[0] - photoCorners[4], 2) + Math.pow(photoCorners[1] - photoCorners[5], 2));

double photoLen = Math.sqrt(Math.pow(backgroundSrcRect.width(), 2) + Math.pow(backgroundSrcRect.height(), 2));

float scaleFactor = detector.getScaleFactor();

//设置Matrix缩放参数

if ((scaleFactor < 1 && len >= photoLen * SCALE_MIN && len >= SCALE_MIN_LEN) || (scaleFactor > 1 && len <= photoLen * SCALE_MAX)) {

Log.e(scaleFactor + "", scaleFactor + "");

convertDrawedPoiontsPosition(scaleFactor, scaleFactor, photoCorners[8], photoCorners[9]);//涂鸦点坐标转换

currentDrawedBgM.postScale(scaleFactor, scaleFactor, photoCorners[8], photoCorners[9]);//底图矩阵缩放

apply2DrawedCanvas();

mScaleValue = scaleFactor * mScaleValue;

Log.e("shang", "scale :" + mScaleValue);

drawDrawedPosition();

}

}

其中

private float[] calculateBgCorners(RectF rectF) {

float[] photoCornersSrc = new float[10];//0,1代表左上角点XY,2,3代表右上角点XY,4,5代表右下角点XY,6,7代表左下角点XY,8,9代表中心点XY

float[] photoCorners = new float[10];//0,1代表左上角点XY,2,3代表右上角点XY,4,5代表右下角点XY,6,7代表左下角点XY,8,9代表中心点XY

photoCornersSrc[0] = rectF.left;

photoCornersSrc[1] = rectF.top;

photoCornersSrc[2] = rectF.right;

photoCornersSrc[3] = rectF.top;

photoCornersSrc[4] = rectF.right;

photoCornersSrc[5] = rectF.bottom;

photoCornersSrc[6] = rectF.left;

photoCornersSrc[7] = rectF.bottom;

photoCornersSrc[8] = rectF.centerX();

photoCornersSrc[9] = rectF.centerY();

currentDrawedBgM.mapPoints(photoCorners, photoCornersSrc);//现阶段的底图的矩阵

return photoCorners;

}

其中

private void apply2DrawedCanvas() {

Matrix matrix = new Matrix();

currentDrawedBgM.invert(matrix);

mBGCanvas.setMatrix(matrix);//mBGCanvas为底图bitmap所在的canvas

}

拖动

拖动和缩放类似,都是对当前涂鸦坐标做转换,另对底图矩阵做变换

private void onDragAction(float distanceX, float distanceY, MotionEvent event) {

//底图变化

currentDrawedBgM.postTranslate((int) distanceX, (int) distanceY);

apply2DrawedCanvas();

//涂鸦坐标转换

convertDrawedPointPosition(distanceX,distanceY);

drawDrawedPosition();

}

旋转

private void onRotateAction(PhotoRecord record) {

float[] corners = calculateCorners(record);

//放大

//目前触摸点与图片显示中心距离

float a = (float) Math.sqrt(Math.pow(curX - corners[8], 2) + Math.pow(curY - corners[9], 2));

//目前上次旋转图标与图片显示中心距离

float b = (float) Math.sqrt(Math.pow(corners[4] - corners[0], 2) + Math.pow(corners[5] - corners[1], 2)) / 2;

//旋转

//根据移动坐标的变化构建两个向量,以便计算两个向量角度.

PointF preVector = new PointF();

PointF curVector = new PointF();

preVector.set(preX - corners[8], preY - corners[9]);//旋转后向量

curVector.set(curX - corners[8], curY - corners[9]);//旋转前向量

//计算向量长度

double preVectorLen = getVectorLength(preVector);

double curVectorLen = getVectorLength(curVector);

//计算两个向量的夹角.

double cosAlpha = (preVector.x * curVector.x + preVector.y * curVector.y)

/ (preVectorLen * curVectorLen);

//由于计算误差,可能会带来略大于1的cos,例如

if (cosAlpha > 1.0f) {

cosAlpha = 1.0f;

}

//本次的角度已经计算出来。

double dAngle = Math.acos(cosAlpha) * 180.0 / Math.PI;

// 判断顺时针和逆时针.

//判断方法其实很简单,这里的v1v2其实相差角度很小的。

//先转换成单位向量

preVector.x /= preVectorLen;

preVector.y /= preVectorLen;

curVector.x /= curVectorLen;

curVector.y /= curVectorLen;

//作curVector的逆时针垂直向量。

PointF verticalVec = new PointF(curVector.y, -curVector.x);

//判断这个垂直向量和v1的点积,点积>0表示俩向量夹角锐角。=0表示垂直,<0表示钝角

float vDot = preVector.x * verticalVec.x + preVector.y * verticalVec.y;

if (vDot > 0) {

//v2的逆时针垂直向量和v1是锐角关系,说明v1在v2的逆时针方向。

} else {

dAngle = -dAngle;

}

currentDrawedBgM.postRotate((float) dAngle, corners[8], corners[9]);

}

撤销

撤销就是你首先保存了涂鸦的坐标组,和原始的底图,将坐标组坐标减一,重新画到原始底图上。

恢复类似,代码我就不贴出来了。

mBGCanvas.drawBitmap(curSketchData.backgroundBMOrigin, currentDrawedBgM, null);

mBGCanvas.drawPath(mPath);

贴纸

其实贴纸的逻辑,和增加第一个底图的逻辑是一直的,只不过要加一个flag来标志操作的是贴纸 还是 底图。这里推荐大家看下这篇文章Android贴纸。

总结

在做图片处理时,首要理解坐标的转换,矩阵有着非常重要的地位,理解好android提供的Martix,很多类似的问题都会事倍功半。

参考链接

Android画布放大缩小,android画板---涂鸦,缩放,旋转,贴纸实现相关推荐

  1. android 图片放大缩小,android中图片放大缩小...

    ClickListener(){ public void onClick(View v){ big(); } }); } private void small(){ //获得Bitmap的高和宽 in ...

  2. Android 图片放大缩小

    2019独角兽企业重金招聘Python工程师标准>>> 在android中,图片的放大和缩小是很重要的一个方法,我们主要用到的就是bitmap.MotionEvent .Matrix ...

  3. android 图片放大缩小_贴在手机上的显微镜,轻松放大400倍,化身“蚁人”玩转微观世界...

    记得在漫威电影<蚁人>里,男猪脚只要穿上那套黑科技制服,按下开关,"咻"的一声就缩到蚂蚁大小,并操控蚂蚁.昆虫和自己并肩作战!!简直不要太酷! 蚁人"进入微观 ...

  4. android 图片放大缩小 多点触摸,Android 多点触摸(图片放大缩小)

    就不说什么了,有注释 基本有基础的都看得懂 欢迎加入我的交流群: 386451316 main.xml文件 xmlns:tools="http://schemas.android.com/t ...

  5. android 画布实现签名,Android 自定义View手写签名并保存图片

    1.自定义View--支撑设置画笔色彩,画笔宽度,画板色彩,铲除画板,查看是否有签名,保存画板图片(仿制粘贴可直接使用) /***CreatedbyYyyyQon2020/3/5. *电子签名*/pu ...

  6. android 画布实现签名,Android实现屏幕手写签名

    Android屏幕手写签名的原理就是把手机屏幕当作画板,把用户手指当作画笔,手指在屏幕上在屏幕上划来划去,屏幕就会显示手指的移动轨迹,就像画笔在画板上写字一样.实现手写签名需要结合绘图的路径工具Pat ...

  7. android画布大小设置,Android:定义onDraw画布大小

    我已经删除了旧的代码,因为它没有按照我的意图工作.我试图在屏幕的某个部分使用onDraw画布功能,这样我的绘图周围仍然可以有按钮和其他文字.Android:定义onDraw画布大小 ***编辑* ** ...

  8. android 画布心形,Android CustomShapeImageView对图片进行各种样式裁剪:圆形、星形、心形、花瓣形等...

     Android CustomShapeImageView对图片进行各种样式裁剪:圆形.星形.心形.花瓣形等 Android CustomShapeImageView是github上一个第三方开源 ...

  9. android 图片放大缩小_几款堪称神器的图片无损放大缩小工具!

    Bigjpg Bigjpg是一款非常强大的能放大图片并使其不失真的工具. 一般来说,图片在经过PS或PhotoZoom放大之后,都会出现一定程度的损害和模糊,而Bigjpg可以让放大的图片无损. Bi ...

最新文章

  1. 如何利用离散Hopfield神经网络进行高校科研能力评价(2)
  2. 团队任务3:第一次冲刺
  3. 道路里程桩号标注_2000公里长距离电车远行,逸动EV460对里程焦虑说不
  4. 控制台输出HelloWorld案例
  5. 爱奇艺深夜就“倒奶视频”致歉:《青你3》成团夜停止录制和直播......
  6. java类的加载顺序题目_Java 类的加载顺序(题)
  7. Spring身份验证+CXF拦截器+RESTful
  8. 2-Twelfth Scrum Meeting20151212
  9. 易基因|一文读懂:八大RNA m6A甲基化研究核心问题
  10. 网络复现笔记-AdaptSegNet
  11. c#的chart标题_C#之Chart篇
  12. Flowable流程设计器的使用
  13. 配置香橙派zeropuls2
  14. Luogu P4147 玉蟾宫
  15. 用python处理excel 数据分析_数据分析---用python处理excel
  16. 学计算机买宏基好吗,想买轻薄笔记本,宏碁蜂鸟是不错的选择
  17. Jenkins实现微服务自动化部署_Jenkins环境设置完整版 (二)
  18. win10麦克风说话没声音_电脑录屏,真的没那么麻烦
  19. zcu111 with pynq环境下安装tensorflow
  20. 螺旋测微器b类不确定度_物理实验直测量不确定度评估.ppt

热门文章

  1. 基于uniapp的个人课程表
  2. MODBUS RTU协议
  3. Windows卸载easyconnect
  4. UVM:filed_automation
  5. 中国金盐银盐行业研究与投资前景预测报告(2022版)
  6. vscode 自定义字体样式_vscode md样式自定义
  7. css 属性 linear-gradient 渐变色
  8. XTOOL EZ500全系统的诊断和特殊功能超越Xtool EZ400 EZ300 Xtool PAD
  9. 来TDengine 开发者大会,探索数据架构的迭代升级
  10. Brooklyn 2.1.0 (60+超炫苹果标志动画屏保)