Note that there are some explanatory texts on larger screens.

plurals
  1. POAndroid Drawing View is very slow
    primarykey
    data
    text
    <p>I got this code from a answer in one of the questions that was asking how to draw in Android, but then when using it and testing it in my app, I found out that it's not efficient when drawing big things or many paths. The problem comes from the code inside <code>onDraw</code> because each time <code>invalidate()</code> is called <code>onDraw</code> is called which contains a loop that draws all <code>paths</code> again to the <code>canvas</code>, and by adding more paths to it, it gets very very slow.</p> <p>Here is the Class:</p> <pre><code>public class DrawingView extends View implements OnTouchListener { private Canvas m_Canvas; private Path m_Path; private Paint m_Paint; ArrayList&lt;Pair&lt;Path, Paint&gt;&gt; paths = new ArrayList&lt;Pair&lt;Path, Paint&gt;&gt;(); ArrayList&lt;Pair&lt;Path, Paint&gt;&gt; undonePaths = new ArrayList&lt;Pair&lt;Path, Paint&gt;&gt;(); private float mX, mY; private static final float TOUCH_TOLERANCE = 4; public static boolean isEraserActive = false; private int color = Color.BLACK; private int stroke = 6; public DrawingView(Context context, AttributeSet attr) { super(context); setFocusable(true); setFocusableInTouchMode(true); setBackgroundColor(Color.WHITE); this.setOnTouchListener(this); onCanvasInitialization(); } public void onCanvasInitialization() { m_Paint = new Paint(); m_Paint.setAntiAlias(true); m_Paint.setDither(true); m_Paint.setColor(Color.parseColor("#000000")); m_Paint.setStyle(Paint.Style.STROKE); m_Paint.setStrokeJoin(Paint.Join.ROUND); m_Paint.setStrokeCap(Paint.Cap.ROUND); m_Paint.setStrokeWidth(2); m_Canvas = new Canvas(); m_Path = new Path(); Paint newPaint = new Paint(m_Paint); paths.add(new Pair&lt;Path, Paint&gt;(m_Path, newPaint)); } @Override public void setBackground(Drawable background) { mBackground = background; super.setBackground(background); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); } public boolean onTouch(View arg0, MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: touch_start(x, y); invalidate(); break; case MotionEvent.ACTION_MOVE: touch_move(x, y); invalidate(); break; case MotionEvent.ACTION_UP: touch_up(); invalidate(); break; } return true; } @Override protected void onDraw(Canvas canvas) { for (Pair&lt;Path, Paint&gt; p : paths) { canvas.drawPath(p.first, p.second); } } private void touch_start(float x, float y) { if (isEraserActive) { m_Paint.setColor(Color.WHITE); m_Paint.setStrokeWidth(50); Paint newPaint = new Paint(m_Paint); // Clones the mPaint object paths.add(new Pair&lt;Path, Paint&gt;(m_Path, newPaint)); } else { m_Paint.setColor(color); m_Paint.setStrokeWidth(stroke); Paint newPaint = new Paint(m_Paint); // Clones the mPaint object paths.add(new Pair&lt;Path, Paint&gt;(m_Path, newPaint)); } m_Path.reset(); m_Path.moveTo(x, y); mX = x; mY = y; } private void touch_move(float x, float y) { float dx = Math.abs(x - mX); float dy = Math.abs(y - mY); if (dx &gt;= TOUCH_TOLERANCE || dy &gt;= TOUCH_TOLERANCE) { m_Path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); mX = x; mY = y; } } private void touch_up() { m_Path.lineTo(mX, mY); // commit the path to our offscreen m_Canvas.drawPath(m_Path, m_Paint); // kill this so we don't double draw m_Path = new Path(); Paint newPaint = new Paint(m_Paint); // Clones the mPaint object paths.add(new Pair&lt;Path, Paint&gt;(m_Path, newPaint)); } public void onClickUndo() { if (!paths.isEmpty()) {//paths.size() &gt; 0) { undonePaths.add(paths.remove(paths.size() - 1)); undo = true; invalidate(); } } public void onClickRedo() { if (!undonePaths.isEmpty()){//undonePaths.size() &gt; 0) { paths.add(undonePaths.remove(undonePaths.size() - 1)); undo = true; invalidate(); } }} </code></pre> <p>But I searched on the internet again to find a better way for drawing, so I found the following:</p> <p>1 Add the following to the constructor:</p> <pre><code>mBitmapPaint = new Paint(Paint.DITHER_FLAG); </code></pre> <p>2 Override onSizeChanged with the following code:</p> <pre><code>protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_4444); m_Canvas = new Canvas(mBitmap); } </code></pre> <p>3 put this in onDraw:</p> <pre><code>protected void onDraw(Canvas canvas) { canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint); if (!paths.isEmpty()) canvas.drawPath(paths.get(paths.size() - 1).first, paths.get(paths.size() - 1).second); } </code></pre> <p>This approach works and it doesn't slow down the view, but the problem with this approach is that I can't have undo and redo functionalities.</p> <p>I tried many many things to do the undo and redo with the second approach, but I couldn't do it. So what I'm asking here is one of three things: 1. A way to do undo and redo with the second approach 2. Another approach that makes it possible to do undo and redo 3. A whole new class that has everything already done, like an open source library or something.</p> <p>Please help if you can. Thanks</p> <p><strong>EDIT 1</strong></p> <p>OK, so I limited it down to this and then I couldn't do anything more, I have been trying for over 8 hours now. It works up until undo (you can undo as many paths as you want), then when drawing again all remaining paths disappear, I don't know what makes it do that.</p> <pre><code>@Override protected void onDraw(Canvas canvas) { if (mBitmap != null) canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint); if (!paths.isEmpty() &amp;&amp; !undo) canvas.drawPath(paths.get(paths.size() - 1).first, paths.get(paths.size() - 1).second); if (undo) { setBackground(mBackground); for (Pair&lt;Path, Paint&gt; p : paths) canvas.drawPath(p.first, p.second); mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_4444); m_Canvas = new Canvas(mBitmap); undo = false; } } </code></pre> <p>so basically what I did is use the first approach at first (before undo is called), then if undo is clicked, <code>undo</code> is set to <code>true</code> and the code under <code>if (undo)</code> is executed which is actually the first approach (calculating all paths again), then I draw the result of calculating all paths again into <code>mBitmap</code> so whenever the <code>onDraw</code> is called again it draws on top of that, but that part is still needs working, I hope someone can help with that part.</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload