android中自定义画布Canvas的实现

一、要求:
1.画布绘制控件的方法,控件应该是一个可以自定义的;
2.画布是可以缩放,且提供一个缩放的方法供外使用;
3.控件之间连线的方法;
4.画布缩放之后手势滑动的识别实现;

二、在github里面种找到了一个类似度挺高的开源项目:

github中的第三方的开源项目地址

在第三方的FabricView的项目中已经实现了:

1.控件的可以绘制;
2.可以连线;
3.未实现的是缩放的实现?

4.手势滑动的识别?

5.缩放之后的滑动识别?

三、需求改造:
把开源项目经过修剪和添加来实现自己项目中的画布功能


1.在画板上连线已经实现:

public boolean onTouchDrawMode(MotionEvent event)
    {
        // get location of touch
        float eventX = event.getX();
        float eventY = event.getY();

        // based on the users action, start drawing
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // create new path and paint
                currentPath = new CPath();
                currentPaint = new Paint();
                currentPaint.setAntiAlias(true);
                currentPaint.setColor(mColor);
                currentPaint.setStyle(mStyle);
                currentPaint.setStrokeJoin(Paint.Join.ROUND);
                currentPaint.setStrokeWidth(mSize);
                currentPath.moveTo(eventX, eventY);
                currentPath.setPaint(currentPaint);
                // capture touched locations
                lastTouchX = eventX;
                lastTouchY = eventY;
                Log.i("Everbrilliant","点下去位置lastTouchX:"+lastTouchX+">>lastTouchY:"+lastTouchY);
                mDrawableList.add(currentPath);
                return true;
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_UP:
                currentPath.lineTo(eventX, eventY);
                // When the hardware tracks events faster than they are delivered, the
                // event will contain a history of those skipped points.
                int historySize = event.getHistorySize();
                for (int i = 0; i < historySize; i++) {
                    float historicalX = event.getHistoricalX(i);
                    float historicalY = event.getHistoricalY(i);
                    Log.i("everb","存在缓存中的值historicalX:"+historicalX+">>historicalY:"+historicalY);
                    if (historicalX < dirtyRect.left) {
                        dirtyRect.left = historicalX;
                    } else if (historicalX > dirtyRect.right) {
                        dirtyRect.right = historicalX;
                    }
                    if (historicalY < dirtyRect.top) {
                        dirtyRect.top = historicalY;
                    } else if (historicalY > dirtyRect.bottom) {
                        dirtyRect.bottom = historicalY;
                    }
                    currentPath.lineTo(historicalX, historicalY);
                }

                // After replaying history, connect the line to the touch point.
                currentPath.lineTo(eventX, eventY);
                cleanDirtyRegion(eventX, eventY);
                break;
            default:
                return false;
        }

        // Include some padding to ensure nothing is clipped
        invalidate(
                (int) (dirtyRect.left - 20),
                (int) (dirtyRect.top - 20),
                (int) (dirtyRect.right + 20),
                (int) (dirtyRect.bottom + 20));

        // register most recent touch locations
        lastTouchX = eventX;
        lastTouchY = eventY;
        return true;
    }
2.绘制控件只要一类实现CDrawable的接口并把这控件添加FabricView画布的onDraw方法中的数组中既可以

private ArrayList<CDrawable> mDrawableList = new ArrayList<>();
    /*
     * Called when there is the canvas is being re-drawn.
     */
    @Override
    protected void onDraw(Canvas canvas) {
        // check if background needs to be redrawn
        mBackgroundMode=BACKGROUND_STYLE_NOTEBOOK_PAPER;
        drawBackground(canvas, mBackgroundMode);

        // go through each item in the list and draw it
        for (int i = 0; i < mDrawableList.size(); i++) {
            try {
                mDrawableList.get(i).draw(canvas);
            }

            catch(Exception ex)
            {

            }
        }
    }

3.至于画布的缩放方法可以用view中的setScaleX、setScaleY来实现缩放。这里在放大后还要画布是可以拖动到:

private void updateScaleStep(int newScaleIndex) {
        if (newScaleIndex != mCurrentZoomScaleIndex) {
            final float oldViewScale = mViewScale;

            mCurrentZoomScaleIndex = newScaleIndex;
            mViewScale = ZOOM_SCALES[mCurrentZoomScaleIndex];
            final float scaleDifference = mViewScale - oldViewScale;
            scrollBy((int) (scaleDifference * getMeasuredWidth() / 2),
                    (int) (scaleDifference * getMeasuredHeight() / 2));

            if (shouldDrawGrid()) {
                mGridRenderer.updateGridBitmap(mViewScale);
            }

            mWorkspaceView.setScaleX(mViewScale);
            mWorkspaceView.setScaleY(mViewScale);
            mWorkspaceView.requestLayout();
        }
    }

