多点触摸方面的知识借鉴于Google的代码:
然后我自己写的这个Demo控件,包含随机颜色Paint构造的办法、Path的二阶贝塞尔曲线的使用办法、在Canvas上分行绘制文本的办法、多指书写的方法等。可以搭配我的仿地图的View来实现可平移、放大绘制上去的内容的白板
自定义控件DrawView:
package com.testFileSystem;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
/**
* Created by cjz on 2019/11/25.
* 绘图用的窗体
*/
public class DrawView extends View{
private Curv currentCurv;
/**当前绘制画布**/
private Canvas canvas;
/**进行过初始化了吗**/
private boolean isInitFinished = false;
/**数据读写根目录**/
private String rootPath;
/**控件长宽**/
private int mWidth, mHeight;
/**当前绘制画布**/
private Bitmap canvasBitmap;
/**是否绘制触摸**/
private boolean isShowTouchEvent = true;
/**事件累积**/
private StringBuffer touchEventStringBuffer = new StringBuffer();
/**当前正在绘制的线条组合**/
private Map<Integer, BaseShape> currentDrawingMap = new HashMap<>();
public DrawView(Context context) {
super(context);
}
public DrawView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public DrawView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(!isInitFinished){
rootPath = getContext().getFilesDir().getAbsolutePath() + File.separatorChar + "drawView";
File rootDir = new File(rootPath);
if(!rootDir.exists()){
rootDir.mkdir();
}
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
mWidth = width;
mHeight = height;
canvasBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
canvas = new Canvas(canvasBitmap);
isInitFinished = true;
}
}
/**获取绘制笔**/
private Paint makePaint(){
Paint paint = new Paint();
paint.setStrokeWidth(12f);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setAntiAlias(true);
int color = 0xFF000000;
//随机颜色
color |= ((int) (Math.random() * 255 + 1) << 16);
color |= ((int) (Math.random() * 255 + 1) << 8);
color |= ((int) (Math.random() * 255 + 1));
paint.setColor(color);
return paint;
}
/**书写**/
private void penDraw(MotionEvent event) {
int actionType = event.getAction() & MotionEvent.ACTION_MASK;
switch (actionType){
case MotionEvent.ACTION_POINTER_DOWN: {
Log.i("penDraw_AT", "MotionEvent.ACTION_POINTER_DOWN");
int id = event.getPointerId(event.getActionIndex());
touchEventStringBuffer.append("MotionEvent.ACTION_DOWN, id:" + id + "\n");
Paint paint = makePaint();
currentCurv = new Curv(paint);
currentCurv.draw(event.getX(event.getActionIndex()), event.getY(event.getActionIndex()), event.getAction(), canvas);
currentDrawingMap.put(id, currentCurv);
break;
}
case MotionEvent.ACTION_DOWN: {
int id = event.getPointerId(event.getActionIndex());
touchEventStringBuffer.append("MotionEvent.ACTION_DOWN, id:" + id + "\n");
Paint paint = makePaint();
currentCurv = new Curv(paint);
currentCurv.draw(event.getX(event.getActionIndex()), event.getY(event.getActionIndex()), event.getAction(), canvas);
currentDrawingMap.put(id, currentCurv);
break;
}
case MotionEvent.ACTION_MOVE: {
for (int i = 0; i < event.getPointerCount(); i++) {
int id = event.getPointerId(i);
touchEventStringBuffer.append("MotionEvent.ACTION_MOVE, id:" + id + "\n");
Curv curv = (Curv) currentDrawingMap.get(id);
if (curv != null) {
curv.draw(event.getX(i), event.getY(i), event.getAction(), canvas);
}
}
break;
}
case MotionEvent.ACTION_UP: {
int id = event.getPointerId(event.getActionIndex());
currentDrawingMap.remove(id);
break;
}
case MotionEvent.ACTION_POINTER_UP: {
int id = event.getPointerId(event.getActionIndex());
currentDrawingMap.remove(id);
break;
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(getClass().getName(), event.toString());
penDraw(event);
invalidate();
return true;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(canvasBitmap, 0, 0, null);
if(isShowTouchEvent) {
//顺便随手写个多行文本框示例
float fontSize = 20f;
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStrokeWidth(1f);
paint.setTextSize(fontSize);
//显示触摸事件
String eventStr[] = touchEventStringBuffer.toString().split("\n");
for(int i = 0; i < eventStr.length; i++){
canvas.drawText(eventStr[i], 0, fontSize * (i + 1), paint);
}
touchEventStringBuffer = new StringBuffer();
}
}
}
绘制线条对象类:
package com.testFileSystem;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.Log;
import android.view.MotionEvent;
import java.util.ArrayList;
import java.util.List;
import javax.microedition.khronos.opengles.GL10;
/**
* 曲线容器,一个容器的曲线>=1
* Created by cjz on 2018/9/17.
*/
public class Curv extends BaseShape{
public Paint paint;
private List<PointF> touchPointList = new ArrayList<>();
private List<Path> segPathList = new ArrayList<>(); //用于加速画布的一小段一小段的path
private List<Path> pathList = new ArrayList<>(); //用于保存该容器里面有多少个path
/**
* 点信息
*/
private PointF start;
private PointF last;
private PointF current;
private PointF mid;
private PointF end;
private float cx;
private float cy;
private float midX;
private float midY;
private float startX;
private float startY;
/**
* 绘制范围
*/
public RectF range;
private float width = 2 / 3.3f;
private boolean isStart = false;
public Path totalPath;
private Path drawPath;
private void init() {
start = new PointF();
last = new PointF();
current = new PointF();
mid = new PointF();
end = new PointF();
range = new RectF();
totalPath = new Path();
pathList.add(totalPath);
}
public boolean isStart() {
return isStart;
}
public Curv(Paint paint) {
this.paint = new Paint(paint);
init();
}
/**
* 处理范围
*
* @param x 判断点x
* @param y 判断点y
*/
private void handleRange(float x, float y) {
if (x <= range.left)
range.left = x;
if (y <= range.top)
range.top = y;
if (x >= range.right)
range.right = x;
if (y >= range.bottom)
range.bottom = y;
}
public Rect getRect() {
int padding = (int) (paint.getStrokeWidth() / 2 + 5);
RectF rectF = new RectF();
drawPath.computeBounds(rectF, true);
return new Rect((int) rectF.left - padding, (int) rectF.top - padding, (int) rectF.right + padding, (int) rectF.bottom + padding);
}
public void setCurrentRaw(float x, float y, int action) {
//记录起始点
if (!isStart) {
start.set(x, y);
mid.set(x,y);
end.set(x,y);
isStart = true;
}
//记录上一个点
last.set(current.x, current.y);
//记录当前点
current.set(x, y);
//处理范围
handleRange(x, y);
}
/**
* 落闸放点(狗),贝塞尔曲线化
*
* @param x
* @param y
* @param action
*/
public void draw(float x, float y, int action, Canvas canvas) {
if(!isStart()) {
setCurrentRaw(x, y, action);
totalPath.moveTo(x, y);
// if(!isBuildPathAllDoing)
touchPointList.add(new PointF(x, y));
segPathList.add(new Path());
canvas.drawPath(segPathList.get(segPathList.size() - 1), paint);
} else {
if (action == MotionEvent.ACTION_UP)
System.out.println("setCurrent end " + x + " , " + y);
touchPointList.add(new PointF(x, y));
drawPath = new Path();
segPathList.add(drawPath);
setCurrentRaw(x, y, action);
double distance = Math.sqrt(Math.pow(Math.abs(x - last.x), 2) + Math.pow(Math.abs(y - last.y), 2));
/**如果两次点击之间的距离过大,就判断为该点报废,Current点回退到last点**/
if (distance > 400) { //如果距离突变过长,判断为无效点,直接current回退到上一次纪录的last的点,并且用UP时间结束这次path draw
Log.i("NewCurv.SetCurrent", "超长" + distance);
// super.setCurrent(getLast().x, getLast().y, MotionEvent.ACTION_UP);
System.out.println("超长?");
return;
}
cx = last.x;
cy = last.y;
midX = (x + cx) / 2;
midY = (y + cy) / 2;
startX = mid.x;
startY = mid.y;
mid.x = midX;
mid.y = midY;
drawPath.moveTo(startX, startY);
double s = Math.sqrt(Math.pow(x - cx, 2) + Math.pow(y - cy, 2));
if (action == MotionEvent.ACTION_UP){
drawPath.lineTo(x,y);
totalPath.lineTo(x, y);
} else {
if (s < 200) {
if (s < 2) {//1.10 //2.12 //3.15
drawPath.cubicTo(cx, cy, midX, midY, x, y);
totalPath.cubicTo(cx, cy, midX, midY, x, y);
System.out.println("cubicTo");
} else {
drawPath.quadTo(cx, cy, midX, midY);
totalPath.quadTo(cx, cy, midX, midY);
// System.out.println("quadTo");
}
} else {
drawPath.quadTo(cx, cy, midX, midY);
totalPath.quadTo(cx, cy, midX, midY);
}
}
canvas.drawPath(segPathList.get(segPathList.size() - 1), paint);
}
//抬起时把画好的线段生成OpenGL线段
// if(action == MotionEvent.ACTION_UP) {
// //OpenGL此时DPI和Canvas不一样,要放大再对景区
// Path path = new Path();
// Matrix matrix = new Matrix();
// matrix.postScale(UITrees.openGLRenderer.scale / 2, UITrees.openGLRenderer.scale / 2, UITrees.panelView.scaleCenterPoint.x, UITrees.panelView.scaleCenterPoint.y);
// totalPath.transform(matrix, path);
//
// PathMeasure pathMeasure = new PathMeasure();
// pathMeasure.setPath(path, false);
// float step = 10f / paint.getStrokeWidth() > 1 ? 10f / paint.getStrokeWidth() : 1; //粗线条的点密度设置大一些咯
//
// float[] point = new float[2];
// for(float i = 0; i < pathMeasure.getLength(); i += step) {
// pathMeasure.getPosTan(i, point, null);
// //todo 缩放之后,Canvas再加Path的时候还是采用实际点,但OpenGL用了这个点就和Canvas的不对齐了,因为OpenGL缩放是把画布前后推,要做做换算,例如缩放小了,左上角的坐标是画布外的坐标
//
// float realtiveX = point[0] / 1080 * 4f - UITrees.openGLRenderer.dx; //4个象限
// float realtiveY = -point[1] / 1080 * 4f + UITrees.openGLRenderer.dy ;
//
// glLine.drawLine(realtiveX, realtiveY);
// }
// }
if(action == MotionEvent.ACTION_UP) {
if(totalPath != null && paint != null){
PathMeasure pathMeasure = new PathMeasure(totalPath, false);
if(pathMeasure.getLength() < 2f){
paint.setStyle(Paint.Style.FILL);
totalPath = new Path();
totalPath.addCircle(x + paint.getStrokeWidth() / 2f, y + paint.getStrokeWidth() / 2f, paint.getStrokeWidth() / 2f, Path.Direction.CCW);
canvas.drawPath(totalPath, paint);
}
}
}
}
}
使用效果: