La vista de dibujo de Android es muy lenta

Tengo este código de una respuesta en una de las preguntas que estaba preguntando cómo dibujar en Android, pero luego al usarlo y probarlo en mi aplicación, descubrí que no es eficiente al dibujar grandes cosas o muchas rutas. El problema proviene del código dentro de onDraw porque cada vez que invalidate() se llama onDraw se llama que contiene un bucle que dibuja todos los paths nuevo al canvas , y añadiendo más rutas a él, se pone muy muy lento.

Aquí está la Clase:

 public class DrawingView extends View implements OnTouchListener { private Canvas m_Canvas; private Path m_Path; private Paint m_Paint; ArrayList<Pair<Path, Paint>> paths = new ArrayList<Pair<Path, Paint>>(); ArrayList<Pair<Path, Paint>> undonePaths = new ArrayList<Pair<Path, Paint>>(); 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<Path, Paint>(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<Path, Paint> 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<Path, Paint>(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<Path, Paint>(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 >= TOUCH_TOLERANCE || dy >= 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<Path, Paint>(m_Path, newPaint)); } public void onClickUndo() { if (!paths.isEmpty()) {//paths.size() > 0) { undonePaths.add(paths.remove(paths.size() - 1)); undo = true; invalidate(); } } public void onClickRedo() { if (!undonePaths.isEmpty()){//undonePaths.size() > 0) { paths.add(undonePaths.remove(undonePaths.size() - 1)); undo = true; invalidate(); } }} 

Pero busqué en Internet de nuevo para encontrar una mejor manera de dibujar, así que encontré lo siguiente:

1 Agregue lo siguiente al constructor:

 mBitmapPaint = new Paint(Paint.DITHER_FLAG); 

2 Anula onSizeChanged con el siguiente código:

 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); } 

3 poner esto en onDraw:

 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); } 

Este enfoque funciona y no ralentiza la vista, pero el problema con este enfoque es que no puedo tener deshacer y rehacer funcionalidades.

He intentado muchas cosas para hacer el deshacer y rehacer con el segundo enfoque, pero no podía hacerlo. Así que lo que estoy pidiendo aquí es una de tres cosas: 1. Una manera de hacer deshacer y rehacer con el segundo enfoque 2. Otro enfoque que hace posible hacer deshacer y rehacer 3. Una clase totalmente nueva que tiene todo lo ya hecho , Como una biblioteca de código abierto o algo así.

Por favor ayuda si puedes. Gracias

EDITAR 1

OK, por lo que limitado a esto y luego no podía hacer nada más, he estado tratando de más de 8 horas ahora. Funciona hasta deshacer (puede deshacer tantas rutas como desee), y al dibujar de nuevo todos los caminos restantes desaparecen, no sé qué hace que haga eso.

 @Override protected void onDraw(Canvas canvas) { if (mBitmap != null) canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint); if (!paths.isEmpty() && !undo) canvas.drawPath(paths.get(paths.size() - 1).first, paths.get(paths.size() - 1).second); if (undo) { setBackground(mBackground); for (Pair<Path, Paint> p : paths) canvas.drawPath(p.first, p.second); mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_4444); m_Canvas = new Canvas(mBitmap); undo = false; } } 

Así que básicamente lo que hice fue usar el primer enfoque al principio (antes de deshacer se llama), entonces si se hace clic en undo , se undo en true y se ejecuta el código bajo if (undo) que es realmente el primer enfoque (calculando todas las rutas De nuevo), entonces dibujo el resultado de calcular todos los caminos de nuevo en mBitmap así que cada vez que el onDraw se llama de nuevo se dibuja en la parte superior de eso, pero esa parte todavía necesita trabajo, espero que alguien puede ayudar con esa parte.

La forma de manejar este caso es tener un mapa de bits que tenga el tamaño de la vista. En eventos táctiles, dibuje en el lienzo del mapa de bits. En onDraw, basta con dibujar el mapa de bits en el lienzo en 0,0. Para deshacer / rehacer ,. Puede borrar el mapa de bits y volver a dibujar todas las rutas. Se tarda un poco más, pero sólo ocurre una vez por deshacer / rehacer. Si los usuarios suelen hacer un deshacer / rehacer. Puede optimizar teniendo otro mapa de bits para sólo un paso atrás.

