When it comes to drawing to canvas on android one very big performance boost can be gained by using a buffered bitmap image. The idea is to draw lines to a bitmap. And then draw the bitmap to the screen.
This approach avoids unnecessarily redrawing elements that has been drawn already multiple times.
1) Create a bitmap in your view.
1 2 3 4 5 6 7 8 9 | public View(Activity activity) { super(activity); DisplayMetrics metrics = new DisplayMetrics(); activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); bufferedImage = Bitmap.createBitmap(metrics.widthPixels, metrics.heightPixels, Bitmap.Config.ARGB_8888); . . . } |
2) Remember all touch events.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @Override public boolean onTouch(View view, MotionEvent motionEvent) { int action = motionEvent.getAction(); Point point = new Point(motionEvent.getX(), motionEvent.getY()); switch (action) { case MotionEvent.ACTION_DOWN: // touch down linePathManager.startLine(point); break; case MotionEvent.ACTION_MOVE: // touch drag linePathManager.addPoint(point); break; case MotionEvent.ACTION_UP: // stop touching linePathManager.endLine(); break; } invalidate(); return super.onTouchEvent(motionEvent); } |
3) Process all touch events. (Draw lines or something)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | private Path path = new Path(); public void startLine(@NotNull Point point) { path.moveTo(point.x, point.y); } public void add(@NotNull Point p) { path.lineTo(p.x, p.y); path.moveTo(p.x, p.y); } public void close() { path.close(); } |
4) Draw to buffered bitmap image.
lineManager.draw():
1 2 3 4 5 6 | private Canvas canvas = new Canvas(); public void draw(Bitmap bitmap, Paint paint) { canvas.setBitmap(bitmap); canvas.drawPath(path, paint); } |
5) And finally draw the buffered bitmap image.
1 2 3 4 5 6 | @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); linePathManager.draw(bufferedImage, paint); canvas.drawBitmap(bufferedImage, 0, 0, paint); } |
Bonus: invalidate canvas only around finger area and only every 90 milliseconds.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | @Override public boolean onTouch(View view, MotionEvent motionEvent) { int action = motionEvent.getAction(); Point point = new Point(motionEvent.getX(), motionEvent.getY()); switch (action) { case MotionEvent.ACTION_DOWN: // touch down linePathManager.startLine(point); break; case MotionEvent.ACTION_MOVE: // touch drag linePathManager.addPoint(point); break; case MotionEvent.ACTION_UP: linePathManager.endLine(); break; } // invalidate only finger area if (Calendar.getInstance().getTimeInMillis() - startTime > 90) { int rectangleSize = (int) (100 * metric.density); invalidate((int) (point.x - rectangleSize / 1.5), (int) (point.y - rectangleSize / 1.5), (int) point.x + rectangleSize, (int) point.y + rectangleSize); startTime = Calendar.getInstance().getTimeInMillis(); } return true; } |
See also
Performance in 2D Canvas App / Game on Android and Traceview War Story
March 28, 2015 at 5:44 am
Hello , the drawn path is not smoothly, why? My paint is :
this.mPaint = new Paint();
this.mPaint.setAntiAlias(true);
this.mPaint.setFilterBitmap(true);
this.mPaint.setDither(true);
this.mPaint.setColor(Color.RED);
this.mPaint.setStyle(Paint.Style.STROKE);
this.mPaint.setStrokeJoin(Paint.Join.ROUND);
this.mPaint.setStrokeCap(Paint.Cap.ROUND);
this.mPaint.setPathEffect(new CornerPathEffect(10) );
this.mPaint.setStrokeWidth(12);
October 2, 2015 at 5:17 pm
Invalidate with a rect behaves the same as Invalidate without a rect, causing the ondraw method to be very slow as it redraws the entire view.