一種自己寫的多指觸摸白板控件Demo

多點觸摸方面的知識借鑑於Google的代碼:

https://github.com/googlearchive/android-BasicMultitouch/blob/master/Application/src/main/java/com/example/android/basicmultitouch/TouchDisplayView.java

 

然後我自己寫的這個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);
                }
            }
        }
    }
}

使用效果:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章