Ok, aquí es lo que me ocurrió al final, el problema era que dibujar las rutas de acceso al lienzo antes de crear el mapa de bits en deshacer, que conducen a la pérdida de las rutas onDraw después de deshacer:

 @Override protected void onDraw(Canvas canvas) { if (mBitmap != null) canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint); if (!paths.isEmpty()) { canvas.drawPath(paths.get(paths.size() - 1).first, paths.get(paths.size() - 1).second); } } public void onClickUndo() { if (paths.size() >= 2) { undonePaths.add(paths.remove(paths.size() - 2)); mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); m_Canvas = new Canvas(mBitmap); for (Pair<Path, Paint> p : paths) m_Canvas.drawPath(p.first, p.second); invalidate(); } } public void onClickRedo() { if (undonePaths.size() >= 2){ paths.add(undonePaths.remove(undonePaths.size() - 2)); mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); m_Canvas = new Canvas(mBitmap); for (Pair<Path, Paint> p : paths) m_Canvas.drawPath(p.first, p.second); invalidate(); } } 

Dibujar todos los caminos una y otra vez sigue ahí pero no en onDraw() , lo que mejora el rendimiento del dibujo bastante. Pero el usuario puede experimentar un poco de retraso en onClickUndo() y onClickRedo() si ha dibujado un montón de rutas porque allí donde las rutas se están dibujando de nuevo desde cero, pero sólo una vez por clic.

No estoy seguro de si esta es la mejor manera para deshacer y rehacer. Sin embargo, el siguiente trabajo en mi dispositivo (Samsung galaxia s3). El Dibujo parece ser rápido y el deshacer funciona bien. Creo que lo siguiente se puede modificar para mejorar aún más el rendimiento.

 public class MainActivity extends Activity { MyView mv; LinearLayout ll; private ArrayList<Path> undonePaths = new ArrayList<Path>(); private ArrayList<Path> paths = new ArrayList<Path>(); Button b; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mv= new MyView(this); mv.setDrawingCacheEnabled(true); ll= (LinearLayout) findViewById(R.id.ll); ll.addView(mv); b= (Button) findViewById(R.id.button1); b.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub if (paths.size() > 0) { undonePaths.add(paths .remove(paths.size()-2)); mv.invalidate(); } } }); } public class MyView extends View implements OnTouchListener { private Canvas mCanvas; private Path mPath; private Paint mPaint; // private ArrayList<Path> undonePaths = new ArrayList<Path>(); private float xleft, xright, xtop, xbottom; public MyView(Context context) { super(context); setFocusable(true); setFocusableInTouchMode(true); this.setOnTouchListener(this); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeJoin(Paint.Join.ROUND); mPaint.setStrokeCap(Paint.Cap.ROUND); mPaint.setStrokeWidth(6); mCanvas = new Canvas(); mPath = new Path(); paths.add(mPath); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); } @Override protected void onDraw(Canvas canvas) { for (Path p : paths) { canvas.drawPath(p, mPaint); } } private float mX, mY; private static final float TOUCH_TOLERANCE = 0; private void touch_start(float x, float y) { mPath.reset(); mPath.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 >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); mX = x; mY = y; } } private void touch_up() { mPath.lineTo(mX, mY); // commit the path to our offscreen mCanvas.drawPath(mPath, mPaint); // kill this so we don't double draw mPath = new Path(); paths.add(mPath); } @Override 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; } } } 

Activity_main.xml

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <LinearLayout android:id="@+id/ll" android:layout_width="match_parent" android:layout_height="fill_parent" android:layout_weight="1" android:orientation="vertical" > </LinearLayout> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Undo" /> </LinearLayout> 
  • Mi aplicación se redujo significativamente en Android 4.0
  • ¿Cuáles son las ventajas / desventajas de pasar argumentos al constructor AsyncTask?
  • GridView carga lento para los recursos de imagen grande
  • Reserva de almacenamiento interno del dispositivo Android para futuros registros críticos de aplicaciones
  • Ionic: transiciones lentas en la aplicación android instalada
  • Cómo agrupar varias vistas en un ConstraintLayout
  • Android TextureView / Dibujo / Rendimiento de la pintura
  • Anidado recylerview lag mientras que los primeros rollos y luego se desplaza suavemente?
  • Gradle construye increíblemente lento
  • Descomprimir archivos de una manera más rápida que usar java.util.zip en Android
  • Android, programación orientada a objetos vs diseñar para el funcionamiento
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.