android图片涂鸦,具有设置画笔,撤销,缩放移动等功能(二)
2016-10-14 17:38
441 查看
上一篇文章《android图片涂鸦,具有设置画笔,撤销,缩放移动等功能(一)》,讲了涂鸦的主要功能,还没看过的同学建议去看一遍,方便接下来的分析。现在,为大家讲解涂鸦功能的实现原理。
放缩与移动
当我们进入涂鸦界面后,发现图片刚好适应屏幕居中,这是因为进行了处理,下面三个变量记录此时图片的位置信息,当确定这三个值后,不会再改变,除非显示区域大小发生变化。
之后我们对图片进行的缩放与移动操作,都是相对于这个状态下的图片,位置信息记录下面两个变量中。
会发现,当缩放移动图片时,相应的涂鸦操作也会被缩放移动,这里是通过缩放移动GraffitiVIew的画布,来实现整体的变化,而不用分别单独对图片和涂鸦进行变化操作。
(不熟悉矩阵变换的同学可以查看我的另一篇文章《浅谈矩阵变换——Matrix》)
经过多次优化后,这里选择画布和图片共用一个坐标系,了解图片的位置信息后,最后需要处理的就是,屏幕坐标系与图片(画布)坐标系的映射,即把屏幕上滑动的轨迹投射到图片中。当手指在屏幕上滑动做绘图操作,此时看到的绘图轨迹其实是画在GraffitiView的画布上的,所以能看到画笔的实时变化,而图片上暂未被改动,即还未真正在图片上涂鸦。当结束此次绘图(松开手指)时,才把真实的绘图操作画在图片上。
从上图的分析中,我们可以得出如下映射关系:
图片坐标x=(屏幕坐标x-图片在x轴上的偏移量)/图片缩放倍数
图片坐标y=(屏幕坐标y-图片在y轴上的偏移量)/图片缩放倍数
可见,屏幕坐标投射到图片上时,需要减去偏移量,因为图片的位置是一直不变的,我们对图片进行偏移,其实是对GraffitView的画布进行偏移。
设置画笔及形状
通过Paint类充当画笔,根据不同选择来设置画笔属性,当画笔的底色为颜色值时,通过Paint.setColor(int)设置颜色,当画笔的底色为一张图片时,则通过Paint.setShader(BitmapShader)把图片设置为画笔底色。代码中我定义了一个类GraffitiColor表示画笔底色,定义如下:
同理,当画笔为仿制(COPY)和橡皮擦(ERASER)时,通过Paint.setShader(BitmapShader)直接把画笔底色设置为当前涂鸦的图片,即可实现擦除的效果,只不过当为仿制时,需要对图片进行偏移处理,相关信息记录在CopyLocation类中:
每当偏移位置发生改变时,重新设置需要仿制的图片的偏移位置,可通过下面的方法设置:
由上图的分析,绿色虚线为仿制图片的偏移量,我们可以得出图片X轴上偏移的位置(图中的黑色虚线)为
CopyLocation.mTouchStartX - CopyLocation.mCopyStartX ,
Y轴上偏移的位置(图中的黑色虚线长度)为
CopyLocation.mTouchStartY - CopyLocation.mCopyStartY 。
每一次的涂鸦操作(从手指点击到抬起)都会记录在GraffitiPath类中:
撤销及清屏
上面说到了GraffitiPath的作用,因此我们可以通过该类还原出每一次的涂鸦操作,我们把操作记录在集合CopyOnWriteArrayList<GraffitiPath>中,通过删除集合的最后一个元素即可实现撤销上一步操作的功能,同理,清除集合内的元素即可实现清屏,当然,删除后需要在原始图片上按次序还原集合中所有操作。
最新源码在Github上的地址为:https://github.com/1993hzw/Graffiti ,
欢迎大家反馈问题,我会及时在上面更新代码,谢谢支持。
建议大家先阅读v3.0的代码,因为3.0之后加入了旋转等功能,原理变得更为复杂。v3.0的代码:
https://github.com/1993hzw/Graffiti/tree/v3.0
放缩与移动
当我们进入涂鸦界面后,发现图片刚好适应屏幕居中,这是因为进行了处理,下面三个变量记录此时图片的位置信息,当确定这三个值后,不会再改变,除非显示区域大小发生变化。
private float mPrivateScale; // 图片适应屏幕时的缩放倍数 private int mPrivateHeight, mPrivateWidth;// 图片适应屏幕时的大小(View窗口坐标系上的大小) private float mCentreTranX, mCentreTranY;// 图片在适应屏幕时,位于居中位置的偏移(View窗口坐标系上的偏移)
之后我们对图片进行的缩放与移动操作,都是相对于这个状态下的图片,位置信息记录下面两个变量中。
private float mScale = 1; // 在适应屏幕时的缩放基础上的缩放倍数 ( 图片真实的缩放倍数为 mPrivateScale*mScale ) private float mTransX = 0, mTransY = 0; // 图片在适应屏幕且处于居中位置的基础上的偏移量( 图片真实偏移量为mCentreTranX + mTransX,View窗口坐标系上的偏移)
会发现,当缩放移动图片时,相应的涂鸦操作也会被缩放移动,这里是通过缩放移动GraffitiVIew的画布,来实现整体的变化,而不用分别单独对图片和涂鸦进行变化操作。
private void doDraw(Canvas canvas) { float left = mCentreTranX + mTransX; float top = mCentreTranY + mTransY; // 画布和图片共用一个坐标系,只需要处理屏幕坐标系到图片(画布)坐标系的映射关系 canvas.translate(left, top); // 偏移画布 canvas.scale(mPrivateScale * mScale, mPrivateScale * mScale); // 缩放画布 ... }
(不熟悉矩阵变换的同学可以查看我的另一篇文章《浅谈矩阵变换——Matrix》)
经过多次优化后,这里选择画布和图片共用一个坐标系,了解图片的位置信息后,最后需要处理的就是,屏幕坐标系与图片(画布)坐标系的映射,即把屏幕上滑动的轨迹投射到图片中。当手指在屏幕上滑动做绘图操作,此时看到的绘图轨迹其实是画在GraffitiView的画布上的,所以能看到画笔的实时变化,而图片上暂未被改动,即还未真正在图片上涂鸦。当结束此次绘图(松开手指)时,才把真实的绘图操作画在图片上。
从上图的分析中,我们可以得出如下映射关系:
图片坐标x=(屏幕坐标x-图片在x轴上的偏移量)/图片缩放倍数
图片坐标y=(屏幕坐标y-图片在y轴上的偏移量)/图片缩放倍数
/** * 将屏幕触摸坐标x转换成在图片中的坐标 */ public final float toX(float touchX) { return (touchX - mCentreTranX - mTransX) / (mPrivateScale * mScale); } /** * 将屏幕触摸坐标y转换成在图片中的坐标 */ public final float toY(float touchY) { return (touchY - mCentreTranY - mTransY) / (mPrivateScale * mScale); }
可见,屏幕坐标投射到图片上时,需要减去偏移量,因为图片的位置是一直不变的,我们对图片进行偏移,其实是对GraffitView的画布进行偏移。
设置画笔及形状
通过Paint类充当画笔,根据不同选择来设置画笔属性,当画笔的底色为颜色值时,通过Paint.setColor(int)设置颜色,当画笔的底色为一张图片时,则通过Paint.setShader(BitmapShader)把图片设置为画笔底色。代码中我定义了一个类GraffitiColor表示画笔底色,定义如下:
public static class GraffitiColor { // 底色类型 public enum Type { COLOR, // 颜色值 BITMAP // 图片 } private int mColor; // 颜色值 private Bitmap mBitmap; // 图片 private Type mType; private Shader.TileMode mTileX = Shader.TileMode.MIRROR; private Shader.TileMode mTileY = Shader.TileMode.MIRROR; // 镜像 }
同理,当画笔为仿制(COPY)和橡皮擦(ERASER)时,通过Paint.setShader(BitmapShader)直接把画笔底色设置为当前涂鸦的图片,即可实现擦除的效果,只不过当为仿制时,需要对图片进行偏移处理,相关信息记录在CopyLocation类中:
private class CopyLocation { private float mCopyStartX, mCopyStartY; // 仿制的坐标 private float mTouchStartX, mTouchStartY; // 开始触摸的坐标 private float mX, mY; // 当前位置 private Paint mPaint; private boolean isRelocating = true; // 正在定位中 private boolean isCopying = false; // 正在仿制绘图中 }
每当偏移位置发生改变时,重新设置需要仿制的图片的偏移位置,可通过下面的方法设置:
ShaderMatrix.postTranslate(float, float); BitmapShader.setLocalMatrix(ShaderMatrix);
由上图的分析,绿色虚线为仿制图片的偏移量,我们可以得出图片X轴上偏移的位置(图中的黑色虚线)为
CopyLocation.mTouchStartX - CopyLocation.mCopyStartX ,
Y轴上偏移的位置(图中的黑色虚线长度)为
CopyLocation.mTouchStartY - CopyLocation.mCopyStartY 。
每一次的涂鸦操作(从手指点击到抬起)都会记录在GraffitiPath类中:
private static class GraffitiPath { Pen mPen; // 画笔类型 Shape mShape; // 画笔形状 float mStrokeWidth; // 大小 GraffitiColor mColor; // 颜色 Path mPath; // 画笔的路径 float mSx, mSy; // 映射后的起始坐标,(手指点击) float mDx, mDy; // 映射后的终止坐标,(手指抬起) Matrix mMatrix; // 仿制图片的偏移矩阵 }
撤销及清屏
上面说到了GraffitiPath的作用,因此我们可以通过该类还原出每一次的涂鸦操作,我们把操作记录在集合CopyOnWriteArrayList<GraffitiPath>中,通过删除集合的最后一个元素即可实现撤销上一步操作的功能,同理,清除集合内的元素即可实现清屏,当然,删除后需要在原始图片上按次序还原集合中所有操作。
/** * 撤销 */ public void undo() { if (mPathStack.size() > 0) { mPathStack.remove(mPathStack.size() - 1); initCanvas(); draw(mBitmapCanvas, mPathStack, false); invalidate(); } }
最新源码在Github上的地址为:https://github.com/1993hzw/Graffiti ,
欢迎大家反馈问题,我会及时在上面更新代码,谢谢支持。
建议大家先阅读v3.0的代码,因为3.0之后加入了旋转等功能,原理变得更为复杂。v3.0的代码:
https://github.com/1993hzw/Graffiti/tree/v3.0
相关文章推荐
- android图片涂鸦,具有设置画笔,撤销,缩放移动等功能(一)
- Android matrix 控制图片的旋转、缩放、移动
- android 图片浏览时 两手指控制缩放 、移动
- Android:利用Matrix,实现双手指缩放图片与拖拽图片功能
- Android自由地对图片进行缩放和移动
- Android之关于手势操作图片的缩放与移动
- android imageview 多点触碰(MultiTouch)实现图片拖拽移动缩放
- 实现图片的移动和缩放的功能类(move and zoom)
- android imageview 多点触碰(MultiTouch)实现图片拖拽移动缩放
- 【移动开发】Android中图片的多点触控和缩放 推荐
- Android实现图片的缩放翻转功能
- android仿快图浏览,图片缩放移动效果
- android自定义设置菜单具有开关功能
- Android使用PhotoView实现图片缩放功能
- android :多点触摸图片移动缩放
- android开发-图片缩放,拖动功能实现。
- 实现图片的移动和缩放的功能类(move and zoom)
- Android开发:对图片的手势操作——旋转、缩放、移动
- Android图片查看器(图片可移动、缩放、旋转)
- Android单张图片查看、单指移动、双指缩放、双击最大化或最小化