public class PaintView extends View {
/**
* 用于判断是否有绘制过
*/
private boolean DrawAlready = false;
private double drawLength = 0;
private static final String TAG = PaintView.class.getSimpleName();
private static final int DEFAULT_PAINT_COLOR = Color.BLACK;
private static final float DEFAULT_PAINT_STROKE_WIDTH = 2f;
private static final float TOUCH_TOLERANCE = 4;
private Paint linePaint;
private Paint mBitmapPaint;
private Path drawPath;
private int mHeight, mWidth;
private Bitmap mBitmap;
private boolean canvasInited;
private Canvas mCanvas;
private RectF mDisplayRect = new RectF();
private float mScale = 1;
private float blurRadius = 0.5f;
private boolean drawModel = false;
private Matrix displayMatrix = new Matrix();
private ArrayList<DrawPath> savePath;
private ArrayList<DrawPath> deletePath;
private DrawPath dp;
private OnDrawingListener onDrawingListener;
public PaintView(Context context) {
this(context, null);
}
public PaintView(Context context, AttributeSet attr) {
this(context, attr, 0);
}
public PaintView(Context context, AttributeSet attr, int defStyle) {
super(context, attr, defStyle);
initPaint();
savePath = new ArrayList<DrawPath>();
deletePath = new ArrayList<DrawPath>();
}
//路径对象
class DrawPath{
Path path;
Paint paint;
}
private void initPaint() {
drawPath = new Path();
linePaint = new Paint();
linePaint.setAntiAlias(true);
linePaint.setDither(true);
linePaint.setStrokeWidth(DEFAULT_PAINT_STROKE_WIDTH);
linePaint.setStrokeJoin(Join.ROUND);
linePaint.setStrokeCap(Cap.ROUND);
linePaint.setStyle(Paint.Style.STROKE);
linePaint.setColor(DEFAULT_PAINT_COLOR);
BlurMaskFilter bmf = new BlurMaskFilter(blurRadius, Blur.SOLID);
linePaint.setMaskFilter(bmf);
}
/**
* 撤销的核心思想就是将画布清空,
* 将保存下来的Path路径最后一个移除掉,
* 重新将路径画在画布上面。
*/
public void undo(){
System.out.println(savePath.size()+"--------------");
if(savePath != null && savePath.size() > 0){
//调用初始化画布函数以清空画布
Paint clearPaint = new Paint();
clearPaint.setXfermode(new PorterDuffXfermode(Mode.CLEAR));
mCanvas.drawPaint(clearPaint);
clearPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
initCanvas(1980, 1000);
//将路径保存列表中的最后一个元素删除 ,并将其保存在路径删除列表中
// DrawPath drawPath = savePath.get(savePath.size() - 1);
// deletePath.add(drawPath);
savePath.remove(savePath.size() - 1);
//
// //将路径保存列表中的路径重绘在画布上
Iterator<DrawPath> iter = savePath.iterator(); //重复保存
while (iter.hasNext()) {
DrawPath dp = iter.next();
mCanvas.drawPath(dp.path, dp.paint);
}
invalidate();// 刷新
}
}
public void setPaintColor(int paintColor) {
linePaint.setColor(paintColor);
}
public int getPaintColor() {
return linePaint.getColor();
}
public void setPaintStrokeWidth(float paintStrokeWidth) {
linePaint.setStrokeWidth(paintStrokeWidth);
}
public float getPaintStrokeWidth() {
return linePaint.getStrokeWidth();
}
public boolean isCanvasInited() {
return canvasInited;
}
public Bitmap getCacheBitmap() {
return mBitmap;
}
@TargetApi(Build.VERSION_CODES.KITKAT)
public Bitmap getCurrentBitmap() {
setDrawingCacheEnabled(true);
if(getDrawingCache()==null){
return null;
}
// Bitmap bmp = Bitmap.createBitmap(getDrawingCache().getWidth(), getDrawingCache().getHeight(), Bitmap.Config.ARGB_4444);
// getDrawingCache().setConfig(Config.ARGB_4444);
getDrawingCache().setConfig(Config.ARGB_4444);
Bitmap bmp = Bitmap.createBitmap(getDrawingCache());
setDrawingCacheEnabled(false);
return bmp;
}
public boolean getDrawAlready(){
return (DrawAlready&&drawLength>40)?true:false;
//return DrawAlready;
}
public int getCanvasWidth() {
return mWidth;
}
public int getCanvasHeight() {
return mHeight;
}
/**
* 初始化画布大小
*
* @param width
* @param height
*/
public void initCanvas(int width, int height) {
if (width == 0 || height == 0) {
Log.w(TAG, this + ",initCanvas...width or height is null");
return;
}
if (canvasInited) {
Log.w(TAG, this + ",canvas has inited");
return;
}
ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
mWidth = width;
mHeight = height;
canvasInited = true;
mBitmap = Bitmap.createBitmap(mWidth, mHeight, Config.ARGB_4444);
mCanvas = new Canvas(mBitmap);
mBitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG
| Paint.ANTI_ALIAS_FLAG);
mBitmapPaint.setAntiAlias(true);
Log.i(TAG, this + ",canvas inited...width=" + width + ",height="
+ height);
}
/**
* 改变画布显示大小(实际大小不改变)
*
* @param rect
*/
public void changeCanvasDisplaySize(RectF rect, Matrix displayMatrix) {
if (rect == null || displayMatrix == null) {
return;
}
mDisplayRect.set(rect);
this.displayMatrix.set(displayMatrix);
float[] values = new float[9];
displayMatrix.getValues(values);
mScale = values[0];
postInvalidate();
}
/**
* 是否开启绘图模式
*
* @param enableDraw
*/
public void enableDrawMode(boolean enableDraw) {
drawModel = enableDraw;
}
private float curX, curY;
/**
* 发送同屏的时候需要发送屏幕坐标而不是控件坐标,这样才能为以后的屏幕适配做准备
*
* */
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!drawModel) {
return false;
}
if (mCanvas == null) {
Log.w(TAG, this + ",canvas not init");
return false;
}
// 控件坐标
float bitmapX = getCanvasCoordinateX(event);
float bitmapY = getCanvasCoordinateY(event);
// 屏幕坐标
float screenX = event.getX();
float screenY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
curX = bitmapX;
curY = bitmapY;
drawPath.moveTo(curX, curY);
if (null != onDrawingListener) {
onDrawingListener.onDrawingDown(bitmapX, bitmapY);
}
break;
case MotionEvent.ACTION_MOVE:
float dx = Math.abs(bitmapX - curX);
float dy = Math.abs(curY - bitmapY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
DrawAlready = true;
drawLength = drawLength + Math.sqrt(dx*dx +dy*dy);
drawPath.quadTo(curX, curY, (bitmapX + curX) / 2,
(bitmapY + curY) / 2);
mCanvas.drawPath(drawPath, linePaint);
invalidate();
curX = bitmapX;
curY = bitmapY;
if (null != onDrawingListener) {
onDrawingListener.onDrawingMove(bitmapX, bitmapY);
}
}
break;
case MotionEvent.ACTION_UP:
dp = new DrawPath();
dp.path = new Path(drawPath);
dp.paint = new Paint(linePaint);
savePath.add(dp);
drawPath.reset();
if (null != onDrawingListener) {
onDrawingListener.onDrawdingUp(screenX, screenY);
}
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mBitmap != null) {
canvas.drawBitmap(mBitmap, displayMatrix, mBitmapPaint);
}
}
private float lastInputX, lastInputY; // 前一个点
private Path inputPath = new Path(); // 主动绘制的路径
/**
* 设置画线的起点
*
* @param startX
* @param startY
*/
public void setDrawDownPoint(float startX, float startY, boolean clearPath) {
if (clearPath) {
inputPath.reset();
}
// float activeX = getCanvasCoordinateX(startX);
// float activeY = getCanvasCoordinateY(startY);
float activeX = startX;
float activeY = startY;
inputPath.moveTo(activeX, activeY);
lastInputX = activeX;
lastInputY = activeY;
}
/**
* 多点画线,如果不刷新,将把点增加到路径上
*
* @param points
* [x1,y1,x2,y2,x3,y3,...]
* @param invalidate
* 是否立即刷新控件
*/
public void drawBezierCurveWithPoints(float[] points, boolean invalidate) {
if (!canvasInited) {
Log.w(TAG, this + ",canvas must be init firstly");
return;
}
if (points == null || points.length == 0 || points.length % 2 != 0) {
Log.w(TAG, this + ",points is illegal in drawBezierCurve");
return;
}
for (int i = 0; i < points.length; i++) {
// 控件坐标
// float activeX = getCanvasCoordinateX(points[i]);
// float activeY = getCanvasCoordinateY(points[++i]);
float activeX = points[i];
float activeY = points[++i];
if (inputPath.isEmpty()) { // 未设置down点,move的第一个点作为down点
inputPath.moveTo(activeX, activeY);
} else {
float dx = Math.abs(activeX - lastInputX);
float dy = Math.abs(activeY - lastInputY);
// 触摸间隔大于阈值才绘制路径
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
// 从x1,y1到x2,y2画一条贝塞尔曲线,更平滑(直接用mPath.lineTo也是可以的)
// inputPath.lineTo(activeX, activeY);
inputPath.quadTo(lastInputX, lastInputY,
(activeX + lastInputX) / 2,
(activeY + lastInputY) / 2);
if (invalidate) {
mCanvas.drawPath(inputPath, linePaint);
postInvalidate();
}
}
}
lastInputX = activeX;
lastInputY = activeY;
}
}
/**
* 将把之前设置的路径应用到控件上,并清空路径
*/
public void drawWithSetedPoints(boolean clearSettedPath) {
if (mCanvas != null && inputPath != null && linePaint != null) {
mCanvas.drawPath(inputPath, linePaint);
postInvalidate();
if (clearSettedPath) {
inputPath.reset();
}
}
}
/**
* 将路径画到画布上,并刷新控件
*
* @param path
*/
public void drawPath(Path path) {
if (path != null && mCanvas != null && linePaint != null) {
mCanvas.drawPath(path, linePaint);
postInvalidate();
}
}
/**
* 清除画布
*/
public void clearCanvas() {
savePath.clear();
deletePath.clear();
DrawAlready = false;
drawLength = 0;
Paint clearPaint = new Paint();
clearPaint.setXfermode(new PorterDuffXfermode(Mode.CLEAR));
mCanvas.drawPaint(clearPaint);
clearPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
invalidate();
}
/** 从控件坐标转换为画布上的坐标 */
private float getCanvasCoordinateX(MotionEvent event) {
return (event.getX() + getOffViewLeftPx()) / mScale;
}
private float getCanvasCoordinateY(MotionEvent event) {
return (event.getY() + getOffViewTopPx()) / mScale;
}
// 获取图像左边超出控件部分的像素
private float getOffViewLeftPx() {
return -mDisplayRect.left;
}
private float getOffViewTopPx() {
return -mDisplayRect.top;
}
/**
* 设置批注监听器供调用类使用,外部类需要实现接口onDrawingCommentListener,接口内可以做发送批注等操作
*
* */
public void setOnDrawingListener(OnDrawingListener listener) {
this.onDrawingListener = listener;
}
public static interface OnDrawingListener {
/**
* @param bitmapX
* 图片上的x坐标
* @param bitmapY
* 图片上的y坐标
*/
void onDrawingDown(float bitmapX, float bitmapY);
void onDrawingMove(float bitmapX, float bitmapY);
void onDrawdingUp(float bitmapX, float bitmapY);
}
}
主要两个点:
1.在其他的博文中看到画图要画完一笔才会显示,改为可以看见自己的绘画轨迹:
如果在MotionEvent.ACTION_UP中去更新画布,就会只在画完后显示,改在MotionEvent.ACTION_MOVE中去调用mCanvas.drawPath(drawPath, linePaint);
方法
2.撤回:
主要是添加一个路径的集合,将绘画过的路径添加进去,按撤回键的时候,把画布清空,再把集合内size-1个路径重新绘制一遍。
这里在用别人代码的过程中发现了一个问题,无法重新绘制路径,最后发现要将原博中的dp.path = drawPath;
改为dp.path = new Path(drawPath);
原因是:
JAVA裏, 除了原子類型, 其他都是引用, dp.path = path;後, 你調用path.reset(), dp.path也會跟着變, 因為是同一個對象