当前位置:   article > 正文

android中自定义画布Canvas的实现_android 自由画布 是不是从pointerlocation改造的?

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

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

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

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

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

4.手势滑动的识别?

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

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


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

  1. public boolean onTouchDrawMode(MotionEvent event)
  2. {
  3. // get location of touch
  4. float eventX = event.getX();
  5. float eventY = event.getY();
  6. // based on the users action, start drawing
  7. switch (event.getAction()) {
  8. case MotionEvent.ACTION_DOWN:
  9. // create new path and paint
  10. currentPath = new CPath();
  11. currentPaint = new Paint();
  12. currentPaint.setAntiAlias(true);
  13. currentPaint.setColor(mColor);
  14. currentPaint.setStyle(mStyle);
  15. currentPaint.setStrokeJoin(Paint.Join.ROUND);
  16. currentPaint.setStrokeWidth(mSize);
  17. currentPath.moveTo(eventX, eventY);
  18. currentPath.setPaint(currentPaint);
  19. // capture touched locations
  20. lastTouchX = eventX;
  21. lastTouchY = eventY;
  22. Log.i("Everbrilliant","点下去位置lastTouchX:"+lastTouchX+">>lastTouchY:"+lastTouchY);
  23. mDrawableList.add(currentPath);
  24. return true;
  25. case MotionEvent.ACTION_MOVE:
  26. case MotionEvent.ACTION_UP:
  27. currentPath.lineTo(eventX, eventY);
  28. // When the hardware tracks events faster than they are delivered, the
  29. // event will contain a history of those skipped points.
  30. int historySize = event.getHistorySize();
  31. for (int i = 0; i < historySize; i++) {
  32. float historicalX = event.getHistoricalX(i);
  33. float historicalY = event.getHistoricalY(i);
  34. Log.i("everb","存在缓存中的值historicalX:"+historicalX+">>historicalY:"+historicalY);
  35. if (historicalX < dirtyRect.left) {
  36. dirtyRect.left = historicalX;
  37. } else if (historicalX > dirtyRect.right) {
  38. dirtyRect.right = historicalX;
  39. }
  40. if (historicalY < dirtyRect.top) {
  41. dirtyRect.top = historicalY;
  42. } else if (historicalY > dirtyRect.bottom) {
  43. dirtyRect.bottom = historicalY;
  44. }
  45. currentPath.lineTo(historicalX, historicalY);
  46. }
  47. // After replaying history, connect the line to the touch point.
  48. currentPath.lineTo(eventX, eventY);
  49. cleanDirtyRegion(eventX, eventY);
  50. break;
  51. default:
  52. return false;
  53. }
  54. // Include some padding to ensure nothing is clipped
  55. invalidate(
  56. (int) (dirtyRect.left - 20),
  57. (int) (dirtyRect.top - 20),
  58. (int) (dirtyRect.right + 20),
  59. (int) (dirtyRect.bottom + 20));
  60. // register most recent touch locations
  61. lastTouchX = eventX;
  62. lastTouchY = eventY;
  63. return true;
  64. }
