Android画板实现(含撤回,改变字体颜色)

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也會跟着變, 因為是同一個對象

参考资料:
android:画图板的撤销和恢复功能无法正常运行

android项目 之 记事本(8) ----- 画板功能之撤销、恢复和清空

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值