4.手势的滑动识别:则在FabricView的onTouchEvent实现手势的滑动识别:
在view中GestrueDetector.OnGstureListener监听点击屏幕的接口和GestrueDetector.OnDoubleTapListener监听双击的接口实现这两个接口就可以实现手势的实现。

mTapGestureDetector = new GestureDetector(getContext(), new TapGestureListener());          //设置手势的监听

private class TapGestureListener implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {

        @Override
        public boolean onDown(MotionEvent motionEvent) {
            return false;
        }

        @Override
        public void onShowPress(MotionEvent motionEvent) {

        }

        @Override
        public boolean onSingleTapUp(MotionEvent motionEvent) {
            return false;
        }

        @Override
        public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
            return false;
        }

        @Override
        public void onLongPress(MotionEvent motionEvent) {
            // TODO: 2017/6/29 长按可以设置模式为连线
            Toast.makeText(mContext,"实现了长按手势",Toast.LENGTH_SHORT).show();
        }

        @Override
        public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
            return false;
        }

        @Override
        public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
            return false;
        }

        @Override
        public boolean onDoubleTap(MotionEvent motionEvent) {
            return false;
        }

        @Override
        public boolean onDoubleTapEvent(MotionEvent motionEvent) {
            return false;
        }
    }

5.缩放后手势滑动识别的实现,同样是在view中有ScaleGestureDetector.SimpleOnScaleGestureListener的接口实现之后运用坐标系的改造,通过坐标的算出比例值设置setScaleX、setScaleY,就可以实现手势的缩放是实现:

mScaleGestureDetector = new ScaleGestureDetector(getContext(), new ScaleGestureListener());         //设置手势缩放的监听

 private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        private float mStartFocusX;
        private float mStartFocusY;
        private float mStartScale;       
        private int mStartScrollX;
        private int mStartScrollY;

        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {
            mStartFocusX = detector.getFocusX();
            mStartFocusY = detector.getFocusY();
            mStartScrollX = getScrollX();
            mStartScrollY = getScrollY();

            mStartScale = mViewScale;
            return true;
        }

        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            final float oldViewScale = mViewScale;

            final float scaleFactor = detector.getScaleFactor();
            mViewScale *= scaleFactor;

            if (mViewScale < ZOOM_SCALES[0]) {
                mCurrentZoomScaleIndex = 0;
                mViewScale = ZOOM_SCALES[mCurrentZoomScaleIndex];
            } else if (mViewScale > ZOOM_SCALES[ZOOM_SCALES.length - 1]) {
                mCurrentZoomScaleIndex = ZOOM_SCALES.length - 1;
                mViewScale = ZOOM_SCALES[mCurrentZoomScaleIndex];
            } else {             
                float minDist = Float.MAX_VALUE;
                int index = ZOOM_SCALES.length - 1;
                for (int i = 0; i < ZOOM_SCALES.length; i++) {
                    float dist = Math.abs(mViewScale - ZOOM_SCALES[i]);
                    if (dist < minDist) {
                        minDist = dist;
                    } else {                       
                        index = i - 1;
                        break;
                    }
                }
                mCurrentZoomScaleIndex = index;
            }         
           ActionEditorCanvasView.this.setScaleX(mViewScale);
            ActionEditorCanvasView.this.setScaleY(mViewScale);           
            final float scaleDifference = mViewScale - mStartScale;
            final int scrollScaleX = (int) (scaleDifference * mStartFocusX);
            final int scrollScaleY = (int) (scaleDifference * mStartFocusY);
            final int scrollPanX = (int) (mStartFocusX - detector.getFocusX());
            final int scrollPanY = (int) (mStartFocusY - detector.getFocusY());
            scrollTo(mStartScrollX + scrollScaleX + scrollPanX,
                    mStartScrollY + scrollScaleY + scrollPanY);
            return true;
        }
    }
四.核心类的分析:
1.CDrawable接口,只要实现这个接口的控件就可以绘制到自定义的画布中:

CDrawable {
    Paint getPaint();                   //获取Paint
    int getXcoords();                   //获取X轴坐标
    int getYcoords();                   //获取Y轴坐标
    void setXcoords(int x);             //设置X轴坐标
    void setYcoords(int y);             //设置Y轴坐标
    void setPaint(Paint p);             //设置画笔
    void draw(Canvas canvas);           //绘制画板
}
  2.FabricView抽象类继承于View,主要功能是把控件绘制到这个画布中、绘制画布的背景、区分了几种绘制模式。

public abstract  class FabricView extends View {
    // painting objects and properties
    public ArrayList<CDrawable> mDrawableList = new ArrayList<>();
    private int mColor = Color.BLACK;

    // Canvas interaction modes
    private int mInteractionMode = DRAW_MODE;

    // background color of the library
    private int mBackgroundColor = Color.WHITE;
    // default style for the library
    private Paint.Style mStyle = Paint.Style.STROKE;

    // default stroke size for the library    默认点击的尺寸
    private float mSize = 5f;

    // flag indicating whether or not the background needs to be redrawn
    private boolean mRedrawBackground;

    // background mode for the library, default to blank
    private int mBackgroundMode = BACKGROUND_STYLE_BLANK;

    // Default Notebook left line color
    public static final int NOTEBOOK_LEFT_LINE_COLOR = Color.RED;

    // Flag indicating that we are waiting for a location for the text
    private boolean mTextExpectTouch;

    // Vars to decrease dirty area and increase performance
    private float lastTouchX, lastTouchY;
    private final RectF dirtyRect = new RectF();
    
    // keep track of path and paint being in use
    CPath currentPath;
    Paint currentPaint;

    /*********************************************************************************************/
    /************************************     FLAGS    *******************************************/
    /*********************************************************************************************/
    // Default Background Styles         背景颜色
    public static final int BACKGROUND_STYLE_BLANK = 0;
    public static final int BACKGROUND_STYLE_NOTEBOOK_PAPER = 1;
    public static final int BACKGROUND_STYLE_GRAPH_PAPER = 2;

    // Interactive Modes
    public static final int DRAW_MODE = 0;
    public static final int SELECT_MODE = 1; // TODO Support Object Selection.
    public static final int ROTATE_MODE = 2; // TODO Support Object ROtation.
    public static final int LOCKED_MODE = 3;

    /*********************************************************************************************/
    /**********************************     CONSTANTS    *****************************************/
    /*********************************************************************************************/
    public static final int NOTEBOOK_LEFT_LINE_PADDING = 120;

    /*********************************************************************************************/
    /************************************     TO-DOs    ******************************************/
    /*********************************************************************************************/
    private float mZoomLevel = 1.0f; //TODO Support Zoom                要去做支持放大缩小的功能
    private float mHorizontalOffset = 1, mVerticalOffset = 1; // TODO Support Offset and Viewport  支持可以偏移的功能
    public int mAutoscrollDistance = 100; // TODO Support Autoscroll


