android中自定義畫布Canvas的實現
阿新 • • 發佈:2019-01-11
一、要求:
1.畫布繪製控制元件的方法,控制元件應該是一個可以自定義的;
2.畫布是可以縮放,且提供一個縮放的方法供外使用;
3.控制元件之間連線的方法;
4.畫布縮放之後手勢滑動的識別實現;
2.可以連線;
3.未實現的是縮放的實現?
4.手勢的滑動識別:則在FabricView的onTouchEvent實現手勢的滑動識別:
在view中GestrueDetector.OnGstureListener監聽點選螢幕的介面和GestrueDetector.OnDoubleTapListener監聽雙擊的介面實現這兩個介面就可以實現手勢的實現。
5.縮放後手勢滑動識別的實現,同樣是在view中有ScaleGestureDetector.SimpleOnScaleGestureListener的介面實現之後運用座標系的改造,通過座標的算出比例值設定setScaleX、setScaleY,就可以實現手勢的縮放是實現:
1.CDrawable介面,只要實現這個介面的控制元件就可以繪製到自定義的畫布中:
3.接下來在上述兩個類和介面的基礎上,定製自己的FabricCanvasView畫布:
五、自己根據這實現的基本實現的自定義的畫布,來滿足專案的需求:
1.畫布繪製控制元件的方法,控制元件應該是一個可以自定義的;
2.畫布是可以縮放,且提供一個縮放的方法供外使用;
3.控制元件之間連線的方法;
4.畫布縮放之後手勢滑動的識別實現;
二、在github裡面種找到了一個類似度挺高的開源專案:
github中的第三方的開源專案地址:
在第三方的FabricView的專案中已經實現了:
1.控制元件的可以繪製;2.可以連線;
3.未實現的是縮放的實現?
4.手勢滑動的識別?
5.縮放之後的滑動識別?
三、需求改造:
把開源專案經過修剪和新增來實現自己專案中的畫布功能
1.在畫板上連線已經實現:
2.繪製控制元件只要一類實現CDrawable的介面並把這控制元件新增FabricView畫布的onDraw方法中的陣列中既可以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; }
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;
}
}
}
五、自己根據這實現的基本實現的自定義的畫布,來滿足專案的需求:
六、關於所用的方法總結:
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.