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:
- Obtener cuenta con cursor.getCount () o para ejecutar un rawQuery con un COUNT en una cláusula SQL?
- Miles de insertos crudos de ORMLite que tardan varios minutos en Android
- ¿Cómo mejorar el rendimiento de la aplicación con un mapa de Google en el construido con sencha touch y phoneGap?
- Android y OOP - Variables globales vs heredados Getters en fragmentos
- ¿Cuál es la mejor solución para un salto de barra de progreso indeterminado en ICS?
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.
- Android Performance XML Drawable vs CSS3 vs Imágenes
- Android Ver animación - bajo rendimiento en pantallas grandes
- La aplicación de Android comienza muy lentamente en el primer lanzamiento, pero normalmente en los próximos lanzamientos
- Cargando datos de forma eficaz en RecycleView
- ¿Qué significa realmente el estado del hilo de Java?
- La creación de clase Pojo desde el cursor Sqlite está tomando demasiado tiempo
- La respuesta de Json es un android muy lento
- Mejora del rendimiento de OpenCV Android: seguimiento rápido de objetos
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>
- Correo java – obtener mensaje por ID de mensaje
- Android ViewPager – agregar presentación de diapositivas con animación de fade-in / out para cambiar entre vistas