2.绘制控件只要一类实现CDrawable的接口并把这控件添加FabricView画布的onDraw方法中的数组中既可以

  1. private ArrayList<CDrawable> mDrawableList = new ArrayList<>();
  2. /*
  3. * Called when there is the canvas is being re-drawn.
  4. */
  5. @Override
  6. protected void onDraw(Canvas canvas) {
  7. // check if background needs to be redrawn
  8. mBackgroundMode=BACKGROUND_STYLE_NOTEBOOK_PAPER;
  9. drawBackground(canvas, mBackgroundMode);
  10. // go through each item in the list and draw it
  11. for (int i = 0; i < mDrawableList.size(); i++) {
  12. try {
  13. mDrawableList.get(i).draw(canvas);
  14. }
  15. catch(Exception ex)
  16. {
  17. }
  18. }
  19. }

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

  1. private void updateScaleStep(int newScaleIndex) {
  2. if (newScaleIndex != mCurrentZoomScaleIndex) {
  3. final float oldViewScale = mViewScale;
  4. mCurrentZoomScaleIndex = newScaleIndex;
  5. mViewScale = ZOOM_SCALES[mCurrentZoomScaleIndex];
  6. final float scaleDifference = mViewScale - oldViewScale;
  7. scrollBy((int) (scaleDifference * getMeasuredWidth() / 2),
  8. (int) (scaleDifference * getMeasuredHeight() / 2));
  9. if (shouldDrawGrid()) {
  10. mGridRenderer.updateGridBitmap(mViewScale);
  11. }
  12. mWorkspaceView.setScaleX(mViewScale);
  13. mWorkspaceView.setScaleY(mViewScale);
  14. mWorkspaceView.requestLayout();
  15. }
  16. }

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

  1. mTapGestureDetector = new GestureDetector(getContext(), new TapGestureListener()); //设置手势的监听
  2. private class TapGestureListener implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {
  3. @Override
  4. public boolean onDown(MotionEvent motionEvent) {
  5. return false;
  6. }
  7. @Override
  8. public void onShowPress(MotionEvent motionEvent) {
  9. }
  10. @Override
  11. public boolean onSingleTapUp(MotionEvent motionEvent) {
  12. return false;
  13. }
  14. @Override
  15. public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
  16. return false;
  17. }
  18. @Override
  19. public void onLongPress(MotionEvent motionEvent) {
  20. // TODO: 2017/6/29 长按可以设置模式为连线
  21. Toast.makeText(mContext,"实现了长按手势",Toast.LENGTH_SHORT).show();
  22. }
  23. @Override
  24. public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
  25. return false;
  26. }
  27. @Override
  28. public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
  29. return false;
  30. }
  31. @Override
  32. public boolean onDoubleTap(MotionEvent motionEvent) {
  33. return false;
  34. }
  35. @Override
  36. public boolean onDoubleTapEvent(MotionEvent motionEvent) {
  37. return false;
  38. }
  39. }

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

  1. mScaleGestureDetector = new ScaleGestureDetector(getContext(), new ScaleGestureListener()); //设置手势缩放的监听
  2. private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
  3. private float mStartFocusX;
  4. private float mStartFocusY;
  5. private float mStartScale;
  6. private int mStartScrollX;
  7. private int mStartScrollY;
  8. @Override
  9. public boolean onScaleBegin(ScaleGestureDetector detector) {
  10. mStartFocusX = detector.getFocusX();
  11. mStartFocusY = detector.getFocusY();
  12. mStartScrollX = getScrollX();
  13. mStartScrollY = getScrollY();
  14. mStartScale = mViewScale;
  15. return true;
  16. }
  17. @Override
  18. public boolean onScale(ScaleGestureDetector detector) {
  19. final float oldViewScale = mViewScale;
  20. final float scaleFactor = detector.getScaleFactor();
  21. mViewScale *= scaleFactor;
  22. if (mViewScale < ZOOM_SCALES[0]) {
  23. mCurrentZoomScaleIndex = 0;
  24. mViewScale = ZOOM_SCALES[mCurrentZoomScaleIndex];
  25. } else if (mViewScale > ZOOM_SCALES[ZOOM_SCALES.length - 1]) {
  26. mCurrentZoomScaleIndex = ZOOM_SCALES.length - 1;
  27. mViewScale = ZOOM_SCALES[mCurrentZoomScaleIndex];
  28. } else {
  29. float minDist = Float.MAX_VALUE;
  30. int index = ZOOM_SCALES.length - 1;
  31. for (int i = 0; i < ZOOM_SCALES.length; i++) {
  32. float dist = Math.abs(mViewScale - ZOOM_SCALES[i]);
  33. if (dist < minDist) {
  34. minDist = dist;
  35. } else {
  36. index = i - 1;
  37. break;
  38. }
  39. }
  40. mCurrentZoomScaleIndex = index;
  41. }
  42. ActionEditorCanvasView.this.setScaleX(mViewScale);
  43. ActionEditorCanvasView.this.setScaleY(mViewScale);
  44. final float scaleDifference = mViewScale - mStartScale;
  45. final int scrollScaleX = (int) (scaleDifference * mStartFocusX);
  46. final int scrollScaleY = (int) (scaleDifference * mStartFocusY);
  47. final int scrollPanX = (int) (mStartFocusX - detector.getFocusX());
  48. final int scrollPanY = (int) (mStartFocusY - detector.getFocusY());
  49. scrollTo(mStartScrollX + scrollScaleX + scrollPanX,
  50. mStartScrollY + scrollScaleY + scrollPanY);
  51. return true;
  52. }
  53. }