    /**
     * Default Constructor, sets sane values.
     *
     * @param context the activity that containts the view
     * @param attrs   view attributes
     */
    public FabricView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setFocusable(true);
        setFocusableInTouchMode(true);
        this.setBackgroundColor(mBackgroundColor);
        mTextExpectTouch = false;

    }

    /**
     * Called when there is the canvas is being re-drawn.
     */
    @Override
    protected void onDraw(Canvas canvas) {
        // check if background needs to be redrawn
        mBackgroundMode=BACKGROUND_STYLE_NOTEBOOK_PAPER;
        drawBackground(canvas, mBackgroundMode);

        // go through each item in the list and draw it
        for (int i = 0; i < mDrawableList.size(); i++) {
            try {
                mDrawableList.get(i).draw(canvas);
            }

            catch(Exception ex)
            {

            }
        }
    }


    /*********************************************************************************************/
    /*******************************     Handling User Touch    **********************************/
    /*********************************************************************************************/

    /**
     * Handles user touch event
     *
     * @param event the user's motion event
     * @return true, the event is consumed.
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // delegate action to the correct method
        if (getInteractionMode() == DRAW_MODE)                  //绘画的模式
            return onTouchDrawMode(event);
        else if (getInteractionMode() == SELECT_MODE)           //选择的模式
            return onTouchSelectMode(event);
        else if (getInteractionMode() == ROTATE_MODE)
            return onTouchRotateMode(event);
        // if none of the above are selected, delegate to locked mode
        else
            return onTouchLockedMode(event);
    }

    /**
     * Handles touch event if the mode is set to locked
     * @param event the event to handle
     * @return false, shouldn't do anything with it for now
     */
    private boolean onTouchLockedMode(MotionEvent event) {
        // return false since we don't want to do anything so far
        return true;
    }

    /**
     * Handles the touch input if the mode is set to rotate
     * @param event the touch event
     * @return the result of the action
     */
    private boolean onTouchRotateMode(MotionEvent event) {
        return false;
    }


    /**
     * Handles the touch input if the mode is set to draw
     * @param event the touch event
     * @return the result of the action
     */
    public boolean onTouchDrawMode(MotionEvent event)
    {
        // get location of touch
        float eventX = event.getX();
        float eventY = event.getY();
        Log.i("everb", "划线或的位置X信息:"+ event.getX()+"划线或的位置Y信息:"+eventY);


        // based on the users action, start drawing
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // create new path and paint
                currentPath = new CPath();
                currentPaint = new Paint();
                currentPaint.setAntiAlias(true);
                currentPaint.setColor(mColor);
                currentPaint.setStyle(mStyle);
                currentPaint.setStrokeJoin(Paint.Join.ROUND);
                currentPaint.setStrokeWidth(mSize);
                currentPath.moveTo(eventX, eventY);
                currentPath.setPaint(currentPaint);
                // capture touched locations
                lastTouchX = eventX;
                lastTouchY = eventY;
                mDrawableList.add(currentPath);
                return true;
            case MotionEvent.ACTION_MOVE:
            case MotionEvent.ACTION_UP:
                currentPath.lineTo(eventX, eventY);
                // When the hardware tracks events faster than they are delivered, the
                // event will contain a history of those skipped points.
                int historySize = event.getHistorySize();
                for (int i = 0; i < historySize; i++) {
                    float historicalX = event.getHistoricalX(i);
                    float historicalY = event.getHistoricalY(i);
                    if (historicalX < dirtyRect.left) {
                        dirtyRect.left = historicalX;
                    } else if (historicalX > dirtyRect.right) {
                        dirtyRect.right = historicalX;
                    }
                    if (historicalY < dirtyRect.top) {
                        dirtyRect.top = historicalY;
                    } else if (historicalY > dirtyRect.bottom) {
                        dirtyRect.bottom = historicalY;
                    }
                    currentPath.lineTo(historicalX, historicalY);
                }

                // After replaying history, connect the line to the touch point.
                currentPath.lineTo(eventX, eventY);
                cleanDirtyRegion(eventX, eventY);
                break;
            default:
                return false;
        }

        // Include some padding to ensure nothing is clipped
        invalidate(
                (int) (dirtyRect.left - 20),
                (int) (dirtyRect.top - 20),
                (int) (dirtyRect.right + 20),
                (int) (dirtyRect.bottom + 20));

        // register most recent touch locations
        lastTouchX = eventX;
        lastTouchY = eventY;
        return true;
    }

    /**
     * Handles the touch input if the mode is set to select
     * @param event the touch event
     */
    private boolean onTouchSelectMode(MotionEvent event) {
        // TODO Implement Method
        return false;
    }


    /*******************************************
     * Drawing Events
     ******************************************/
    /**
     * Draw the background on the canvas
     * @param canvas the canvas to draw on
     * @param backgroundMode one of BACKGROUND_STYLE_GRAPH_PAPER, BACKGROUND_STYLE_NOTEBOOK_PAPER, BACKGROUND_STYLE_BLANK
     */
    public void drawBackground(Canvas canvas, int backgroundMode) {
        canvas.drawColor(mBackgroundColor);
        if(backgroundMode != BACKGROUND_STYLE_BLANK) {
            Paint linePaint = new Paint();
            linePaint.setColor(Color.argb(50, 0, 0, 0));
            linePaint.setStyle(mStyle);
            linePaint.setStrokeJoin(Paint.Join.ROUND);
            linePaint.setStrokeWidth(mSize - 2f);
            switch (backgroundMode) {
                case BACKGROUND_STYLE_GRAPH_PAPER:
                    drawGraphPaperBackground(canvas, linePaint);
                    break;
                case BACKGROUND_STYLE_NOTEBOOK_PAPER:
                    drawNotebookPaperBackground(canvas, linePaint);
                default:
                    break;
            }
        }
        mRedrawBackground = false;
    }

    /**
     * Draws a graph paper background on the view
     * @param canvas the canvas to draw on
     * @param paint the paint to use
     */
    private void drawGraphPaperBackground(Canvas canvas, Paint paint) {
        int i = 0;
        boolean doneH = false, doneV = false;

        // while we still need to draw either H or V
        while (!(doneH && doneV)) {

            // check if there is more H lines to draw
            if (i < canvas.getHeight())
                canvas.drawLine(0, i, canvas.getWidth(), i, paint);
            else
                doneH = true;

            // check if there is more V lines to draw
            if (i < canvas.getWidth())
                canvas.drawLine(i, 0, i, canvas.getHeight(), paint);
            else
                doneV = true;

            // declare as done
            i += 75;
        }
    }

    /**
     * Draws a notebook paper background on the view
     * @param canvas the canvas to draw on
     * @param paint the paint to use
     */
    private void drawNotebookPaperBackground(Canvas canvas, Paint paint) {
        int i = 0;
        boolean doneV = false;
        // draw horizental lines
        while (!(doneV)) {
            if (i < canvas.getHeight())
                canvas.drawLine(0, i, canvas.getWidth(), i, paint);
            else
                doneV = true;
            i += 75;
        }
        // change line color
        paint.setColor(NOTEBOOK_LEFT_LINE_COLOR);
        // draw side line
        canvas.drawLine(NOTEBOOK_LEFT_LINE_PADDING, 0,
                NOTEBOOK_LEFT_LINE_PADDING, canvas.getHeight(), paint);


    }

    /**
     * Draw text on the screen
     * @param text the text to draw
     * @param x the x location of the text
     * @param y the y location of the text
     * @param p the paint to use
     */
    public void drawText(String text, int x, int y, Paint p) {
        mDrawableList.add(new CText(text, x, y, p));
        invalidate();
    }

    /**
     * Capture Text from the keyboard and draw it on the screen
     * //TODO Implement the method
     */
    private void drawTextFromKeyboard() {
        Toast.makeText(getContext(), "Touch where you want the text to be", Toast.LENGTH_LONG).show();
        //TODO
        mTextExpectTouch = true;
    }

    /**
     * Retrieve the region needing to be redrawn
     * @param eventX The current x location of the touch
     * @param eventY the current y location of the touch
     */
    private void cleanDirtyRegion(float eventX, float eventY) {
        // figure out the sides of the dirty region
        dirtyRect.left = Math.min(lastTouchX, eventX);
        dirtyRect.right = Math.max(lastTouchX, eventX);
        dirtyRect.top = Math.min(lastTouchY, eventY);
        dirtyRect.bottom = Math.max(lastTouchY, eventY);
    }

    /**
     * Clean the canvas, remove everything drawn on the canvas.
     */
    public void cleanPage() {
        // remove everything from the list
        while (!(mDrawableList.isEmpty())) {
            mDrawableList.remove(0);
        }
        // request to redraw the canvas
        invalidate();
    }

    /**
     * Draws an image on the canvas
     *
     * @param x      location of the image
     * @param y      location of the image
     * @param width  the width of the image
     * @param height the height of the image
     * @param pic    the image itself
     */
    public void drawImage(int x, int y, int width, int height, Bitmap pic) {
        CBitmap bitmap = new CBitmap(pic, x, y);
        bitmap.setWidth(width);
        bitmap.setHeight(height);
        mDrawableList.add(bitmap);
        invalidate();
    }


    /*******************************************
     * Getters and Setters
     ******************************************/


    /**
     * Gets what has been drawn on the canvas so far as a bitmap
     * @return Bitmap of the canvas.
     */
    public Bitmap getCanvasBitmap()
    {
        // build drawing cache of the canvas, use it to create a new bitmap, then destroy it.
        buildDrawingCache();
        Bitmap mCanvasBitmap = Bitmap.createBitmap(getDrawingCache());
        destroyDrawingCache();

        // return the created bitmap.
        return mCanvasBitmap;
    }

    public int getColor() {
        return mColor;
    }

    public void setColor(int mColor) {
        this.mColor = mColor;
    }

    public int getBackgroundColor() {
        return mBackgroundColor;
    }

    public int getBackgroundMode() {
        return mBackgroundMode;
    }

    public void setBackgroundMode(int mBackgroundMode) {
        this.mBackgroundMode = mBackgroundMode;
        invalidate();
    }

    public void setBackgroundColor(int mBackgroundColor) {
        this.mBackgroundColor = mBackgroundColor;
    }

    public Paint.Style getStyle() {
        return mStyle;
    }

    public void setStyle(Paint.Style mStyle) {
        this.mStyle = mStyle;
    }

    public float getSize() {
        return mSize;
    }

    public void setSize(float mSize) {
        this.mSize = mSize;
    }


    public int getInteractionMode() {
        return mInteractionMode;
    }

    public void setInteractionMode(int interactionMode) {

        // if the value passed is not any of the flags, set the library to locked mode
        if (interactionMode > LOCKED_MODE)
            interactionMode = LOCKED_MODE;
        else if (interactionMode < DRAW_MODE)
            interactionMode = LOCKED_MODE;

        this.mInteractionMode = interactionMode;
    }
}

3.接下来在上述两个类和接口的基础上,定制自己的FabricCanvasView画布:

package com.lejurobot.aelos.aelosmini.view;

import android.content.Context;
import android.graphics.Point;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.Toast;

import com.lejurobot.aelos.aelosmini.activities.ActionEditorActivity;
import com.lejurobot.aelos.aelosmini.view.FabricView.CDrawable;
import com.lejurobot.aelos.aelosmini.view.FabricView.FabricView;


/**
 * @author wangyao
 * @package com.example.administrator.myapplication
 * @date 2017/6/27  14:05
 * @describe 实现一个画布的放置控件、缩放画布、控件之间的连线、画布的手势识别、缩放后手势滑动的识别
 * @project
 */

public class ActionEditorCanvasView extends FabricView {

    private Context mContext;
    // Scale and zoom in/out factor.
    private static final int INIT_ZOOM_SCALES_INDEX = 0;
    private int mCurrentZoomScaleIndex = INIT_ZOOM_SCALES_INDEX;
    private static final float[] ZOOM_SCALES = new float[]{1.0f, 1.25f, 1.5f, 1.75f, 2.0f};
    private float mViewScale = ZOOM_SCALES[INIT_ZOOM_SCALES_INDEX];

    protected boolean mScrollable = true;
    private ScaleGestureDetector mScaleGestureDetector;                 //缩放手势
    private GestureDetector mTapGestureDetector;                        //手势监听类
    private int mPanningPointerId = MotionEvent.INVALID_POINTER_ID;
    private Point mPanningStart = new Point();
    private int mOriginalScrollX;
    private int mOriginalScrollY;
    private float mOffSetViewScroll;

