多點觸摸方面的知識借鑑於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);
}
}
}
}
}
使用效果: