因为项目需要,所以前段时间做了个绘图工具,不是很完美,下来展示一下效果图:
功能介绍
对于Canvrs就不用我多做介绍了,估计大家能来看这篇文章,也对Canvrs有了基本的认识,
不认识的朋友可以百度一下,网上有很多这种介绍。我就直接说下本位实现的功能
以及列出一些常用的属性。
- 自由绘制划线,画箭头
- 橡皮察
- 文字描述,可以设置字体大小,字体颜色
- 绘制图片,可以双指旋转,放大、放小、单指随意拖动
- 撤销划线,返回上一步。
- 添加背景
- 保存成图片存入本地
可以选择操作某个图片
说道画布,肯定是需要手势操作了,在这里列举画布常用的属性及手势操
setARGB(int a,int r,int g,int b):设置ARGB颜色
- setColor(int color):设置颜色。
- setAlpha(int a):设置透明度。
- setPathEffect(PathEffect effect):设置绘制路径时的路径效果。
- setShader(Shader shader):设置Paint的填充效果。
- setAntiAlias(boolean aa):设置是否抗锯齿。
- setStrokeWidth(float width):设置Paint的笔触宽度。
- setStyle(Paint.Style style):设置Paint的填充风格。
- setTextSize(float textSize):设置绘制文本时的文字大小。
手势操作
- MotionEvent.ACTION_DOWN:手指触摸屏幕。
- MotionEvent.ACTION_MOVE:手指移动。
- MotionEvent.ACTION_UP:手指离开屏幕。
- MotionEvent.ACTION_POINTER_DOWN:双指按下
- MotionEvent.ACTION_POINTER_UP:双指抬起
自由绘制划线,画箭头
因为要做撤销功能,所以会建个对象集合,用来保存每步的操作,每次会根据手指
的抬起或者按下时的坐标来存入path,和paint到集合。而划线是根据手指划过的
路径来绘制点,连起来就是一条线,你可以设置直线,或者是虚线,因项目需要,
我用的是虚线。下面给大家贴出代码
//虚线的画笔
mPaint.setAntiAlias(true);
mPaint.setColor(Color.RED);// 设置画笔的默认颜色
mPaint.setStyle(Paint.Style.STROKE);// 设置画笔的填充方式为无填充、仅仅是画线
mPaint.setStrokeWidth(5);// 设置画笔的宽度为1
PathEffect effects = new DashPathEffect(new float[]{5,5,5,5},1);
mPaint.setPathEffect(effects);
在onDraw方法里如下:
if (path != null) {
canvas.drawPath(path, mPaint); // 实时的显示
}
在onTouchEvent方法里如下
@Override
public boolean onTouchEvent(MotionEvent event) {
// 获取触摸位置
float x = event.getX();
float y = event.getY();
switch (event.getAction() & MotionEvent.ACTION_MASK){// 获取触摸的各个瞬间
case MotionEvent.ACTION_DOWN:// 手势按下
path=new Path();
// 手指摁下时清空之前的设置
dp = new DrawPathDTO();
dp.paint=mPaint;
dp.path=path;
path.moveTo(x,y);// 绘图的起始点
}
break;
case MotionEvent.ACTION_MOVE: //手势移动
float dx = Math.abs(x - preX);
float dy = Math.abs(y - preY);
if (dx > 5 || dy > 5) {// 用户要移动超过5像素才算是画图,免得手滑、手抖现象
path.quadTo(preX, preY, (x + preX) / 2, (y + preY) / 2);
preX = x;
preY = y;
cacheCanvas.drawPath(path, mPaint);// 绘制路径
}
break;
case MotionEvent.ACTION_UP://手势抬起
path.lineTo(preX,preY);
path = null;// 重新置空
savePath.add(dp)
break;
}
invalidate();
return true;
}
画箭头的方法,画箭头这个东西太麻烦啦,开始想用把箭头画好,然后到指定点旋转的方法但是,
效果一直不好。想用数学的方法来画,但是发现计算很复杂啊。于是google,
发现一个兄台使用了Java当中的awt实现了画箭头(http://www.bangchui.org/simple/?t16755.html),于是就借过来,改了一下。
/**
* 画箭头
* @param sx
* @param sy
* @param ex
* @param ey
*/
public void drawAL(int sx, int sy, int ex, int ey)
{
double H = 8; // 箭头高度
double L = 3.5; // 底边的一半
int x3 = 0;
int y3 = 0;
int x4 = 0;
int y4 = 0;
double awrad = Math.atan(L / H); // 箭头角度
double arraow_len = Math.sqrt(L * L + H * H); // 箭头的长度
double[] arrXY_1 = rotateVec(ex - sx, ey - sy, awrad, true, arraow_len);
double[] arrXY_2 = rotateVec(ex - sx, ey - sy, -awrad, true, arraow_len);
double x_3 = ex - arrXY_1[0]; // (x3,y3)是第一端点
double y_3 = ey - arrXY_1[1];
double x_4 = ex - arrXY_2[0]; // (x4,y4)是第二端点
double y_4 = ey - arrXY_2[1];
Double X3 = new Double(x_3);
x3 = X3.intValue();
Double Y3 = new Double(y_3);
y3 = Y3.intValue();
Double X4 = new Double(x_4);
x4 = X4.intValue();
Double Y4 = new Double(y_4);
y4 = Y4.intValue();
// 画线
myCanvas.drawLine(sx, sy, ex, ey,myPaint);
Path triangle = new Path();
triangle.moveTo(ex, ey);
triangle.lineTo(x3, y3);
triangle.lineTo(x4, y4);
triangle.close();
myCanvas.drawPath(triangle,myPaint);
}
// 计算
public double[] rotateVec(int px, int py, double ang, boolean isChLen, double newLen)
{
double mathstr[] = new double[2];
// 矢量旋转函数,参数含义分别是x分量、y分量、旋转角、是否改变长度、新长度
double vx = px * Math.cos(ang) - py * Math.sin(ang);
double vy = px * Math.sin(ang) + py * Math.cos(ang);
if (isChLen) {
double d = Math.sqrt(vx * vx + vy * vy);
vx = vx / d * newLen;
vy = vy / d * newLen;
mathstr[0] = vx;
mathstr[1] = vy;
}
return mathstr;
}
橡皮察
橡皮察的主要路,就是讲画笔的颜色改成跟画布一样的颜色,在吧画笔放大,就可以。没什么说的。
(有点投机取巧),哪位大神有新方法,也可分享下。
文字描述,可以设置字体大小,字体颜色
本来想做成跟WS一样自带可以拖动,移动,旋转的那种文本样式,结果想不到思路,
要不就是思路,用代码是现实不了,要不就是太麻烦。就写成了,单击屏幕获得坐标,
弹出对话框的形式,吧文本绘制的画布上。这个做的确实不满意,之后想到好思路再改吧,
哪位大神有想法希望可以跟小弟交流一下,感激不尽。
绘制图片,可以双指旋转,放大、放小、单指随意拖动
图片的绘制,是一定要保存每张图片的对象,图片分装成了Bitmap位图显示在画布上,
每次绘制的时候,都要根据保存的集合对象,全部绘制一遍,包括移动,旋转。还有之后选择
操作某张图片。主要代码在触摸事件里,我给出,我的触摸事件的所有代码,包括划线的。
@Override
public boolean onTouchEvent(MotionEvent event) {
// 获取触摸位置
float x = event.getX();
float y = event.getY();
switch (event.getAction() & MotionEvent.ACTION_MASK){// 获取触摸的各个瞬间
case MotionEvent.ACTION_DOWN:// 手势按下
path=new Path();
// 手指摁下时清空之前的设置
dp = new DrawPathDTO();
dp.paint=mPaint;
dp.path=path;
path.moveTo(x,y);// 绘图的起始点
if (mtype == 1) {
if (saveBitmap.size()>0&&saveBitmap!=null) {
Iterator<DrawBitmapDTO> iter = saveBitmap.iterator();
while (iter.hasNext()) {
DrawBitmapDTO drawbitmap = iter.next();
if (drawbitmap.isStamp){
mode = DRAG;
x_down = x;
y_down = y;
if (drawbitmap.isinit){
drawbitmap.matrix.postTranslate(x_down,y_down);//定位初始位置
drawbitmap.isinit=false;
}else{
drawbitmap.matrix.postTranslate(x_down-drawbitmap.X_last,y_down-drawbitmap.Y_last);//定位初始位置
}
savedMatrix.set(drawbitmap.matrix);
drawbitmap.X_last=x;drawbitmap.Y_last=y;
}
}
}
}
textX=x;
textY=y;
preX = x;
preY = y;
startX=event.getX();
startY=event.getY();
break;
case MotionEvent.ACTION_POINTER_DOWN:
if (saveBitmap.size()>0&&saveBitmap!=null) {
Iterator<DrawBitmapDTO> iter = saveBitmap.iterator();
while (iter.hasNext()) {
DrawBitmapDTO drawbitmap = iter.next();
if (drawbitmap.isStamp){
mode = ZOOM;
oldDist = spacing(event); //触碰两点间的距离
oldRotation = rotation(event); //计算旋转角度
savedMatrix.set(drawbitmap.matrix);
midPoint(mid, event);
}
}
}
break;
case MotionEvent.ACTION_MOVE: //手势移动
float dx = Math.abs(x - preX);
float dy = Math.abs(y - preY);
if (dx > 5 || dy > 5) {// 用户要移动超过5像素才算是画图,免得手滑、手抖现象
if (mtype==0){
path.quadTo(preX, preY, (x + preX) / 2, (y + preY) / 2);
preX = x;
preY = y;
cacheCanvas.drawPath(path, mPaint);// 绘制路径
}
if (mtype==1){
Iterator<DrawBitmapDTO> iter = saveBitmap.iterator();
while (iter.hasNext()) {
DrawBitmapDTO drawbitmap = iter.next();
if (drawbitmap.isStamp){
if (mode == ZOOM) {
matrix1.set(savedMatrix);
float rotation = rotation(event) - oldRotation;
float newDist = spacing(event);
float scale = newDist / oldDist;
matrix1.postScale(scale, scale, mid.x, mid.y);// 縮放
matrix1.postRotate(rotation, mid.x, mid.y);// 旋轉
matrixCheck = matrixCheck();
if (matrixCheck == false) {
drawbitmap.matrix.set(matrix1);
invalidate();
}
} else if (mode == DRAG) {
matrix1.set(savedMatrix);
matrix1.postTranslate(event.getX() - x_down, event.getY()
- y_down);// 平移
matrixCheck = matrixCheck(); //检查是否超出屏幕外
matrixCheck = matrixCheck();
if (matrixCheck == false) {
drawbitmap.matrix.set(matrix1);
invalidate();
}
}
}
}
}
}
break;
case MotionEvent.ACTION_UP://手势抬起
path.lineTo(preX,preY);
cacheCanvas.drawPath(path, mPaint); // 实时的显示
path = null;// 重新置空
savePath.add(dp);
if (mUp != null&&mtype==2) {
mUp.OnUp(x, y);
}
break;
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
}
invalidate();
return true;
}
//判断是超出界面,当然也可不判断
private boolean matrixCheck() {
if (saveBitmap.size()>0&&saveBitmap!=null) {
Iterator<DrawBitmapDTO> iter = saveBitmap.iterator();
while (iter.hasNext()) {
DrawBitmapDTO drawbitmap = iter.next();
if (drawbitmap.isStamp) {
float[] f = new float[9];
matrix1.getValues(f);
// 图片4个顶点的坐标
float x1 = f[0] * 0 + f[1] * 0 + f[2];
float y1 = f[3] * 0 + f[4] * 0 + f[5];
float x2 = f[0] * drawbitmap.imageBitmap.getWidth() + f[1] * 0 + f[2];
float y2 = f[3] * drawbitmap.imageBitmap.getWidth() + f[4] * 0 + f[5];
float x3 = f[0] * 0 + f[1] * drawbitmap.imageBitmap.getHeight() + f[2];
float y3 = f[3] * 0 + f[4] * drawbitmap.imageBitmap.getHeight() + f[5];
float x4 = f[0] * drawbitmap.imageBitmap.getWidth() + f[1] * drawbitmap.imageBitmap.getHeight() + f[2];
float y4 = f[3] * drawbitmap.imageBitmap.getWidth() + f[4] * drawbitmap.imageBitmap.getHeight() + f[5];
}
}
}
return false;
}
// 触碰两点间距离
private float spacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float)Math.sqrt(x * x + y * y);//FloatMath..sqrt(x * x + y * y);
}
// 取手势中心点
private void midPoint(PointF point, MotionEvent event) {
float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1);
point.set(x / 2, y / 2);
}
// 取旋转角度
private float rotation(MotionEvent event) {
double delta_x = (event.getX(0) - event.getX(1));
double delta_y = (event.getY(0) - event.getY(1));
double radians = Math.atan2(delta_y, delta_x);
return (float) Math.toDegrees(radians);
}
撤销划线,返回上一步。
这一步主要就是对之前划线,画图对象集合的操作,每次删掉集合最后一位,在从新绘制集合,
下面给大家上代码,我只是撤销掉了划线的集合。 如果大家需要,也可以自己加上撤销图片的集合
/**
* 撤销上一步操作
*/
public void undo() {
//TODO Auto-generated method stub
if (this.cacheBitmap != null) {
cacheBitmap = Bitmap.createBitmap(view_width, view_height, Bitmap.Config.ARGB_8888);
cacheCanvas.setBitmap(cacheBitmap);
}
if (savePath != null && savePath.size() > 0) {
// 移除最后一个path,相当于出栈操作
savePath.remove(savePath.size() - 1);
Iterator<DrawPathDTO> iter = savePath.iterator();
while (iter.hasNext()) {
DrawPathDTO drawPath = iter.next();
cacheCanvas.drawPath(drawPath.path, drawPath.paint);
}
}
invalidate();// 刷新
}
保存成图片存入本地
这个就不做叙述了,直接给大家上代码
//保存图片
public void saveBitmap(){
Bitmap b=BitmapFactory.decodeResource(getResources(), R.mipmap.bg);
Bitmap bitmap = Bitmap.createBitmap(view_width, view_height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawBitmap(b, 0, 0, null);
canvas.drawBitmap(cacheBitmap, 0, 0, mPaint);// 把cacheBitmap画到DrawView上
if (saveBitmap.size()>0&&saveBitmap!=null){
Iterator<DrawBitmapDTO> iter = saveBitmap.iterator();
while (iter.hasNext()) {
DrawBitmapDTO drawbitmap = iter.next();
canvas.drawBitmap(drawbitmap.imageBitmap, drawbitmap.matrix, mPaint);
}
}
//保存全部图层
canvas.save(Canvas.ALL_SAVE_FLAG);
canvas.restore();
//存储路径
File file = new File("/sdcard/song/");
if(!file.exists())
file.mkdirs();
try {
FileOutputStream fileOutputStream = new FileOutputStream(file.getPath() + "/ty2017.jpg");
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream);
fileOutputStream.close();
//弹出吐司,提醒用户图片的保存路径
Toast.makeText(getContext(),"图像已保存",
Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
}
}
其实主要的重点就是这些了,希望能帮到大家,哪里写的不好的地方,请大家勿怪……。下面将源码的链接给你们。