    // Default desired width of the view in pixels.
    private static final int DESIRED_WIDTH = 2048;
    // Default desired height of the view in pixels.
    private static final int DESIRED_HEIGHT = 2048;
    // Interactive Modes
    public static final int DRAW_MODE = 0;              //可以绘制线段的模式
    public static final int SELECT_MODE = 1; // TODO Support Object Selection.
    public static final int ROTATE_MODE = 2; // TODO Support Object ROtation.
    public static final int LOCKED_MODE = 3;              //空模式

    // Default Background Styles         背景颜色
    public static final int BACKGROUND_STYLE_BLANK = 0;
    public static final int BACKGROUND_STYLE_NOTEBOOK_PAPER = 1;
    public static final int BACKGROUND_STYLE_GRAPH_PAPER = 2;


    public ActionEditorCanvasView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext=context;
    }

    /**
     * The method onFinishInflate() will be called after all children have been added.
     * 这个方法是所有的子view被添加之后调用
     */
    @Override
    public void onFinishInflate() {
        super.onFinishInflate();

        // Setting the child view's pivot point to (0,0) means scaling leaves top-left corner in
        // place means there is no need to adjust view translation.
        this.setPivotX(0);
        this.setPivotY(0);

        setWillNotDraw(false);
        setHorizontalScrollBarEnabled(mScrollable);                 //水平滑动滚动条的设置
        setVerticalScrollBarEnabled(mScrollable);                   //竖直滑动滚动条的设置
        setBackgroundMode(BACKGROUND_STYLE_GRAPH_PAPER);
        mScaleGestureDetector = new ScaleGestureDetector(getContext(), new ScaleGestureListener());         //设置手势缩放的监听

        mTapGestureDetector = new GestureDetector(getContext(), new TapGestureListener());          //设置手势的监听

    }

    public void setBackgroundMode(int mBackgroundMode) {
        super.setBackgroundMode(mBackgroundMode);
    }

    /**
     * 设置画布所处在的模式,可以添加并设置其他的模式
     */
    public void setInteractionMode(int interactionMode) {
        if (interactionMode == DRAW_MODE) {
            super.setInteractionMode(DRAW_MODE);
        } else if (interactionMode == LOCKED_MODE) {
            super.setInteractionMode(LOCKED_MODE);
        }else if (interactionMode==SELECT_MODE){
            super.setInteractionMode(SELECT_MODE);
        }

    }

    /**
     * 增加画布里面的控件或其他实现了CDrawable接口的类
     */
    public boolean addCanvasDrawable(CDrawable cDrawable) {
        super.mDrawableList.add(cDrawable);
        return true;
    }

    /**
     * 清除画布里面的控件
     */
    public void cleanPager() {
        super.cleanPage();
    }

    /**
     * 重置画布的大小尺寸
     */
    public void resetView() {
        // Reset scrolling state.
        mPanningPointerId = MotionEvent.INVALID_POINTER_ID;
        mPanningStart.set(0, 0);
        mOriginalScrollX = 0;
        mOriginalScrollY = 0;
        updateScaleStep(INIT_ZOOM_SCALES_INDEX);
        scrollTo((int) this.getX(), (int) this.getY());
    }


    @Override
    public boolean onTouchDrawMode(MotionEvent event) {
        event.offsetLocation(getScrollX(), getScrollY());                //缩放后偏移的距离,保证缩放后触点跟缩放前对应
        return super.onTouchDrawMode(event);
    }

    @Override
    public boolean onTouchSelectMode(MotionEvent event) {
        return super.onTouchSelectMode(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mScaleGestureDetector.onTouchEvent(event);
        mTapGestureDetector.onTouchEvent(event);
        // TODO: 2017/6/29 可根据触发事件做到动作控件的拖动
        return super.onTouchEvent(event);
    }

    /**
     * 画布实现缩小的方法
     *
     * @return
     */
    public boolean zoomOut() {
//        if (mScrollable && mCurrentZoomScaleIndex > 0) {
        if (mCurrentZoomScaleIndex > 0) {
            updateScaleStep(mCurrentZoomScaleIndex - 1);
            return true;
        }
        return false;
    }


    /**
     * 画布实现放大的方法
     *
     * @return
     */
    public boolean zoomIn() {
        if (mCurrentZoomScaleIndex < ZOOM_SCALES.length - 1) {
            updateScaleStep(mCurrentZoomScaleIndex + 1);
            return true;
        }
        return false;
    }

    /**
     * 缩放的具体实现
     *
     * @param newScaleIndex
     */
    private void updateScaleStep(int newScaleIndex) {
        if (newScaleIndex != mCurrentZoomScaleIndex) {
            final float oldViewScale = mViewScale;

            mCurrentZoomScaleIndex = newScaleIndex;
            mViewScale = ZOOM_SCALES[mCurrentZoomScaleIndex];

            final float scaleDifference = mViewScale - oldViewScale;
            scrollBy((int) (scaleDifference * getMeasuredWidth() / 2),
                    (int) (scaleDifference * getMeasuredHeight() / 2));

            this.setScaleX(mViewScale);
            this.setScaleY(mViewScale);
            this.requestLayout();
        }
    }

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(
                getMeasuredSize(widthMeasureSpec, DESIRED_WIDTH),
                getMeasuredSize(heightMeasureSpec, DESIRED_HEIGHT));

    }

    private static int getMeasuredSize(int measureSpec, int desiredSize) {
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);

        if (mode == MeasureSpec.EXACTLY) {
            return size;
        } else if (mode == MeasureSpec.AT_MOST) {
            return Math.min(size, desiredSize);
        } else {
            return desiredSize;
        }

    }


    private class TapGestureListener implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {

        @Override
        public boolean onDown(MotionEvent motionEvent) {
            return false;
        }

        @Override
        public void onShowPress(MotionEvent motionEvent) {

        }

        @Override
        public boolean onSingleTapUp(MotionEvent motionEvent) {
            return false;
        }

        @Override
        public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
            return false;
        }

        @Override
        public void onLongPress(MotionEvent motionEvent) {
            // TODO: 2017/6/29 长按可以设置模式为连线
            Toast.makeText(mContext,"实现了长按手势",Toast.LENGTH_SHORT).show();
        }

        @Override
        public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
            return false;
        }

        @Override
        public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
            return false;
        }

        @Override
        public boolean onDoubleTap(MotionEvent motionEvent) {
            return false;
        }

        @Override
        public boolean onDoubleTapEvent(MotionEvent motionEvent) {
            return false;
        }
    }

    private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        // Focus point at the start of the pinch gesture. This is used for computing proper scroll
        // offsets during scaling, as well as for simultaneous panning.
        private float mStartFocusX;
        private float mStartFocusY;
        // View scale at the beginning of the gesture. This is used for computing proper scroll
        // offsets during scaling.
        private float mStartScale;
        // View scroll offsets at the beginning of the gesture. These provide the reference point
        // for adjusting scroll in response to scaling and panning.
        private int mStartScrollX;
        private int mStartScrollY;

        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {
            mStartFocusX = detector.getFocusX();
            mStartFocusY = detector.getFocusY();
            mStartScrollX = getScrollX();
            mStartScrollY = getScrollY();

            mStartScale = mViewScale;
            return true;
        }

        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            final float oldViewScale = mViewScale;

            final float scaleFactor = detector.getScaleFactor();
            mViewScale *= scaleFactor;

            if (mViewScale < ZOOM_SCALES[0]) {
                mCurrentZoomScaleIndex = 0;
                mViewScale = ZOOM_SCALES[mCurrentZoomScaleIndex];
            } else if (mViewScale > ZOOM_SCALES[ZOOM_SCALES.length - 1]) {
                mCurrentZoomScaleIndex = ZOOM_SCALES.length - 1;
                mViewScale = ZOOM_SCALES[mCurrentZoomScaleIndex];
            } else {
                // find nearest zoom scale
                float minDist = Float.MAX_VALUE;
                // If we reach the end the last one was the closest
                int index = ZOOM_SCALES.length - 1;
                for (int i = 0; i < ZOOM_SCALES.length; i++) {
                    float dist = Math.abs(mViewScale - ZOOM_SCALES[i]);
                    if (dist < minDist) {
                        minDist = dist;
                    } else {
                        // When it starts increasing again we've found the closest
                        index = i - 1;
                        break;
                    }
                }
                mCurrentZoomScaleIndex = index;
            }

          /*  if (shouldDrawGrid()) {
                mGridRenderer.updateGridBitmap(mViewScale);
            }*/

           ActionEditorCanvasView.this.setScaleX(mViewScale);
            ActionEditorCanvasView.this.setScaleY(mViewScale);

            // Compute scroll offsets based on difference between original and new scaling factor
            // and the focus point where the gesture started. This makes sure that the scroll offset
            // is adjusted to keep the focus point in place on the screen unless there is also a
            // focus point shift (see next scroll component below).
            final float scaleDifference = mViewScale - mStartScale;
            final int scrollScaleX = (int) (scaleDifference * mStartFocusX);
            final int scrollScaleY = (int) (scaleDifference * mStartFocusY);

            // Compute scroll offset based on shift of the focus point. This makes sure the view
            // pans along with the focus.
            final int scrollPanX = (int) (mStartFocusX - detector.getFocusX());
            final int scrollPanY = (int) (mStartFocusY - detector.getFocusY());

            // Apply the computed scroll components for scale and panning relative to the scroll
            // coordinates at the beginning of the gesture.
            scrollTo(mStartScrollX + scrollScaleX + scrollPanX,
                    mStartScrollY + scrollScaleY + scrollPanY);

            return true;
        }
    }
}

五、自己根据这实现的基本实现的自定义的画布,来满足项目的需求:

  项目地址为:http://download.csdn.net/download/wangyongyao1989/9886503

六、关于所用的方法总结:

1.setScaleX、setScaleY:这是View中方法,设定View轴心点缩放能用于缩放View的大小;

2.offsetLocation(float deltaX,float deltaY):调整event的位置,参数的含义是调整横纵坐标的在event的数值;

3.getScrollX(),getScrollY():是View中的方法,返回View在坐标系中横纵坐标的数值;

4.getAction:获取触摸时间的类型值,比如说ACTION_DOWN、ACTION_MOVE、ACTION_UP等的动作;

5.getActionMask:返回正在执行被屏蔽的操作,没有指针索引信息。

6.getActionIndex:返回ACTION_POINTER_DOWN、ACTION_POINTER_MOVE、ACTION_POINTER_UP相关的指针;

7.getHistoricalX.getHistoricalY:返回在native中存储的坐标的值,比getX()/getY()获取得粒度更细。

8.


  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值