四.核心类的分析:
1.CDrawable接口,只要实现这个接口的控件就可以绘制到自定义的画布中:

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

  1. public abstract class FabricView extends View {
  2. // painting objects and properties
  3. public ArrayList<CDrawable> mDrawableList = new ArrayList<>();
  4. private int mColor = Color.BLACK;
  5. // Canvas interaction modes
  6. private int mInteractionMode = DRAW_MODE;
  7. // background color of the library
  8. private int mBackgroundColor = Color.WHITE;
  9. // default style for the library
  10. private Paint.Style mStyle = Paint.Style.STROKE;
  11. // default stroke size for the library 默认点击的尺寸
  12. private float mSize = 5f;
  13. // flag indicating whether or not the background needs to be redrawn
  14. private boolean mRedrawBackground;
  15. // background mode for the library, default to blank
  16. private int mBackgroundMode = BACKGROUND_STYLE_BLANK;
  17. // Default Notebook left line color
  18. public static final int NOTEBOOK_LEFT_LINE_COLOR = Color.RED;
  19. // Flag indicating that we are waiting for a location for the text
  20. private boolean mTextExpectTouch;
  21. // Vars to decrease dirty area and increase performance
  22. private float lastTouchX, lastTouchY;
  23. private final RectF dirtyRect = new RectF();
  24. // keep track of path and paint being in use
  25. CPath currentPath;
  26. Paint currentPaint;
  27. /*********************************************************************************************/
  28. /************************************ FLAGS *******************************************/
  29. /*********************************************************************************************/
  30. // Default Background Styles 背景颜色
  31. public static final int BACKGROUND_STYLE_BLANK = 0;
  32. public static final int BACKGROUND_STYLE_NOTEBOOK_PAPER = 1;
  33. public static final int BACKGROUND_STYLE_GRAPH_PAPER = 2;
  34. // Interactive Modes
  35. public static final int DRAW_MODE = 0;
  36. public static final int SELECT_MODE = 1; // TODO Support Object Selection.
  37. public static final int ROTATE_MODE = 2; // TODO Support Object ROtation.
  38. public static final int LOCKED_MODE = 3;
  39. /*********************************************************************************************/
  40. /********************************** CONSTANTS *****************************************/
  41. /*********************************************************************************************/
  42. public static final int NOTEBOOK_LEFT_LINE_PADDING = 120;
  43. /*********************************************************************************************/
  44. /************************************ TO-DOs ******************************************/
  45. /*********************************************************************************************/
  46. private float mZoomLevel = 1.0f; //TODO Support Zoom 要去做支持放大缩小的功能
  47. private float mHorizontalOffset = 1, mVerticalOffset = 1; // TODO Support Offset and Viewport 支持可以偏移的功能
  48. public int mAutoscrollDistance = 100; // TODO Support Autoscroll
  49. /**
  50. * Default Constructor, sets sane values.
  51. *
  52. * @param context the activity that containts the view
  53. * @param attrs view attributes
  54. */
  55. public FabricView(Context context, AttributeSet attrs) {
  56. super(context, attrs);
  57. setFocusable(true);
  58. setFocusableInTouchMode(true);
  59. this.setBackgroundColor(mBackgroundColor);
  60. mTextExpectTouch = false;
  61. }
  62. /**
  63. * Called when there is the canvas is being re-drawn.
  64. */
  65. @Override
  66. protected void onDraw(Canvas canvas) {
  67. // check if background needs to be redrawn
  68. mBackgroundMode=BACKGROUND_STYLE_NOTEBOOK_PAPER;
  69. drawBackground(canvas, mBackgroundMode);
  70. // go through each item in the list and draw it
  71. for (int i = 0; i < mDrawableList.size(); i++) {
  72. try {
  73. mDrawableList.get(i).draw(canvas);
  74. }
  75. catch(Exception ex)
  76. {
  77. }
  78. }
  79. }
  80. /*********************************************************************************************/
  81. /******************************* Handling User Touch **********************************/
  82. /*********************************************************************************************/
  83. /**
  84. * Handles user touch event
  85. *
  86. * @param event the user's motion event
  87. * @return true, the event is consumed.
  88. */
  89. @Override
  90. public boolean onTouchEvent(MotionEvent event) {
  91. // delegate action to the correct method
  92. if (getInteractionMode() == DRAW_MODE) //绘画的模式
  93. return onTouchDrawMode(event);
  94. else if (getInteractionMode() == SELECT_MODE) //选择的模式
  95. return onTouchSelectMode(event);
  96. else if (getInteractionMode() == ROTATE_MODE)
  97. return onTouchRotateMode(event);
  98. // if none of the above are selected, delegate to locked mode
  99. else
  100. return onTouchLockedMode(event);
  101. }
  102. /**
  103. * Handles touch event if the mode is set to locked
  104. * @param event the event to handle
  105. * @return false, shouldn't do anything with it for now
  106. */
  107. private boolean onTouchLockedMode(MotionEvent event) {
  108. // return false since we don't want to do anything so far
  109. return true;
  110. }
  111. /**
  112. * Handles the touch input if the mode is set to rotate
  113. * @param event the touch event
  114. * @return the result of the action
  115. */
  116. private boolean onTouchRotateMode(MotionEvent event) {
  117. return false;
  118. }
  119. /**
  120. * Handles the touch input if the mode is set to draw
  121. * @param event the touch event
  122. * @return the result of the action
  123. */
  124. public boolean onTouchDrawMode(MotionEvent event)
  125. {
  126. // get location of touch
  127. float eventX = event.getX();
  128. float eventY = event.getY();
  129. Log.i("everb", "划线或的位置X信息:"+ event.getX()+"划线或的位置Y信息:"+eventY);
  130. // based on the users action, start drawing
  131. switch (event.getAction()) {
  132. case MotionEvent.ACTION_DOWN:
  133. // create new path and paint
  134. currentPath = new CPath();
  135. currentPaint = new Paint();
  136. currentPaint.setAntiAlias(true);
  137. currentPaint.setColor(mColor);
  138. currentPaint.setStyle(mStyle);
  139. currentPaint.setStrokeJoin(Paint.Join.ROUND);
  140. currentPaint.setStrokeWidth(mSize);
  141. currentPath.moveTo(eventX, eventY);
  142. currentPath.setPaint(currentPaint);
  143. // capture touched locations
  144. lastTouchX = eventX;
  145. lastTouchY = eventY;
  146. mDrawableList.add(currentPath);
  147. return true;
  148. case MotionEvent.ACTION_MOVE:
  149. case MotionEvent.ACTION_UP:
  150. currentPath.lineTo(eventX, eventY);
  151. // When the hardware tracks events faster than they are delivered, the
  152. // event will contain a history of those skipped points.
  153. int historySize = event.getHistorySize();
  154. for (int i = 0; i < historySize; i++) {
  155. float historicalX = event.getHistoricalX(i);
  156. float historicalY = event.getHistoricalY(i);
  157. if (historicalX < dirtyRect.left) {
  158. dirtyRect.left = historicalX;
  159. } else if (historicalX > dirtyRect.right) {
  160. dirtyRect.right = historicalX;
  161. }
  162. if (historicalY < dirtyRect.top) {
  163. dirtyRect.top = historicalY;
  164. } else if (historicalY > dirtyRect.bottom) {
  165. dirtyRect.bottom = historicalY;
  166. }
  167. currentPath.lineTo(historicalX, historicalY);
  168. }
  169. // After replaying history, connect the line to the touch point.
  170. currentPath.lineTo(eventX, eventY);
  171. cleanDirtyRegion(eventX, eventY);
  172. break;
  173. default:
  174. return false;
  175. }
  176. // Include some padding to ensure nothing is clipped
  177. invalidate(
  178. (int) (dirtyRect.left - 20),
  179. (int) (dirtyRect.top - 20),
  180. (int) (dirtyRect.right + 20),
  181. (int) (dirtyRect.bottom + 20));
  182. // register most recent touch locations
  183. lastTouchX = eventX;
  184. lastTouchY = eventY;
  185. return true;
  186. }
  187. /**
  188. * Handles the touch input if the mode is set to select
  189. * @param event the touch event
  190. */
  191. private boolean onTouchSelectMode(MotionEvent event) {
  192. // TODO Implement Method
  193. return false;
  194. }
  195. /*******************************************
  196. * Drawing Events
  197. ******************************************/
  198. /**
  199. * Draw the background on the canvas
  200. * @param canvas the canvas to draw on
  201. * @param backgroundMode one of BACKGROUND_STYLE_GRAPH_PAPER, BACKGROUND_STYLE_NOTEBOOK_PAPER, BACKGROUND_STYLE_BLANK
  202. */
  203. public void drawBackground(Canvas canvas, int backgroundMode) {
  204. canvas.drawColor(mBackgroundColor);
  205. if(backgroundMode != BACKGROUND_STYLE_BLANK) {
  206. Paint linePaint = new Paint();
  207. linePaint.setColor(Color.argb(50, 0, 0, 0));
  208. linePaint.setStyle(mStyle);
  209. linePaint.setStrokeJoin(Paint.Join.ROUND);
  210. linePaint.setStrokeWidth(mSize - 2f);
  211. switch (backgroundMode) {
  212. case BACKGROUND_STYLE_GRAPH_PAPER:
  213. drawGraphPaperBackground(canvas, linePaint);
  214. break;
  215. case BACKGROUND_STYLE_NOTEBOOK_PAPER:
  216. drawNotebookPaperBackground(canvas, linePaint);
  217. default:
  218. break;
  219. }
  220. }
  221. mRedrawBackground = false;
  222. }
  223. /**
  224. * Draws a graph paper background on the view
  225. * @param canvas the canvas to draw on
  226. * @param paint the paint to use
  227. */
  228. private void drawGraphPaperBackground(Canvas canvas, Paint paint) {
  229. int i = 0;
  230. boolean doneH = false, doneV = false;
  231. // while we still need to draw either H or V
  232. while (!(doneH && doneV)) {
  233. // check if there is more H lines to draw
  234. if (i < canvas.getHeight())
  235. canvas.drawLine(0, i, canvas.getWidth(), i, paint);
  236. else
  237. doneH = true;
  238. // check if there is more V lines to draw
  239. if (i < canvas.getWidth())
  240. canvas.drawLine(i, 0, i, canvas.getHeight(), paint);
  241. else
  242. doneV = true;
  243. // declare as done
  244. i += 75;
  245. }
  246. }
  247. /**
  248. * Draws a notebook paper background on the view
  249. * @param canvas the canvas to draw on
  250. * @param paint the paint to use
  251. */
  252. private void drawNotebookPaperBackground(Canvas canvas, Paint paint) {
  253. int i = 0;
  254. boolean doneV = false;
  255. // draw horizental lines
  256. while (!(doneV)) {
  257. if (i < canvas.getHeight())
  258. canvas.drawLine(0, i, canvas.getWidth(), i, paint);
  259. else
  260. doneV = true;
  261. i += 75;
  262. }
  263. // change line color
  264. paint.setColor(NOTEBOOK_LEFT_LINE_COLOR);
  265. // draw side line
  266. canvas.drawLine(NOTEBOOK_LEFT_LINE_PADDING, 0,
  267. NOTEBOOK_LEFT_LINE_PADDING, canvas.getHeight(), paint);
  268. }
  269. /**
  270. * Draw text on the screen
  271. * @param text the text to draw
  272. * @param x the x location of the text
  273. * @param y the y location of the text
  274. * @param p the paint to use
  275. */
  276. public void drawText(String text, int x, int y, Paint p) {
  277. mDrawableList.add(new CText(text, x, y, p));
  278. invalidate();
  279. }
  280. /**
  281. * Capture Text from the keyboard and draw it on the screen
  282. * //TODO Implement the method
  283. */
  284. private void drawTextFromKeyboard() {
  285. Toast.makeText(getContext(), "Touch where you want the text to be", Toast.LENGTH_LONG).show();
  286. //TODO
  287. mTextExpectTouch = true;
  288. }
  289. /**
  290. * Retrieve the region needing to be redrawn
  291. * @param eventX The current x location of the touch
  292. * @param eventY the current y location of the touch
  293. */
  294. private void cleanDirtyRegion(float eventX, float eventY) {
  295. // figure out the sides of the dirty region
  296. dirtyRect.left = Math.min(lastTouchX, eventX);
  297. dirtyRect.right = Math.max(lastTouchX, eventX);
  298. dirtyRect.top = Math.min(lastTouchY, eventY);
  299. dirtyRect.bottom = Math.max(lastTouchY, eventY);
  300. }
  301. /**
  302. * Clean the canvas, remove everything drawn on the canvas.
  303. */
  304. public void cleanPage() {
  305. // remove everything from the list
  306. while (!(mDrawableList.isEmpty())) {
  307. mDrawableList.remove(0);
  308. }
  309. // request to redraw the canvas
  310. invalidate();
  311. }
  312. /**
  313. * Draws an image on the canvas
  314. *
  315. * @param x location of the image
  316. * @param y location of the image
  317. * @param width the width of the image
  318. * @param height the height of the image
  319. * @param pic the image itself
  320. */
  321. public void drawImage(int x, int y, int width, int height, Bitmap pic) {
  322. CBitmap bitmap = new CBitmap(pic, x, y);
  323. bitmap.setWidth(width);
  324. bitmap.setHeight(height);
  325. mDrawableList.add(bitmap);
  326. invalidate();
  327. }
  328. /*******************************************
  329. * Getters and Setters
  330. ******************************************/
  331. /**
  332. * Gets what has been drawn on the canvas so far as a bitmap
  333. * @return Bitmap of the canvas.
  334. */
  335. public Bitmap getCanvasBitmap()
  336. {
  337. // build drawing cache of the canvas, use it to create a new bitmap, then destroy it.
  338. buildDrawingCache();
  339. Bitmap mCanvasBitmap = Bitmap.createBitmap(getDrawingCache());
  340. destroyDrawingCache();
  341. // return the created bitmap.
  342. return mCanvasBitmap;
  343. }
  344. public int getColor() {
  345. return mColor;
  346. }
  347. public void setColor(int mColor) {
  348. this.mColor = mColor;
  349. }
  350. public int getBackgroundColor() {
  351. return mBackgroundColor;
  352. }
  353. public int getBackgroundMode() {
  354. return mBackgroundMode;
  355. }
  356. public void setBackgroundMode(int mBackgroundMode) {
  357. this.mBackgroundMode = mBackgroundMode;
  358. invalidate();
  359. }
  360. public void setBackgroundColor(int mBackgroundColor) {
  361. this.mBackgroundColor = mBackgroundColor;
  362. }
  363. public Paint.Style getStyle() {
  364. return mStyle;
  365. }
  366. public void setStyle(Paint.Style mStyle) {
  367. this.mStyle = mStyle;
  368. }
  369. public float getSize() {
  370. return mSize;
  371. }
  372. public void setSize(float mSize) {
  373. this.mSize = mSize;
  374. }
  375. public int getInteractionMode() {
  376. return mInteractionMode;
  377. }
  378. public void setInteractionMode(int interactionMode) {
  379. // if the value passed is not any of the flags, set the library to locked mode
  380. if (interactionMode > LOCKED_MODE)
  381. interactionMode = LOCKED_MODE;
  382. else if (interactionMode < DRAW_MODE)
  383. interactionMode = LOCKED_MODE;
  384. this.mInteractionMode = interactionMode;
  385. }
  386. }

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

  1. package com.lejurobot.aelos.aelosmini.view;
  2. import android.content.Context;
  3. import android.graphics.Point;
  4. import android.util.AttributeSet;
  5. import android.util.Log;
  6. import android.view.GestureDetector;
  7. import android.view.MotionEvent;
  8. import android.view.ScaleGestureDetector;
  9. import android.widget.Toast;
  10. import com.lejurobot.aelos.aelosmini.activities.ActionEditorActivity;
  11. import com.lejurobot.aelos.aelosmini.view.FabricView.CDrawable;
  12. import com.lejurobot.aelos.aelosmini.view.FabricView.FabricView;
  13. /**
  14. * @author wangyao
  15. * @package com.example.administrator.myapplication
  16. * @date 2017/6/27 14:05
  17. * @describe 实现一个画布的放置控件、缩放画布、控件之间的连线、画布的手势识别、缩放后手势滑动的识别
  18. * @project
  19. */
  20. public class ActionEditorCanvasView extends FabricView {
  21. private Context mContext;
  22. // Scale and zoom in/out factor.
  23. private static final int INIT_ZOOM_SCALES_INDEX = 0;
  24. private int mCurrentZoomScaleIndex = INIT_ZOOM_SCALES_INDEX;
  25. private static final float[] ZOOM_SCALES = new float[]{1.0f, 1.25f, 1.5f, 1.75f, 2.0f};
  26. private float mViewScale = ZOOM_SCALES[INIT_ZOOM_SCALES_INDEX];
  27. protected boolean mScrollable = true;
  28. private ScaleGestureDetector mScaleGestureDetector; //缩放手势
  29. private GestureDetector mTapGestureDetector; //手势监听类
  30. private int mPanningPointerId = MotionEvent.INVALID_POINTER_ID;
  31. private Point mPanningStart = new Point();
  32. private int mOriginalScrollX;
  33. private int mOriginalScrollY;
  34. private float mOffSetViewScroll;
  35. // Default desired width of the view in pixels.
  36. private static final int DESIRED_WIDTH = 2048;
  37. // Default desired height of the view in pixels.
  38. private static final int DESIRED_HEIGHT = 2048;
  39. // Interactive Modes
  40. public static final int DRAW_MODE = 0; //可以绘制线段的模式
  41. public static final int SELECT_MODE = 1; // TODO Support Object Selection.
  42. public static final int ROTATE_MODE = 2; // TODO Support Object ROtation.
  43. public static final int LOCKED_MODE = 3; //空模式
  44. // Default Background Styles 背景颜色
  45. public static final int BACKGROUND_STYLE_BLANK = 0;
  46. public static final int BACKGROUND_STYLE_NOTEBOOK_PAPER = 1;
  47. public static final int BACKGROUND_STYLE_GRAPH_PAPER = 2;
  48. public ActionEditorCanvasView(Context context, AttributeSet attrs) {
  49. super(context, attrs);
  50. this.mContext=context;
  51. }
  52. /**
  53. * The method onFinishInflate() will be called after all children have been added.
  54. * 这个方法是所有的子view被添加之后调用
  55. */
  56. @Override
  57. public void onFinishInflate() {
  58. super.onFinishInflate();
  59. // Setting the child view's pivot point to (0,0) means scaling leaves top-left corner in
  60. // place means there is no need to adjust view translation.
  61. this.setPivotX(0);
  62. this.setPivotY(0);
  63. setWillNotDraw(false);
  64. setHorizontalScrollBarEnabled(mScrollable); //水平滑动滚动条的设置
  65. setVerticalScrollBarEnabled(mScrollable); //竖直滑动滚动条的设置
  66. setBackgroundMode(BACKGROUND_STYLE_GRAPH_PAPER);
  67. mScaleGestureDetector = new ScaleGestureDetector(getContext(), new ScaleGestureListener()); //设置手势缩放的监听
  68. mTapGestureDetector = new GestureDetector(getContext(), new TapGestureListener()); //设置手势的监听
  69. }
  70. public void setBackgroundMode(int mBackgroundMode) {
  71. super.setBackgroundMode(mBackgroundMode);
  72. }
  73. /**
  74. * 设置画布所处在的模式,可以添加并设置其他的模式
  75. */
  76. public void setInteractionMode(int interactionMode) {
  77. if (interactionMode == DRAW_MODE) {
  78. super.setInteractionMode(DRAW_MODE);
  79. } else if (interactionMode == LOCKED_MODE) {
  80. super.setInteractionMode(LOCKED_MODE);
  81. }else if (interactionMode==SELECT_MODE){
  82. super.setInteractionMode(SELECT_MODE);
  83. }
  84. }
  85. /**
  86. * 增加画布里面的控件或其他实现了CDrawable接口的类
  87. */
  88. public boolean addCanvasDrawable(CDrawable cDrawable) {
  89. super.mDrawableList.add(cDrawable);
  90. return true;
  91. }
  92. /**
  93. * 清除画布里面的控件
  94. */
  95. public void cleanPager() {
  96. super.cleanPage();
  97. }
  98. /**
  99. * 重置画布的大小尺寸
  100. */
  101. public void resetView() {
  102. // Reset scrolling state.
  103. mPanningPointerId = MotionEvent.INVALID_POINTER_ID;
  104. mPanningStart.set(0, 0);
  105. mOriginalScrollX = 0;
  106. mOriginalScrollY = 0;
  107. updateScaleStep(INIT_ZOOM_SCALES_INDEX);
  108. scrollTo((int) this.getX(), (int) this.getY());
  109. }
  110. @Override
  111. public boolean onTouchDrawMode(MotionEvent event) {
  112. event.offsetLocation(getScrollX(), getScrollY()); //缩放后偏移的距离,保证缩放后触点跟缩放前对应
  113. return super.onTouchDrawMode(event);
  114. }
  115. @Override
  116. public boolean onTouchSelectMode(MotionEvent event) {
  117. return super.onTouchSelectMode(event);
  118. }
  119. @Override
  120. public boolean onTouchEvent(MotionEvent event) {
  121. mScaleGestureDetector.onTouchEvent(event);
  122. mTapGestureDetector.onTouchEvent(event);
  123. // TODO: 2017/6/29 可根据触发事件做到动作控件的拖动
  124. return super.onTouchEvent(event);
  125. }
  126. /**
  127. * 画布实现缩小的方法
  128. *
  129. * @return
  130. */
  131. public boolean zoomOut() {
  132. // if (mScrollable && mCurrentZoomScaleIndex > 0) {
  133. if (mCurrentZoomScaleIndex > 0) {
  134. updateScaleStep(mCurrentZoomScaleIndex - 1);
  135. return true;
  136. }
  137. return false;
  138. }
  139. /**
  140. * 画布实现放大的方法
  141. *
  142. * @return
  143. */
  144. public boolean zoomIn() {
  145. if (mCurrentZoomScaleIndex < ZOOM_SCALES.length - 1) {
  146. updateScaleStep(mCurrentZoomScaleIndex + 1);
  147. return true;
  148. }
  149. return false;
  150. }
  151. /**
  152. * 缩放的具体实现
  153. *
  154. * @param newScaleIndex
  155. */
  156. private void updateScaleStep(int newScaleIndex) {
  157. if (newScaleIndex != mCurrentZoomScaleIndex) {
  158. final float oldViewScale = mViewScale;
  159. mCurrentZoomScaleIndex = newScaleIndex;
  160. mViewScale = ZOOM_SCALES[mCurrentZoomScaleIndex];
  161. final float scaleDifference = mViewScale - oldViewScale;
  162. scrollBy((int) (scaleDifference * getMeasuredWidth() / 2),
  163. (int) (scaleDifference * getMeasuredHeight() / 2));
  164. this.setScaleX(mViewScale);
  165. this.setScaleY(mViewScale);
  166. this.requestLayout();
  167. }
  168. }
  169. @Override
  170. public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  171. setMeasuredDimension(
  172. getMeasuredSize(widthMeasureSpec, DESIRED_WIDTH),
  173. getMeasuredSize(heightMeasureSpec, DESIRED_HEIGHT));
  174. }
  175. private static int getMeasuredSize(int measureSpec, int desiredSize) {
  176. int mode = MeasureSpec.getMode(measureSpec);
  177. int size = MeasureSpec.getSize(measureSpec);
  178. if (mode == MeasureSpec.EXACTLY) {
  179. return size;
  180. } else if (mode == MeasureSpec.AT_MOST) {
  181. return Math.min(size, desiredSize);
  182. } else {
  183. return desiredSize;
  184. }
  185. }
  186. private class TapGestureListener implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {
  187. @Override
  188. public boolean onDown(MotionEvent motionEvent) {
  189. return false;
  190. }
  191. @Override
  192. public void onShowPress(MotionEvent motionEvent) {
  193. }
  194. @Override
  195. public boolean onSingleTapUp(MotionEvent motionEvent) {
  196. return false;
  197. }
  198. @Override
  199. public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
  200. return false;
  201. }
  202. @Override
  203. public void onLongPress(MotionEvent motionEvent) {
  204. // TODO: 2017/6/29 长按可以设置模式为连线
  205. Toast.makeText(mContext,"实现了长按手势",Toast.LENGTH_SHORT).show();
  206. }
  207. @Override
  208. public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
  209. return false;
  210. }
  211. @Override
  212. public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
  213. return false;
  214. }
  215. @Override
  216. public boolean onDoubleTap(MotionEvent motionEvent) {
  217. return false;
  218. }
  219. @Override
  220. public boolean onDoubleTapEvent(MotionEvent motionEvent) {
  221. return false;
  222. }
  223. }
  224. private class ScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
  225. // Focus point at the start of the pinch gesture. This is used for computing proper scroll
  226. // offsets during scaling, as well as for simultaneous panning.
  227. private float mStartFocusX;
  228. private float mStartFocusY;
  229. // View scale at the beginning of the gesture. This is used for computing proper scroll
  230. // offsets during scaling.
  231. private float mStartScale;
  232. // View scroll offsets at the beginning of the gesture. These provide the reference point
  233. // for adjusting scroll in response to scaling and panning.
  234. private int mStartScrollX;
  235. private int mStartScrollY;
  236. @Override
  237. public boolean onScaleBegin(ScaleGestureDetector detector) {
  238. mStartFocusX = detector.getFocusX();
  239. mStartFocusY = detector.getFocusY();
  240. mStartScrollX = getScrollX();
  241. mStartScrollY = getScrollY();
  242. mStartScale = mViewScale;
  243. return true;
  244. }
  245. @Override
  246. public boolean onScale(ScaleGestureDetector detector) {
  247. final float oldViewScale = mViewScale;
  248. final float scaleFactor = detector.getScaleFactor();
  249. mViewScale *= scaleFactor;
  250. if (mViewScale < ZOOM_SCALES[0]) {
  251. mCurrentZoomScaleIndex = 0;
  252. mViewScale = ZOOM_SCALES[mCurrentZoomScaleIndex];
  253. } else if (mViewScale > ZOOM_SCALES[ZOOM_SCALES.length - 1]) {
  254. mCurrentZoomScaleIndex = ZOOM_SCALES.length - 1;
  255. mViewScale = ZOOM_SCALES[mCurrentZoomScaleIndex];
  256. } else {
  257. // find nearest zoom scale
  258. float minDist = Float.MAX_VALUE;
  259. // If we reach the end the last one was the closest
  260. int index = ZOOM_SCALES.length - 1;
  261. for (int i = 0; i < ZOOM_SCALES.length; i++) {
  262. float dist = Math.abs(mViewScale - ZOOM_SCALES[i]);
  263. if (dist < minDist) {
  264. minDist = dist;
  265. } else {
  266. // When it starts increasing again we've found the closest
  267. index = i - 1;
  268. break;
  269. }
  270. }
  271. mCurrentZoomScaleIndex = index;
  272. }
  273. /* if (shouldDrawGrid()) {
  274. mGridRenderer.updateGridBitmap(mViewScale);
  275. }*/
  276. ActionEditorCanvasView.this.setScaleX(mViewScale);
  277. ActionEditorCanvasView.this.setScaleY(mViewScale);
  278. // Compute scroll offsets based on difference between original and new scaling factor
  279. // and the focus point where the gesture started. This makes sure that the scroll offset
  280. // is adjusted to keep the focus point in place on the screen unless there is also a
  281. // focus point shift (see next scroll component below).
  282. final float scaleDifference = mViewScale - mStartScale;
  283. final int scrollScaleX = (int) (scaleDifference * mStartFocusX);
  284. final int scrollScaleY = (int) (scaleDifference * mStartFocusY);
  285. // Compute scroll offset based on shift of the focus point. This makes sure the view
  286. // pans along with the focus.
  287. final int scrollPanX = (int) (mStartFocusX - detector.getFocusX());
  288. final int scrollPanY = (int) (mStartFocusY - detector.getFocusY());
  289. // Apply the computed scroll components for scale and panning relative to the scroll
  290. // coordinates at the beginning of the gesture.
  291. scrollTo(mStartScrollX + scrollScaleX + scrollPanX,
  292. mStartScrollY + scrollScaleY + scrollPanY);
  293. return true;
  294. }
  295. }
  296. }

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

  项目地址为: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.


声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/598669
推荐阅读
相关标签
  

闽ICP备14008679号